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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.ui.refactoring.nls.search;
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;
22 import org.eclipse.core.runtime.CoreException;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.resources.IFile;
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;
33 import org.eclipse.jface.text.Position;
35 import org.eclipse.search.ui.text.Match;
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;
52 import org.eclipse.jdt.internal.corext.refactoring.nls.PropertyFileDocumentModel;
53 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
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;
60 class NLSSearchResultRequestor extends SearchRequestor {
62 * Matches are added to fResult. Element (group key) is IJavaElement or FileEntry.
65 private static final StringMatcher fgGetClassNameMatcher= new StringMatcher("*.class.getName()*", false, false); //$NON-NLS-1$
68 * Object to indicate that no key has been found.
69 * @see #findKey(Position, IJavaElement)
72 private static final String NO_KEY= new String();
74 private NLSSearchResult fResult;
75 private IFile fPropertiesFile;
76 private Properties fProperties;
77 private HashSet<String> fUsedPropertyNames;
79 public NLSSearchResultRequestor(IFile propertiesFile, NLSSearchResult result) {
80 fPropertiesFile= propertiesFile;
85 * @see org.eclipse.jdt.core.search.SearchRequestor#beginReporting()
88 public void beginReporting() {
90 fUsedPropertyNames= new HashSet<String>(fProperties.size());
94 * @see org.eclipse.jdt.core.search.SearchRequestor#acceptSearchMatch(org.eclipse.jdt.core.search.SearchMatch)
97 public void acceptSearchMatch(SearchMatch match) throws CoreException {
98 if (match.getAccuracy() == SearchMatch.A_INACCURATE)
101 int offset= match.getOffset();
102 int length= match.getLength();
103 if (offset == -1 || length == -1)
106 if (! (match.getElement() instanceof IJavaElement))
108 IJavaElement javaElement= (IJavaElement) match.getElement();
110 // ignore matches in import declarations:
111 if (javaElement.getElementType() == IJavaElement.IMPORT_DECLARATION)
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
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))
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$
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))
141 ICompilationUnit[] allCompilationUnits= JavaModelUtil.getAllCompilationUnits(new IJavaElement[] {javaElement});
142 Object element= javaElement;
143 if (allCompilationUnits != null && allCompilationUnits.length == 1)
144 element= allCompilationUnits[0];
146 fResult.addMatch(new Match(element, mutableKeyPosition.getOffset(), mutableKeyPosition.getLength()));
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);
156 for (Enumeration<?> enumeration= fProperties.propertyNames(); enumeration.hasMoreElements();) {
157 String propertyName= (String) enumeration.nextElement();
158 if (!fUsedPropertyNames.contains(propertyName)) {
159 addMatch(groupElement, propertyName);
165 fResult.addFileEntryGroup(groupElement);
171 private void addMatch(FileEntry groupElement, String propertyName) {
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.
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.
181 String escapedPropertyName= PropertyFileDocumentModel.escape(propertyName, false);
182 int start= findPropertyNameStartPosition(escapedPropertyName);
184 if (start == -1) { // not found -> report at beginning
188 length= escapedPropertyName.length();
190 fResult.addMatch(new Match(groupElement, start, length));
194 * Checks if the key is defined in the property file
195 * and adds it to the list of used properties.
198 * @return <code>true</code> if the key is defined, <code>false</code> otherwise
200 private boolean isKeyDefined(String key) {
204 fUsedPropertyNames.add(key);
205 if (fProperties.getProperty(key) != null) {
211 public boolean hasPropertyKey(String key) {
212 return fProperties.containsKey(key);
215 public boolean isUsedPropertyKey(String key) {
216 return fUsedPropertyNames.contains(key);
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.
224 * @param keyPositionResult reference parameter: will be filled with the position of the found
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>
231 private String findKey(Position keyPositionResult, IJavaElement enclosingElement) throws CoreException {
232 ICompilationUnit unit= (ICompilationUnit)enclosingElement.getAncestor(IJavaElement.COMPILATION_UNIT);
236 String source= unit.getSource();
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);
247 scanner= ToolFactory.createScanner(false, false, false, false);
249 scanner.setSource(source.toCharArray());
250 scanner.resetTo(keyPositionResult.getOffset() + keyPositionResult.getLength(), source.length());
253 if (scanner.getNextToken() != ITerminalSymbols.TokenNameDOT)
256 if (scanner.getNextToken() != ITerminalSymbols.TokenNameIdentifier)
259 String src= new String(scanner.getCurrentTokenSource());
260 int tokenStart= scanner.getCurrentTokenStartPosition();
261 int tokenEnd= scanner.getCurrentTokenEndPosition();
263 if (scanner.getNextToken() == ITerminalSymbols.TokenNameLPAREN) {
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)
270 tokenStart= scanner.getCurrentTokenStartPosition();
271 tokenEnd= scanner.getCurrentTokenEndPosition();
273 while ((token= scanner.getNextToken()) == ITerminalSymbols.TokenNameDOT) {
274 if ((nextToken= scanner.getNextToken()) != ITerminalSymbols.TokenNameIdentifier) {
277 tokenStart= scanner.getCurrentTokenStartPosition();
278 tokenEnd= scanner.getCurrentTokenEndPosition();
280 if (token != ITerminalSymbols.TokenNameRPAREN)
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$
297 Object obj= fields[i].getConstant();
298 return obj instanceof String ? ((String)obj).substring(1, ((String)obj).length() - 1) : NO_KEY;
304 keyPositionResult.setOffset(tokenStart);
305 keyPositionResult.setLength(tokenEnd - tokenStart + 1);
308 } catch (InvalidInputException e) {
309 throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
314 * Finds the start position in the property file. We assume that
315 * the key is the first match on a line.
317 * @param propertyName the property name
318 * @return the start position of the property name in the file, -1 if not found
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;
326 encoding= fPropertiesFile.getCharset();
327 } catch (CoreException e1) {
328 encoding= "ISO-8859-1"; //$NON-NLS-1$
331 stream= createInputStream(fPropertiesFile);
332 lineReader= new LineReader(stream, encoding);
333 } catch (CoreException cex) {
334 // failed to get input stream
337 } catch (IOException e) {
338 if (stream != null) {
341 } catch (IOException ce) {
349 StringBuffer buf= new StringBuffer(80);
350 int eols= lineReader.readLine(buf);
351 int keyLength= propertyName.length();
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
365 start += line.length() + eols;
367 eols= lineReader.readLine(buf);
371 start= -1; //key not found in file. See bug 63794. This can happen if the key contains escaped characters.
372 } catch (IOException ex) {
378 } catch (IOException ex) {
385 private void loadProperties() {
386 Set<Object> duplicateKeys= new HashSet<Object>();
387 fProperties= new Properties(duplicateKeys);
390 stream= new BufferedInputStream(createInputStream(fPropertiesFile));
391 } catch (CoreException ex) {
392 fProperties= new Properties();
396 fProperties.load(stream);
397 } catch (IOException ex) {
398 fProperties= new Properties();
403 } catch (IOException ex) {
405 reportDuplicateKeys(duplicateKeys);
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());
418 return propertiesFile.getContents();
421 private void reportDuplicateKeys(Set<Object> duplicateKeys) {
422 if (duplicateKeys.size() == 0)
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);
431 fResult.addFileEntryGroup(groupElement);