| /******************************************************************************* |
| * Copyright (c) 2000, 2011 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.rename; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.core.compiler.IScanner; |
| import org.eclipse.jdt.core.compiler.ITerminalSymbols; |
| import org.eclipse.jdt.core.compiler.InvalidInputException; |
| |
| |
| public class RefactoringScanner { |
| |
| private static int NO_MATCH= 0; |
| private static int MATCH_QUALIFIED= 1; |
| private static int MATCH_UNQUALIFIED= 2; |
| |
| public static class TextMatch { |
| |
| private int fStartPosition; |
| private boolean fQualified; |
| |
| private TextMatch(int startPosition, boolean qualified) { |
| fStartPosition= startPosition; |
| fQualified= qualified; |
| } |
| |
| /** |
| * @return the offset where the unqualified name starts |
| */ |
| public int getStartPosition() { |
| return fStartPosition; |
| } |
| |
| public boolean isQualified() { |
| return fQualified; |
| } |
| } |
| |
| private final String fName; |
| private final String fQualifier; |
| |
| private IScanner fScanner; |
| private Set<TextMatch> fMatches; //Set<TextMatch> |
| |
| |
| public RefactoringScanner(String name, String qualifier) { |
| Assert.isNotNull(name); |
| Assert.isNotNull(qualifier); |
| fName= name; |
| fQualifier= qualifier; |
| } |
| |
| public void scan(ICompilationUnit cu) throws JavaModelException { |
| char[] chars= cu.getBuffer().getCharacters(); |
| fMatches= new HashSet<TextMatch>(); |
| fScanner= ToolFactory.createScanner(true, true, false, true); |
| fScanner.setSource(chars); |
| |
| // IImportContainer importContainer= cu.getImportContainer(); |
| // if (importContainer.exists()) |
| // fNoFlyZone= importContainer.getSourceRange(); |
| // else |
| // fNoFlyZone= null; |
| |
| doScan(); |
| fScanner= null; |
| } |
| |
| /** |
| * Scan the given text. |
| * <p> |
| * <strong>NOTE:</strong> Use only for testing. |
| * </p> |
| * |
| * @param text the text |
| */ |
| public void scan(String text) { |
| char[] chars= text.toCharArray(); |
| fMatches= new HashSet<TextMatch>(); |
| fScanner= ToolFactory.createScanner(true, true, false, true); |
| fScanner.setSource(chars); |
| doScan(); |
| fScanner= null; |
| } |
| |
| private void doScan() { |
| try{ |
| int token = fScanner.getNextToken(); |
| while (token != ITerminalSymbols.TokenNameEOF) { |
| switch (token) { |
| case ITerminalSymbols.TokenNameStringLiteral : |
| case ITerminalSymbols.TokenNameCOMMENT_JAVADOC : |
| case ITerminalSymbols.TokenNameCOMMENT_LINE : |
| case ITerminalSymbols.TokenNameCOMMENT_BLOCK : |
| parseCurrentToken(); |
| } |
| token = fScanner.getNextToken(); |
| } |
| } catch (InvalidInputException e){ |
| //ignore |
| } |
| } |
| |
| private static boolean isWholeWord(String value, int from, int to){ |
| if (from > 0) { |
| char ch= value.charAt(from - 1); |
| if (Character.isLetterOrDigit(ch) || ch == '_') { |
| return false; |
| } |
| } |
| if (to < value.length()) { |
| char ch= value.charAt(to); |
| if (Character.isLetterOrDigit(ch) || ch == '_' ) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void parseCurrentToken() { |
| // only works for references without whitespace |
| String value = new String(fScanner.getRawTokenSource()); |
| int start= fScanner.getCurrentTokenStartPosition(); |
| int index= value.indexOf(fName); |
| while (index != -1) { |
| if (isWholeWord(value, index, index + fName.length())) { |
| int ok= isQualifierOK(value, index); |
| if (ok > NO_MATCH) |
| addMatch(start + index, ok); |
| } |
| index= value.indexOf(fName, index + 1); |
| } |
| } |
| |
| private int isQualifierOK(String value, int nameStart) { |
| // only works for references without whitespace |
| int qualifierAfter= nameStart - 1; |
| if (qualifierAfter < 0) |
| // there is absolutely nothing before the name itself in the string |
| return MATCH_UNQUALIFIED; |
| |
| char charBeforeName= value.charAt(qualifierAfter); |
| if (! isQualifierSeparator(charBeforeName)) |
| // the char before the name is not a # or . - should not get here anyway |
| return MATCH_UNQUALIFIED; // NO_MATCH ? |
| |
| boolean canFinish= charBeforeName == '#'; |
| // work through the qualifier from back to front |
| for (int i= 0; i < fQualifier.length() ; i++) { |
| int qualifierCharPos= qualifierAfter - 1 - i; |
| if (qualifierCharPos < 0) |
| // the position does not exist, return OK if last read char was a non-separator |
| return canFinish ? MATCH_UNQUALIFIED : NO_MATCH; |
| |
| char qualifierChar= value.charAt(qualifierCharPos); |
| char goalQualifierChar= fQualifier.charAt(fQualifier.length() - 1 - i); |
| if (qualifierChar != goalQualifierChar) |
| // the chars do not match. return OK if last read char was a non-separator and the current one a non-qualifier |
| return (canFinish && !isQualifierPart(qualifierChar)) ? MATCH_UNQUALIFIED : NO_MATCH; |
| |
| canFinish= ! isQualifierSeparator(qualifierChar); |
| } |
| int beforeQualifierPos= qualifierAfter - fQualifier.length() - 1; |
| if (beforeQualifierPos >= 0) { |
| char beforeQualifierChar= value.charAt(beforeQualifierPos); |
| if (Character.isJavaIdentifierPart(beforeQualifierChar)) { |
| return NO_MATCH; |
| } |
| if (isQualifierSeparator(beforeQualifierChar)) { |
| if (beforeQualifierPos > 0) { |
| /* |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=142508 : |
| * If the character before the qualifier separator is not |
| * an identifier part, then accept the match. |
| */ |
| char precedingOne= value.charAt(beforeQualifierPos - 1); |
| if (Character.isJavaIdentifierPart(precedingOne)) { |
| return NO_MATCH; |
| } |
| } |
| } |
| return MATCH_QUALIFIED; |
| |
| } |
| return MATCH_QUALIFIED; |
| } |
| |
| private boolean isQualifierPart(char ch) { |
| return Character.isJavaIdentifierPart(ch) || isQualifierSeparator(ch); |
| } |
| |
| private boolean isQualifierSeparator(char c) { |
| return ".#".indexOf(c) != -1; //$NON-NLS-1$ |
| } |
| |
| private void addMatch(int matchStart, int matchCode) { |
| fMatches.add(new TextMatch(matchStart, matchCode == MATCH_QUALIFIED)); |
| } |
| |
| /** |
| * @return Set of TextMatch |
| */ |
| public Set<TextMatch> getMatches() { |
| return fMatches; |
| } |
| } |
| |