]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-before/ui refactoring/org/eclipse/jdt/internal/ui/refactoring/nls/search/NLSSearchResultRequestor.java
Some talks, mostly identical.
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui refactoring / org / eclipse / jdt / internal / ui / refactoring / nls / search / NLSSearchResultRequestor.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.ui.refactoring.nls.search;
12
13 import java.io.BufferedInputStream;
14 import java.io.ByteArrayInputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.Enumeration;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.Set;
21
22 import org.eclipse.core.runtime.CoreException;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25
26 import org.eclipse.core.resources.IFile;
27
28 import org.eclipse.core.filebuffers.FileBuffers;
29 import org.eclipse.core.filebuffers.ITextFileBuffer;
30 import org.eclipse.core.filebuffers.ITextFileBufferManager;
31 import org.eclipse.core.filebuffers.LocationKind;
32
33 import org.eclipse.jface.text.Position;
34
35 import org.eclipse.search.ui.text.Match;
36
37 import org.eclipse.jdt.core.ICompilationUnit;
38 import org.eclipse.jdt.core.IField;
39 import org.eclipse.jdt.core.IJavaElement;
40 import org.eclipse.jdt.core.IJavaProject;
41 import org.eclipse.jdt.core.ISourceReference;
42 import org.eclipse.jdt.core.IType;
43 import org.eclipse.jdt.core.JavaCore;
44 import org.eclipse.jdt.core.Signature;
45 import org.eclipse.jdt.core.ToolFactory;
46 import org.eclipse.jdt.core.compiler.IScanner;
47 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
48 import org.eclipse.jdt.core.compiler.InvalidInputException;
49 import org.eclipse.jdt.core.search.SearchMatch;
50 import org.eclipse.jdt.core.search.SearchRequestor;
51
52 import org.eclipse.jdt.internal.corext.refactoring.nls.PropertyFileDocumentModel;
53 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
54
55 import org.eclipse.jdt.internal.ui.JavaPlugin;
56 import org.eclipse.jdt.internal.ui.JavaUIStatus;
57 import org.eclipse.jdt.internal.ui.util.StringMatcher;
58
59
60 class NLSSearchResultRequestor extends SearchRequestor {
61         /*
62          * Matches are added to fResult. Element (group key) is IJavaElement or FileEntry.
63          */
64
65         private static final StringMatcher fgGetClassNameMatcher= new StringMatcher("*.class.getName()*", false, false);  //$NON-NLS-1$
66
67         /**
68          * Object to indicate that no key has been found.
69          * @see #findKey(Position, IJavaElement)
70          * @since 3.6
71          */
72         private static final String NO_KEY= new String();
73
74         private NLSSearchResult fResult;
75         private IFile fPropertiesFile;
76         private Properties fProperties;
77         private HashSet<String> fUsedPropertyNames;
78
79         public NLSSearchResultRequestor(IFile propertiesFile, NLSSearchResult result) {
80                 fPropertiesFile= propertiesFile;
81                 fResult= result;
82         }
83
84         /*
85          * @see org.eclipse.jdt.core.search.SearchRequestor#beginReporting()
86          */
87         @Override
88         public void beginReporting() {
89                 loadProperties();
90                 fUsedPropertyNames= new HashSet<String>(fProperties.size());
91         }
92
93         /*
94          * @see org.eclipse.jdt.core.search.SearchRequestor#acceptSearchMatch(org.eclipse.jdt.core.search.SearchMatch)
95          */
96         @Override
97         public void acceptSearchMatch(SearchMatch match) throws CoreException {
98                 if (match.getAccuracy() == SearchMatch.A_INACCURATE)
99                         return;
100
101                 int offset= match.getOffset();
102                 int length= match.getLength();
103                 if (offset == -1 || length == -1)
104                         return;
105
106                 if (! (match.getElement() instanceof IJavaElement))
107                         return;
108                 IJavaElement javaElement= (IJavaElement) match.getElement();
109
110                 // ignore matches in import declarations:
111                 if (javaElement.getElementType() == IJavaElement.IMPORT_DECLARATION)
112                         return;
113                 if (javaElement.getElementType() == IJavaElement.CLASS_FILE)
114                         return; //matches in import statements of class files
115                 if (javaElement.getElementType() == IJavaElement.TYPE)
116                         return; //classes extending the accessor class and workaround for bug 61286
117
118                 // heuristic: ignore matches in resource bundle name field:
119                 if (javaElement.getElementType() == IJavaElement.FIELD) {
120                         IField field= (IField) javaElement;
121                         String source= field.getSource();
122                         if (source != null && fgGetClassNameMatcher.match(source))
123                                 return;
124                 }
125
126                 if (javaElement instanceof ISourceReference) {
127                         String source= ((ISourceReference) javaElement).getSource();
128                         if (source != null) {
129                                 if (source.indexOf("NLS.initializeMessages") != -1) //$NON-NLS-1$
130                                         return;
131                         }
132                 }
133
134                 // found reference to NLS Wrapper - now check if the key is there:
135                 Position mutableKeyPosition= new Position(offset, length);
136                 //TODO: What to do if argument string not found? Currently adds a match with type name.
137                 String key= findKey(mutableKeyPosition, javaElement);
138                 if (key == null || isKeyDefined(key))
139                         return;
140
141                 ICompilationUnit[] allCompilationUnits= JavaModelUtil.getAllCompilationUnits(new IJavaElement[] {javaElement});
142                 Object element= javaElement;
143                 if (allCompilationUnits != null && allCompilationUnits.length == 1)
144                         element= allCompilationUnits[0];
145
146                 fResult.addMatch(new Match(element, mutableKeyPosition.getOffset(), mutableKeyPosition.getLength()));
147         }
148
149         public void reportUnusedPropertyNames(IProgressMonitor pm) {
150                 //Don't use endReporting() for long running operation.
151                 pm.beginTask("", fProperties.size()); //$NON-NLS-1$
152                 boolean hasUnused= false;
153                 pm.setTaskName(NLSSearchMessages.NLSSearchResultRequestor_searching);
154                 FileEntry groupElement= new FileEntry(fPropertiesFile, NLSSearchMessages.NLSSearchResultCollector_unusedKeys);
155
156                 for (Enumeration<?> enumeration= fProperties.propertyNames(); enumeration.hasMoreElements();) {
157                         String propertyName= (String) enumeration.nextElement();
158                         if (!fUsedPropertyNames.contains(propertyName)) {
159                                 addMatch(groupElement, propertyName);
160                                 hasUnused= true;
161                         }
162                         pm.worked(1);
163                 }
164                 if (hasUnused)
165                         fResult.addFileEntryGroup(groupElement);
166                 pm.done();
167         }
168
169
170
171         private void addMatch(FileEntry groupElement, String propertyName) {
172                 /*
173                  * TODO (bug 63794): Should read in .properties file with our own reader and not
174                  * with Properties.load(InputStream) . Then, we can remember start position and
175                  * original version (not interpreting escape characters) for each property.
176                  *
177                  * The current workaround is to escape the key again before searching in the
178                  * .properties file. However, this can fail if the key is escaped in a different
179                  * manner than what PropertyFileDocumentModel.unwindEscapeChars(.) produces.
180                  */
181                 String escapedPropertyName= PropertyFileDocumentModel.escape(propertyName, false);
182                 int start= findPropertyNameStartPosition(escapedPropertyName);
183                 int length;
184                 if (start == -1) { // not found -> report at beginning
185                         start= 0;
186                         length= 0;
187                 } else {
188                         length= escapedPropertyName.length();
189                 }
190                 fResult.addMatch(new Match(groupElement, start, length));
191         }
192
193         /**
194          * Checks if the key is defined in the property file
195          * and adds it to the list of used properties.
196          *
197          * @param key the key
198          * @return <code>true</code> if the key is defined, <code>false</code> otherwise
199          */
200         private boolean isKeyDefined(String key) {
201                 if (key == NO_KEY)
202                         return false;
203
204                 fUsedPropertyNames.add(key);
205                 if (fProperties.getProperty(key) != null) {
206                         return true;
207                 }
208                 return false;
209         }
210
211         public boolean hasPropertyKey(String key) {
212                 return fProperties.containsKey(key);
213         }
214
215         public boolean isUsedPropertyKey(String key) {
216                 return fUsedPropertyNames.contains(key);
217         }
218
219         /**
220          * Finds the key defined by the given match. The assumption is that the key is the only argument
221          * and it is a string literal i.e. quoted ("...") or a string constant i.e. 'static final
222          * String' defined in the same class.
223          * 
224          * @param keyPositionResult reference parameter: will be filled with the position of the found
225          *            key
226          * @param enclosingElement enclosing java element
227          * @return a string denoting the key, {@link #NO_KEY} if no key can be found and
228          *         <code>null</code> otherwise
229          * @throws CoreException if a problem occurs while accessing the <code>enclosingElement</code>
230          */
231         private String findKey(Position keyPositionResult, IJavaElement enclosingElement) throws CoreException {
232                 ICompilationUnit unit= (ICompilationUnit)enclosingElement.getAncestor(IJavaElement.COMPILATION_UNIT);
233                 if (unit == null)
234                         return null;
235
236                 String source= unit.getSource();
237                 if (source == null)
238                         return null;
239
240                 IJavaProject javaProject= unit.getJavaProject();
241                 IScanner scanner= null;
242                 if (javaProject != null) {
243                         String complianceLevel= javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
244                         String sourceLevel= javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
245                         scanner= ToolFactory.createScanner(false, false, false, sourceLevel, complianceLevel);
246                 } else {
247                         scanner= ToolFactory.createScanner(false, false, false, false);
248                 }
249                 scanner.setSource(source.toCharArray());
250                 scanner.resetTo(keyPositionResult.getOffset() + keyPositionResult.getLength(), source.length());
251
252                 try {
253                         if (scanner.getNextToken() != ITerminalSymbols.TokenNameDOT)
254                                 return null;
255
256                         if (scanner.getNextToken() != ITerminalSymbols.TokenNameIdentifier)
257                                 return null;
258
259                         String src= new String(scanner.getCurrentTokenSource());
260                         int tokenStart= scanner.getCurrentTokenStartPosition();
261                         int tokenEnd= scanner.getCurrentTokenEndPosition();
262
263                         if (scanner.getNextToken() == ITerminalSymbols.TokenNameLPAREN) {
264                                 // Old school
265                                 // next must be key string. Ignore methods which do not take a single String parameter (Bug 295040).
266                                 int nextToken= scanner.getNextToken();
267                                 if (nextToken != ITerminalSymbols.TokenNameStringLiteral && nextToken != ITerminalSymbols.TokenNameIdentifier)
268                                         return null;
269
270                                 tokenStart= scanner.getCurrentTokenStartPosition();
271                                 tokenEnd= scanner.getCurrentTokenEndPosition();
272                                 int token;
273                                 while ((token= scanner.getNextToken()) == ITerminalSymbols.TokenNameDOT) {
274                                         if ((nextToken= scanner.getNextToken()) != ITerminalSymbols.TokenNameIdentifier) {
275                                                         return null;
276                                         }
277                                         tokenStart= scanner.getCurrentTokenStartPosition();
278                                         tokenEnd= scanner.getCurrentTokenEndPosition();
279                                 }
280                                 if (token != ITerminalSymbols.TokenNameRPAREN)
281                                         return null;
282                                 
283                                 if (nextToken == ITerminalSymbols.TokenNameStringLiteral) {
284                                         keyPositionResult.setOffset(tokenStart + 1);
285                                         keyPositionResult.setLength(tokenEnd - tokenStart - 1);
286                                         return source.substring(tokenStart + 1, tokenEnd);
287                                 } else if (nextToken == ITerminalSymbols.TokenNameIdentifier) {
288                                         keyPositionResult.setOffset(tokenStart);
289                                         keyPositionResult.setLength(tokenEnd - tokenStart + 1);
290                                         IType parentClass= (IType)enclosingElement.getAncestor(IJavaElement.TYPE);
291                                         IField[] fields= parentClass.getFields();
292                                         String identifier= source.substring(tokenStart, tokenEnd + 1);
293                                         for (int i= 0; i < fields.length; i++) {
294                                                 if (fields[i].getElementName().equals(identifier)) {
295                                                         if (!Signature.getSignatureSimpleName(fields[i].getTypeSignature()).equals("String")) //$NON-NLS-1$
296                                                                 return null;
297                                                         Object obj= fields[i].getConstant();
298                                                         return obj instanceof String ? ((String)obj).substring(1, ((String)obj).length() - 1) : NO_KEY;
299                                                 }
300                                         }
301                                 }
302                                 return NO_KEY;
303                         } else {
304                                 keyPositionResult.setOffset(tokenStart);
305                                 keyPositionResult.setLength(tokenEnd - tokenStart + 1);
306                                 return src;
307                         }
308                 } catch (InvalidInputException e) {
309                         throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
310                 }
311         }
312
313         /**
314          * Finds the start position in the property file. We assume that
315          * the key is the first match on a line.
316          *
317          * @param propertyName the property name
318          * @return      the start position of the property name in the file, -1 if not found
319          */
320         private int findPropertyNameStartPosition(String propertyName) {
321                 // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=19319
322                 InputStream stream= null;
323                 LineReader lineReader= null;
324                 String encoding;
325                 try {
326                         encoding= fPropertiesFile.getCharset();
327                 } catch (CoreException e1) {
328                         encoding= "ISO-8859-1";  //$NON-NLS-1$
329                 }
330                 try {
331                         stream= createInputStream(fPropertiesFile);
332                         lineReader= new LineReader(stream, encoding);
333                 } catch (CoreException cex) {
334                         // failed to get input stream
335                         JavaPlugin.log(cex);
336                         return -1;
337                 } catch (IOException e) {
338                         if (stream != null) {
339                                 try {
340                                         stream.close();
341                                 } catch (IOException ce) {
342                                         JavaPlugin.log(ce);
343                                 }
344                         }
345                         return -1;
346                 }
347                 int start= 0;
348                 try {
349                         StringBuffer buf= new StringBuffer(80);
350                         int eols= lineReader.readLine(buf);
351                         int keyLength= propertyName.length();
352                         while (eols > 0) {
353                                 String line= buf.toString();
354                                 int i= line.indexOf(propertyName);
355                                 int charPos= i + keyLength;
356                                 char terminatorChar= 0;
357                                 boolean hasNoValue= (charPos >= line.length());
358                                 if (i > -1 && !hasNoValue)
359                                         terminatorChar= line.charAt(charPos);
360                                 if (line.trim().startsWith(propertyName) &&
361                                                 (hasNoValue || Character.isWhitespace(terminatorChar) || terminatorChar == '=')) {
362                                         start += line.indexOf(propertyName);
363                                         eols= -17; // found key
364                                 } else {
365                                         start += line.length() + eols;
366                                         buf.setLength(0);
367                                         eols= lineReader.readLine(buf);
368                                 }
369                         }
370                         if (eols != -17)
371                                 start= -1; //key not found in file. See bug 63794. This can happen if the key contains escaped characters.
372                 } catch (IOException ex) {
373                         JavaPlugin.log(ex);
374                         return -1;
375                 } finally {
376                         try {
377                                 lineReader.close();
378                         } catch (IOException ex) {
379                                 JavaPlugin.log(ex);
380                         }
381                 }
382                 return start;
383         }
384
385         private void loadProperties() {
386                 Set<Object> duplicateKeys= new HashSet<Object>();
387                 fProperties= new Properties(duplicateKeys);
388                 InputStream stream;
389                 try {
390                         stream= new BufferedInputStream(createInputStream(fPropertiesFile));
391                 } catch (CoreException ex) {
392                         fProperties= new Properties();
393                         return;
394                 }
395                 try {
396                         fProperties.load(stream);
397                 } catch (IOException ex) {
398                         fProperties= new Properties();
399                         return;
400                 } finally {
401                         try {
402                                 stream.close();
403                         } catch (IOException ex) {
404                         }
405                         reportDuplicateKeys(duplicateKeys);
406                 }
407         }
408
409         private InputStream createInputStream(IFile propertiesFile) throws CoreException {
410                 ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
411                 if (manager != null) {
412                         ITextFileBuffer buffer= manager.getTextFileBuffer(propertiesFile.getFullPath(), LocationKind.IFILE);
413                         if (buffer != null) {
414                                 return new ByteArrayInputStream(buffer.getDocument().get().getBytes());
415                         }
416                 }
417
418                 return propertiesFile.getContents();
419         }
420
421         private void reportDuplicateKeys(Set<Object> duplicateKeys) {
422                 if (duplicateKeys.size() == 0)
423                         return;
424
425                 FileEntry groupElement= new FileEntry(fPropertiesFile, NLSSearchMessages.NLSSearchResultCollector_duplicateKeys);
426                 Iterator<Object> iter= duplicateKeys.iterator();
427                 while (iter.hasNext()) {
428                         String propertyName= (String) iter.next();
429                         addMatch(groupElement, propertyName);
430                 }
431                 fResult.addFileEntryGroup(groupElement);
432         }
433
434 }