content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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.wst.jsdt.internal.corext.refactoring.rename;
  
  import java.util.HashSet;
  import java.util.Set;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.ToolFactory;
  import org.eclipse.wst.jsdt.core.compiler.IScanner;
  import org.eclipse.wst.jsdt.core.compiler.ITerminalSymbols;
  import org.eclipse.wst.jsdt.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 fMatches; //Set<TextMatch>
  
  	
  	public RefactoringScanner(String name, String qualifier) {
  		Assert.isNotNull(name);
  		Assert.isNotNull(qualifier);
  		fName= name;
  		fQualifier= qualifier;
  	}
  	
  	public void scan(IJavaScriptUnit cu)	throws JavaScriptModelException {
  		char[] chars= cu.getBuffer().getCharacters();
  		fMatches= new HashSet();
  		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;
  	}
  
  	/** only for testing */
  	public void scan(String text) {
  		char[] chars= text.toCharArray();
  		fMatches= new HashSet();
  		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 getMatches() {
  		return fMatches;
  	}
  }
  
  