/*******************************************************************************
 * Copyright (c) 2000, 2014 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.aspectj.org.eclipse.jdt.internal.codeassist;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.aspectj.org.eclipse.jdt.core.IBuffer;
import org.aspectj.org.eclipse.jdt.core.IMember;
import org.aspectj.org.eclipse.jdt.core.IOpenable;
import org.aspectj.org.eclipse.jdt.core.ISourceRange;
import org.aspectj.org.eclipse.jdt.core.IType;
import org.aspectj.org.eclipse.jdt.core.JavaModelException;
import org.aspectj.org.eclipse.jdt.core.Signature;
import org.aspectj.org.eclipse.jdt.core.WorkingCopyOwner;
import org.aspectj.org.eclipse.jdt.core.compiler.*;
import org.aspectj.org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.aspectj.org.eclipse.jdt.core.search.IJavaSearchScope;
import org.aspectj.org.eclipse.jdt.core.search.SearchPattern;
import org.aspectj.org.eclipse.jdt.core.search.TypeNameMatch;
import org.aspectj.org.eclipse.jdt.core.search.TypeNameMatchRequestor;
import org.aspectj.org.eclipse.jdt.internal.codeassist.impl.*;
import org.aspectj.org.eclipse.jdt.internal.codeassist.select.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.aspectj.org.eclipse.jdt.internal.compiler.env.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.parser.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.problem.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
import org.aspectj.org.eclipse.jdt.internal.compiler.util.ObjectVector;
import org.aspectj.org.eclipse.jdt.internal.core.BinaryTypeConverter;
import org.aspectj.org.eclipse.jdt.internal.core.ClassFile;
import org.aspectj.org.eclipse.jdt.internal.core.JavaModelManager;
import org.aspectj.org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.aspectj.org.eclipse.jdt.internal.core.SelectionRequestor;
import org.aspectj.org.eclipse.jdt.internal.core.SourceType;
import org.aspectj.org.eclipse.jdt.internal.core.SourceTypeElementInfo;
import org.aspectj.org.eclipse.jdt.internal.core.search.BasicSearchEngine;
import org.aspectj.org.eclipse.jdt.internal.core.search.TypeNameMatchRequestorWrapper;
import org.aspectj.org.eclipse.jdt.internal.core.util.ASTNodeFinder;
import org.aspectj.org.eclipse.jdt.internal.core.util.HashSetOfCharArrayArray;

/**
 * The selection engine is intended to infer the nature of a selected name in some
 * source code. This name can be qualified.
 *
 * Selection is resolving context using a name environment (no need to search), assuming
 * the source where selection occurred is correct and will not perform any completion
 * attempt. If this was the desired behavior, a call to the CompletionEngine should be
 * performed instead.
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public final class SelectionEngine extends Engine implements ISearchRequestor {
	
	private static class SelectionTypeNameMatchRequestorWrapper extends TypeNameMatchRequestorWrapper {
		
		class AcceptedType {
			public int modifiers;
			public char[] packageName;
			public char[] simpleTypeName;
			public String path;
			public AccessRestriction access;
			
			public AcceptedType(int modifiers, char[] packageName, char[] simpleTypeName, String path, AccessRestriction access) {
				this.modifiers = modifiers;
				this.packageName = packageName;
				this.simpleTypeName = simpleTypeName;
				this.path = path;
				this.access = access;
			}
		}
		
		private ImportReference[] importReferences;
		
		private boolean importCachesNodeInitialized = false;
		private ImportReference[] onDemandImportsNodeCache;
		private int onDemandImportsNodeCacheCount;
		private char[][][] importsNodeCache;
		private int importsNodeCacheCount;
		
		private HashtableOfObject onDemandFound = new HashtableOfObject();
		private ObjectVector notImportedFound = new ObjectVector();
		
		public SelectionTypeNameMatchRequestorWrapper(TypeNameMatchRequestor requestor, IJavaSearchScope scope, ImportReference[] importReferences) {
			super(requestor, scope);
			this.importReferences = importReferences;
		}
		
		public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path, AccessRestriction access) {
			if (enclosingTypeNames != null && enclosingTypeNames.length > 0) return;
			
			if (!this.importCachesNodeInitialized) initializeImportNodeCaches();
			
			char[] fullyQualifiedTypeName = CharOperation.concat(packageName, simpleTypeName, '.');
			
			for (int i = 0; i < this.importsNodeCacheCount; i++) {
				char[][] importName = this.importsNodeCache[i];
				if (CharOperation.equals(importName[0], simpleTypeName)) {
					
					if(CharOperation.equals(importName[1], fullyQualifiedTypeName)) {
						super.acceptType(modifiers, packageName, simpleTypeName, enclosingTypeNames, path, access);
					}
					return;
				}
			}
			
			for (int i = 0; i < this.onDemandImportsNodeCacheCount; i++) {
				char[][] importName = this.onDemandImportsNodeCache[i].tokens;
				char[] importFlatName = CharOperation.concatWith(importName, '.');
				
				if (CharOperation.equals(importFlatName, packageName)) {
					
					this.onDemandFound.put(simpleTypeName, simpleTypeName);
					super.acceptType(modifiers, packageName, simpleTypeName, enclosingTypeNames, path, access);
					return;
				}
			}
			
			
			this.notImportedFound.add(new AcceptedType(modifiers, packageName, simpleTypeName, path, access));
		}
		
		public void acceptNotImported() {
			int size = this.notImportedFound.size();
			for (int i = 0; i < size; i++) {
				AcceptedType acceptedType = (AcceptedType)this.notImportedFound.elementAt(i);
				
				if (this.onDemandFound.get(acceptedType.simpleTypeName) == null) {
					super.acceptType(
							acceptedType.modifiers,
							acceptedType.packageName,
							acceptedType.simpleTypeName,
							null,
							acceptedType.path,
							acceptedType.access);
				}
			}
		}
		
		public void initializeImportNodeCaches() {
			int length = this.importReferences == null ? 0 : this.importReferences.length;
			
			for (int i = 0; i < length; i++) {
				ImportReference importReference = this.importReferences[i];
				if((importReference.bits & ASTNode.OnDemand) != 0) {
					if(this.onDemandImportsNodeCache == null) {
						this.onDemandImportsNodeCache = new ImportReference[length - i];
					}
					this.onDemandImportsNodeCache[this.onDemandImportsNodeCacheCount++] =
						importReference;
				} else {
					if(this.importsNodeCache == null) {
						this.importsNodeCache = new char[length - i][][];
					}
					
					
					this.importsNodeCache[this.importsNodeCacheCount++] = new char[][]{
							importReference.tokens[importReference.tokens.length - 1],
							CharOperation.concatWith(importReference.tokens, '.')
						};
				}
			}
			
			this.importCachesNodeInitialized = true;
		}
	}

	public static boolean DEBUG = false;
	public static boolean PERF = false;

	SelectionParser parser;
	ISelectionRequestor requestor;
	WorkingCopyOwner owner;

	boolean acceptedAnswer;

	private int actualSelectionStart;
	private int actualSelectionEnd;
	private char[] selectedIdentifier;

	private char[][][] acceptedClasses;
	private int[] acceptedClassesModifiers;
	private char[][][] acceptedInterfaces;
	private int[] acceptedInterfacesModifiers;
	private char[][][] acceptedEnums;
	private int[] acceptedEnumsModifiers;
	private char[][][] acceptedAnnotations;
	private int[] acceptedAnnotationsModifiers;
	int acceptedClassesCount;
	int acceptedInterfacesCount;
	int acceptedEnumsCount;
	int acceptedAnnotationsCount;

	boolean noProposal = true;
	CategorizedProblem problem = null;

	/**
	 * The SelectionEngine is responsible for computing the selected object.
	 *
	 * It requires a searchable name environment, which supports some
	 * specific search APIs, and a requestor to feed back the results to a UI.
	 *
	 *  @param nameEnvironment org.aspectj.org.eclipse.jdt.internal.core.SearchableEnvironment
	 *      used to resolve type/package references and search for types/packages
	 *      based on partial names.
	 *
	 *  @param requestor org.aspectj.org.eclipse.jdt.internal.codeassist.ISelectionRequestor
	 *      since the engine might produce answers of various forms, the engine
	 *      is associated with a requestor able to accept all possible completions.
	 *
	 *  @param settings java.util.Map
	 *		set of options used to configure the code assist engine.
	 */
	public SelectionEngine(
		SearchableEnvironment nameEnvironment,
		ISelectionRequestor requestor,
		Map settings,
		WorkingCopyOwner owner) {

		super(settings);

		this.requestor = requestor;
		this.nameEnvironment = nameEnvironment;

		ProblemReporter problemReporter =
			new ProblemReporter(
				DefaultErrorHandlingPolicies.proceedWithAllProblems(),
				this.compilerOptions,
				new DefaultProblemFactory(Locale.getDefault())) {

			public CategorizedProblem createProblem(
				char[] fileName,
				int problemId,
				String[] problemArguments,
				String[] messageArguments,
				int severity,
				int problemStartPosition,
				int problemEndPosition,
				int lineNumber,
				int columnNumber) {
				CategorizedProblem pb =  super.createProblem(
					fileName,
					problemId,
					problemArguments,
					messageArguments,
					severity,
					problemStartPosition,
					problemEndPosition,
					lineNumber,
					columnNumber);
					if(SelectionEngine.this.problem == null && pb.isError() && (pb.getID() & IProblem.Syntax) == 0) {
						SelectionEngine.this.problem = pb;
					}

					return pb;
			}
		};
		this.lookupEnvironment =
			new LookupEnvironment(this, this.compilerOptions, problemReporter, nameEnvironment);
		this.parser = new SelectionParser(problemReporter);
		this.owner = owner;
	}
	
	public void acceptConstructor(
			int modifiers,
			char[] simpleTypeName,
			int parameterCount,
			char[] signature,
			char[][] parameterTypes,
			char[][] parameterNames,
			int typeModifiers,
			char[] packageName,
			int extraFlags,
			String path,
			AccessRestriction access) {
		// constructors aren't searched
	}

	public void acceptType(char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, int modifiers, AccessRestriction accessRestriction) {
		char[] typeName = enclosingTypeNames == null ?
				simpleTypeName :
					CharOperation.concat(
						CharOperation.concatWith(enclosingTypeNames, '.'),
						simpleTypeName,
						'.');

		if (CharOperation.equals(simpleTypeName, this.selectedIdentifier)) {
			char[] flatEnclosingTypeNames =
				enclosingTypeNames == null || enclosingTypeNames.length == 0 ?
						null :
							CharOperation.concatWith(enclosingTypeNames, '.');
			if(mustQualifyType(packageName, simpleTypeName, flatEnclosingTypeNames, modifiers)) {
				int length = 0;
				int kind = modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccEnum | ClassFileConstants.AccAnnotation);
				switch (kind) {
					case ClassFileConstants.AccAnnotation:
					case ClassFileConstants.AccAnnotation | ClassFileConstants.AccInterface:
						char[][] acceptedAnnotation = new char[2][];
						acceptedAnnotation[0] = packageName;
						acceptedAnnotation[1] = typeName;

						if(this.acceptedAnnotations == null) {
							this.acceptedAnnotations = new char[10][][];
							this.acceptedAnnotationsModifiers = new int[10];
							this.acceptedAnnotationsCount = 0;
						}
						length = this.acceptedAnnotations.length;
						if(length == this.acceptedAnnotationsCount) {
							int newLength = (length + 1)* 2;
							System.arraycopy(this.acceptedAnnotations, 0, this.acceptedAnnotations = new char[newLength][][], 0, length);
							System.arraycopy(this.acceptedAnnotationsModifiers, 0, this.acceptedAnnotationsModifiers = new int[newLength], 0, length);
						}
						this.acceptedAnnotationsModifiers[this.acceptedAnnotationsCount] = modifiers;
						this.acceptedAnnotations[this.acceptedAnnotationsCount++] = acceptedAnnotation;
						break;
					case ClassFileConstants.AccEnum:
						char[][] acceptedEnum = new char[2][];
						acceptedEnum[0] = packageName;
						acceptedEnum[1] = typeName;

						if(this.acceptedEnums == null) {
							this.acceptedEnums = new char[10][][];
							this.acceptedEnumsModifiers = new int[10];
							this.acceptedEnumsCount = 0;
						}
						length = this.acceptedEnums.length;
						if(length == this.acceptedEnumsCount) {
							int newLength = (length + 1)* 2;
							System.arraycopy(this.acceptedEnums, 0, this.acceptedEnums = new char[newLength][][], 0, length);
							System.arraycopy(this.acceptedEnumsModifiers, 0, this.acceptedEnumsModifiers = new int[newLength], 0, length);
						}
						this.acceptedEnumsModifiers[this.acceptedEnumsCount] = modifiers;
						this.acceptedEnums[this.acceptedEnumsCount++] = acceptedEnum;
						break;
					case ClassFileConstants.AccInterface:
						char[][] acceptedInterface= new char[2][];
						acceptedInterface[0] = packageName;
						acceptedInterface[1] = typeName;

						if(this.acceptedInterfaces == null) {
							this.acceptedInterfaces = new char[10][][];
							this.acceptedInterfacesModifiers = new int[10];
							this.acceptedInterfacesCount = 0;
						}
						length = this.acceptedInterfaces.length;
						if(length == this.acceptedInterfacesCount) {
							int newLength = (length + 1)* 2;
							System.arraycopy(this.acceptedInterfaces, 0, this.acceptedInterfaces = new char[newLength][][], 0, length);
							System.arraycopy(this.acceptedInterfacesModifiers, 0, this.acceptedInterfacesModifiers = new int[newLength], 0, length);
						}
						this.acceptedInterfacesModifiers[this.acceptedInterfacesCount] = modifiers;
						this.acceptedInterfaces[this.acceptedInterfacesCount++] = acceptedInterface;
						break;
					default:
						char[][] acceptedClass = new char[2][];
						acceptedClass[0] = packageName;
						acceptedClass[1] = typeName;

						if(this.acceptedClasses == null) {
							this.acceptedClasses = new char[10][][];
							this.acceptedClassesModifiers = new int[10];
							this.acceptedClassesCount = 0;
						}
						length = this.acceptedClasses.length;
						if(length == this.acceptedClassesCount) {
							int newLength = (length + 1)* 2;
							System.arraycopy(this.acceptedClasses, 0, this.acceptedClasses = new char[newLength][][], 0, length);
							System.arraycopy(this.acceptedClassesModifiers, 0, this.acceptedClassesModifiers = new int[newLength], 0, length);
						}
						this.acceptedClassesModifiers[this.acceptedClassesCount] = modifiers;
						this.acceptedClasses[this.acceptedClassesCount++] = acceptedClass;
						break;
				}
			} else {
				this.noProposal = false;
				this.requestor.acceptType(
					packageName,
					typeName,
					modifiers,
					false,
					null,
					this.actualSelectionStart,
					this.actualSelectionEnd);
				this.acceptedAnswer = true;
			}
		}
	}

	/**
	 * One result of the search consists of a new package.
	 * @param packageName char[]
	 *
	 * NOTE - All package names are presented in their readable form:
	 *    Package names are in the form "a.b.c".
	 *    The default package is represented by an empty array.
	 */
	public void acceptPackage(char[] packageName) {
		// implementation of interface method
	}

	private void acceptQualifiedTypes() {
		if(this.acceptedClasses != null){
			this.acceptedAnswer = true;
			for (int i = 0; i < this.acceptedClassesCount; i++) {
				this.noProposal = false;
				this.requestor.acceptType(
					this.acceptedClasses[i][0],
					this.acceptedClasses[i][1],
					this.acceptedClassesModifiers[i],
					false,
					null,
					this.actualSelectionStart,
					this.actualSelectionEnd);
			}
			this.acceptedClasses = null;
			this.acceptedClassesModifiers = null;
			this.acceptedClassesCount = 0;
		}
		if(this.acceptedInterfaces != null){
			this.acceptedAnswer = true;
			for (int i = 0; i < this.acceptedInterfacesCount; i++) {
				this.noProposal = false;
				this.requestor.acceptType(
					this.acceptedInterfaces[i][0],
					this.acceptedInterfaces[i][1],
					this.acceptedInterfacesModifiers[i],
					false,
					null,
					this.actualSelectionStart,
					this.actualSelectionEnd);
			}
			this.acceptedInterfaces = null;
			this.acceptedInterfacesModifiers = null;
			this.acceptedInterfacesCount = 0;
		}
		if(this.acceptedAnnotations != null){
			this.acceptedAnswer = true;
			for (int i = 0; i < this.acceptedAnnotationsCount; i++) {
				this.noProposal = false;
				this.requestor.acceptType(
					this.acceptedAnnotations[i][0],
					this.acceptedAnnotations[i][1],
					this.acceptedAnnotationsModifiers[i],
					false,
					null,
					this.actualSelectionStart,
					this.actualSelectionEnd);
			}
			this.acceptedAnnotations = null;
			this.acceptedAnnotationsModifiers = null;
			this.acceptedAnnotationsCount = 0;
		}
		if(this.acceptedEnums != null){
			this.acceptedAnswer = true;
			for (int i = 0; i < this.acceptedEnumsCount; i++) {
				this.noProposal = false;
				this.requestor.acceptType(
					this.acceptedEnums[i][0],
					this.acceptedEnums[i][1],
					this.acceptedEnumsModifiers[i],
					false,
					null,
					this.actualSelectionStart,
					this.actualSelectionEnd);
			}
			this.acceptedEnums = null;
			this.acceptedEnumsModifiers = null;
			this.acceptedEnumsCount = 0;
		}
	}
	private boolean checkSelection(
			char[] source,
			int selectionStart,
			int selectionEnd) {

		Scanner scanner =
			new Scanner(
				false /*comment*/,
				false /*whitespace*/,
				false /*nls*/,
				this.compilerOptions.sourceLevel,
				this.compilerOptions.complianceLevel,
				null/*taskTag*/,
				null/*taskPriorities*/,
				true /*taskCaseSensitive*/);
		scanner.setSource(source);
		
		int lastIdentifierStart = -1;
		int lastIdentifierEnd = -1;
		char[] lastIdentifier = null;
		int token;

		if(selectionStart > selectionEnd){
			int end = source.length - 1;

			// compute start position of current line
			int currentPosition = selectionStart - 1;
			int nextCharacterPosition = selectionStart;
			char currentCharacter = ' ';
			try {
				lineLoop: while(currentPosition > 0){

					if(source[currentPosition] == '\\' && source[currentPosition+1] == 'u') {
						int pos = currentPosition + 2;
						int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
						while (source[pos] == 'u') {
							pos++;
						}

						int endOfUnicode = pos + 3;
						if (end < endOfUnicode) {
							if (endOfUnicode < source.length) {
								end = endOfUnicode;
							} else {
								return false; // not enough characters to decode an unicode
							}
						}

						if ((c1 = ScannerHelper.getHexadecimalValue(source[pos++])) > 15
							|| c1 < 0
							|| (c2 = ScannerHelper.getHexadecimalValue(source[pos++])) > 15
							|| c2 < 0
							|| (c3 = ScannerHelper.getHexadecimalValue(source[pos++])) > 15
							|| c3 < 0
							|| (c4 = ScannerHelper.getHexadecimalValue(source[pos++])) > 15
							|| c4 < 0) {
							return false;
						} else {
							currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
							nextCharacterPosition = pos;
						}
					} else {
						currentCharacter = source[currentPosition];
						nextCharacterPosition = currentPosition+1;
					}

					switch(currentCharacter) {
						case '\r':
						case '\n':
						case '/':
						case '"':
						case '\'':
							break lineLoop;
						case '-':
							if (source[nextCharacterPosition] == '>') {
								nextCharacterPosition--; // nextCharacterPosition = currentPosition
								break lineLoop;
							}
							break;
						case ':':
							if (source[nextCharacterPosition] == ':') {
								nextCharacterPosition--; // nextCharacterPosition = currentPosition
								break lineLoop;
							}
					}
					currentPosition--;
				}
			} catch (ArrayIndexOutOfBoundsException e) {
				return false;
			}

			// compute start and end of the last token
			scanner.resetTo(nextCharacterPosition, end);
			isolateLastName: do {
				try {
					token = scanner.getNextToken();
				} catch (InvalidInputException e) {
					return false;
				}
				switch (token) {
					case TerminalTokens.TokenNamethis:
					case TerminalTokens.TokenNamesuper:
					case TerminalTokens.TokenNamenew:
					case TerminalTokens.TokenNameIdentifier:
						if (scanner.startPosition <= selectionStart && selectionStart <= scanner.currentPosition) {
							if (scanner.currentPosition == scanner.eofPosition) {
								int temp = scanner.eofPosition;
								scanner.eofPosition = scanner.source.length;
							 	while(scanner.getNextCharAsJavaIdentifierPart()){/*empty*/}
							 	scanner.eofPosition = temp;
							}
							lastIdentifierStart = scanner.startPosition;
							lastIdentifierEnd = scanner.currentPosition - 1;
							lastIdentifier = scanner.getCurrentTokenSource();
							break isolateLastName;
						}
						break;
				}
			} while (token != TerminalTokens.TokenNameEOF);
		} else {
			if (selectionStart == selectionEnd) { // Widen the selection to scan -> || :: if needed. No unicode handling for now.
				if (selectionStart > 0 && selectionEnd < source.length - 1) {
					if ((source[selectionStart] == '>' && source[selectionStart - 1] == '-') ||
							source[selectionStart] == ':' && source[selectionStart - 1] == ':') { 
						selectionStart--;
					} else {
						if ((source[selectionStart] == '-' && source[selectionEnd + 1] == '>') ||
								source[selectionStart] == ':' && source[selectionEnd + 1] == ':') {
							selectionEnd++;
						}
					}
				}  
			} // there could be some innocuous widening, shouldn't matter.
			scanner.resetTo(selectionStart, selectionEnd);

			boolean expectingIdentifier = true;
			do {
				try {
					token = scanner.getNextToken();
				} catch (InvalidInputException e) {
					return false;
				}
				switch (token) {
					case TerminalTokens.TokenNamethis :
					case TerminalTokens.TokenNamesuper :
					case TerminalTokens.TokenNamenew :
					case TerminalTokens.TokenNameIdentifier :
						if (!expectingIdentifier)
							return false;
						lastIdentifier = scanner.getCurrentTokenSource();
						lastIdentifierStart = scanner.startPosition;
						lastIdentifierEnd = scanner.currentPosition - 1;
						if(lastIdentifierEnd > selectionEnd) {
							lastIdentifierEnd = selectionEnd;
							lastIdentifier = CharOperation.subarray(lastIdentifier, 0,lastIdentifierEnd - lastIdentifierStart + 1);
						}
						expectingIdentifier = false;
						break;
					case TerminalTokens.TokenNameCOLON_COLON:	
						if (selectionStart >= scanner.startPosition && selectionEnd < scanner.currentPosition) {
							this.actualSelectionStart = selectionStart;
							this.actualSelectionEnd = selectionEnd;
							this.selectedIdentifier = CharOperation.NO_CHAR;
							return true;		
						}
						//$FALL-THROUGH$
					case TerminalTokens.TokenNameDOT :
						if (expectingIdentifier)
							return false;
						expectingIdentifier = true;
						break;
					case TerminalTokens.TokenNameEOF :
						if (expectingIdentifier)
							return false;
						break;
					case TerminalTokens.TokenNameLESS :
						if(!checkTypeArgument(scanner))
							return false;
						break;
					case TerminalTokens.TokenNameAT:
						if(scanner.startPosition != scanner.initialPosition)
							return false;
						break;
					case TerminalTokens.TokenNameARROW:
						if (selectionStart >= scanner.startPosition && selectionEnd < scanner.currentPosition) {
							this.actualSelectionStart = selectionStart;
							this.actualSelectionEnd = selectionEnd;
							this.selectedIdentifier = CharOperation.NO_CHAR;
							return true;		
						}
						return false;
					default :
						return false;
				}
			} while (token != TerminalTokens.TokenNameEOF);
		}
		if (lastIdentifierStart > 0) {
			this.actualSelectionStart = lastIdentifierStart;
			this.actualSelectionEnd = lastIdentifierEnd;
			this.selectedIdentifier = lastIdentifier;
			return true;
		}
		return false;
	}
	private boolean checkTypeArgument(Scanner scanner) {
		int depth = 1;
		int token;
		StringBuffer buffer = new StringBuffer();
		do {
			try {
				token = scanner.getNextToken();
			} catch (InvalidInputException e) {
				return false;
			}
			switch(token) {
				case TerminalTokens.TokenNameLESS :
					depth++;
					buffer.append(scanner.getCurrentTokenSource());
					break;
				case TerminalTokens.TokenNameGREATER :
					depth--;
					buffer.append(scanner.getCurrentTokenSource());
					break;
				case TerminalTokens.TokenNameRIGHT_SHIFT :
					depth-=2;
					buffer.append(scanner.getCurrentTokenSource());
					break;
				case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT :
					depth-=3;
					buffer.append(scanner.getCurrentTokenSource());
					break;
				case TerminalTokens.TokenNameextends :
				case TerminalTokens.TokenNamesuper :
					buffer.append(' ');
					buffer.append(scanner.getCurrentTokenSource());
					buffer.append(' ');
					break;
				case TerminalTokens.TokenNameCOMMA :
					if(depth == 1) {
						int length = buffer.length();
						char[] typeRef = new char[length];
						buffer.getChars(0, length, typeRef, 0);
						try {
							Signature.createTypeSignature(typeRef, true);
							buffer = new StringBuffer();
						} catch(IllegalArgumentException e) {
							return false;
						}
					}
					break;
				default :
					buffer.append(scanner.getCurrentTokenSource());
					break;

			}
			if(depth < 0) {
				return false;
			}
		} while (depth != 0 && token != TerminalTokens.TokenNameEOF);

		if(depth == 0) {
			int length = buffer.length() - 1;
			char[] typeRef = new char[length];
			buffer.getChars(0, length, typeRef, 0);
			try {
				Signature.createTypeSignature(typeRef, true);
				return true;
			} catch(IllegalArgumentException e) {
				return false;
			}
		}

		return false;
	}
	
	/*
	 * find all types outside the project scope
	 */
	private void findAllTypes(char[] prefix) {
		try {
			IProgressMonitor progressMonitor = new IProgressMonitor() {
				boolean isCanceled = false;
				public void beginTask(String name, int totalWork) {
					// implements interface method
				}
				public void done() {
					// implements interface method
				}
				public void internalWorked(double work) {
					// implements interface method
				}
				public boolean isCanceled() {
					return this.isCanceled;
				}
				public void setCanceled(boolean value) {
					this.isCanceled = value;
				}
				public void setTaskName(String name) {
					// implements interface method
				}
				public void subTask(String name) {
					// implements interface method
				}
				public void worked(int work) {
					// implements interface method
				}
			};
			
			TypeNameMatchRequestor typeNameMatchRequestor = new TypeNameMatchRequestor() {
				public void acceptTypeNameMatch(TypeNameMatch match) {
					if (SelectionEngine.this.requestor instanceof SelectionRequestor) {
						SelectionEngine.this.noProposal = false;
						((SelectionRequestor)SelectionEngine.this.requestor).acceptType(match.getType());
					}
				}
			};
			
			IJavaSearchScope scope = BasicSearchEngine.createWorkspaceScope();
			
			SelectionTypeNameMatchRequestorWrapper requestorWrapper =
				new SelectionTypeNameMatchRequestorWrapper(
						typeNameMatchRequestor, 
						scope,
						this.unitScope == null ? null : this.unitScope.referenceContext.imports);
			
			org.aspectj.org.eclipse.jdt.core.ICompilationUnit[] workingCopies = this.owner == null ? null : JavaModelManager.getJavaModelManager().getWorkingCopies(this.owner, true/*add primary WCs*/);
			
			try {
				new BasicSearchEngine(workingCopies).searchAllTypeNames(
					null,
					SearchPattern.R_EXACT_MATCH,
					prefix,
					SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE,
					IJavaSearchConstants.TYPE,
					scope,
					requestorWrapper,
					IJavaSearchConstants.CANCEL_IF_NOT_READY_TO_SEARCH,
					progressMonitor);
			} catch (OperationCanceledException e) {
				// do nothing
			}
			requestorWrapper.acceptNotImported();
		} catch (JavaModelException e) {
			// do nothing
		}
	}

	public AssistParser getParser() {
		return this.parser;
	}

	/*
	 * Returns whether the given binding is a local/anonymous reference binding, or if its declaring class is
	 * local.
	 */
	private boolean isLocal(ReferenceBinding binding) {
		if(binding instanceof ParameterizedTypeBinding) {
			return isLocal(((ParameterizedTypeBinding)binding).genericType());
		}
		if (!(binding instanceof SourceTypeBinding)) return false;
		if (binding instanceof LocalTypeBinding) return true;
		if (binding instanceof MemberTypeBinding) {
			return isLocal(((MemberTypeBinding)binding).enclosingType);
		}
		return false;
	}

	/**
	 * Ask the engine to compute the selection at the specified position
	 * of the given compilation unit.

	 *  @param sourceUnit org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit
	 *      the source of the current compilation unit.
	 *
	 *  @param selectionSourceStart int
	 *  @param selectionSourceEnd int
	 *      a range in the source where the selection is.
	 */
	public void select(
		ICompilationUnit sourceUnit,
		int selectionSourceStart,
		int selectionSourceEnd) {

		char[] source = sourceUnit.getContents();

		if(DEBUG) {
			System.out.print("SELECTION IN "); //$NON-NLS-1$
			System.out.print(sourceUnit.getFileName());
			System.out.print(" FROM "); //$NON-NLS-1$
			System.out.print(selectionSourceStart);
			System.out.print(" TO "); //$NON-NLS-1$
			System.out.println(selectionSourceEnd);
			System.out.println("SELECTION - Source :"); //$NON-NLS-1$
			System.out.println(source);
		}
		if (!checkSelection(source, selectionSourceStart, selectionSourceEnd)) {
			return;
		}
		if (DEBUG) {
			System.out.print("SELECTION - Checked : \""); //$NON-NLS-1$
			System.out.print(new String(source, this.actualSelectionStart, this.actualSelectionEnd-this.actualSelectionStart+1));
			System.out.println('"');
		}
		try {
			this.acceptedAnswer = false;
			CompilationResult result = new CompilationResult(sourceUnit, 1, 1, this.compilerOptions.maxProblemsPerUnit);
			CompilationUnitDeclaration parsedUnit =
				this.parser.dietParse(sourceUnit, result, this.actualSelectionStart, this.actualSelectionEnd);

			if (parsedUnit != null) {
				if(DEBUG) {
					System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$
					System.out.println(parsedUnit.toString());
				}

				// scan the package & import statements first
				if (parsedUnit.currentPackage instanceof SelectionOnPackageReference) {
					char[][] tokens =
						((SelectionOnPackageReference) parsedUnit.currentPackage).tokens;
					this.noProposal = false;
					this.requestor.acceptPackage(CharOperation.concatWith(tokens, '.'));
					return;
				}
				ImportReference[] imports = parsedUnit.imports;
				if (imports != null) {
					for (int i = 0, length = imports.length; i < length; i++) {
						ImportReference importReference = imports[i];
						if (importReference instanceof SelectionOnImportReference) {
							char[][] tokens = ((SelectionOnImportReference) importReference).tokens;
							this.noProposal = false;
							this.requestor.acceptPackage(CharOperation.concatWith(tokens, '.'));
							this.nameEnvironment.findTypes(CharOperation.concatWith(tokens, '.'), false, false, IJavaSearchConstants.TYPE, this);

							this.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
							if ((this.unitScope = parsedUnit.scope) != null) {
								int tokenCount = tokens.length;
								char[] lastToken = tokens[tokenCount - 1];
								char[][] qualifierTokens = CharOperation.subarray(tokens, 0, tokenCount - 1);

								if(qualifierTokens != null && qualifierTokens.length > 0) {
									Binding binding = this.unitScope.getTypeOrPackage(qualifierTokens);
									if(binding != null && binding instanceof ReferenceBinding) {
										ReferenceBinding ref = (ReferenceBinding) binding;
										selectMemberTypeFromImport(parsedUnit, lastToken, ref, importReference.isStatic());
										if(importReference.isStatic()) {
											selectStaticFieldFromStaticImport(parsedUnit, lastToken, ref);
											selectStaticMethodFromStaticImport(parsedUnit, lastToken, ref);
										}
									}
								}
							}

							// accept qualified types only if no unqualified type was accepted
							if(!this.acceptedAnswer) {
								acceptQualifiedTypes();
								if (!this.acceptedAnswer) {
									this.nameEnvironment.findTypes(this.selectedIdentifier, false, false, IJavaSearchConstants.TYPE, this);
									// try with simple type name
									if(!this.acceptedAnswer) {
										acceptQualifiedTypes();
									}
								}
							}
							if(this.noProposal && this.problem != null) {
								this.requestor.acceptError(this.problem);
							}
							return;
						}
					}
				}
				if (parsedUnit.types != null || parsedUnit.isPackageInfo()) {
					if(selectDeclaration(parsedUnit))
						return;
					this.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
					if ((this.unitScope = parsedUnit.scope)  != null) {
						try {
							this.lookupEnvironment.completeTypeBindings(parsedUnit, true);
							
							CompilationUnitDeclaration previousUnitBeingCompleted = this.lookupEnvironment.unitBeingCompleted;
							this.lookupEnvironment.unitBeingCompleted = parsedUnit;
							parsedUnit.scope.faultInTypes();
							this.lookupEnvironment.unitBeingCompleted = previousUnitBeingCompleted;
							ASTNode node = null;
							if (parsedUnit.types != null)
								node = parseBlockStatements(parsedUnit, selectionSourceStart);
							if(DEBUG) {
								System.out.println("SELECTION - AST :"); //$NON-NLS-1$
								System.out.println(parsedUnit.toString());
							}
							parsedUnit.resolve();
							if (node != null) {
								selectLocalDeclaration(node);
							}
						} catch (SelectionNodeFound e) {
							if (e.binding != null) {
								if(DEBUG) {
									System.out.println("SELECTION - Selection binding:"); //$NON-NLS-1$
									System.out.println(e.binding.toString());
								}
								// if null then we found a problem in the selection node
								selectFrom(e.binding, parsedUnit, sourceUnit, e.isDeclaration);
							}
						}
					}
				}
			}
			// only reaches here if no selection could be derived from the parsed tree
			// thus use the selected source and perform a textual type search
			if (!this.acceptedAnswer) {
				this.nameEnvironment.findTypes(this.selectedIdentifier, false, false, IJavaSearchConstants.TYPE, this);

				// accept qualified types only if no unqualified type was accepted
				if(!this.acceptedAnswer) {
					acceptQualifiedTypes();
					
					// accept types from all the workspace only if no type was found in the project scope
					if (this.noProposal) {
						findAllTypes(this.selectedIdentifier);
					}
				}
			}
			if(this.noProposal && this.problem != null) {
				this.requestor.acceptError(this.problem);
			}
		} catch (IndexOutOfBoundsException e) { // work-around internal failure - 1GEMF6D
			if(DEBUG) {
				System.out.println("Exception caught by SelectionEngine:"); //$NON-NLS-1$
				e.printStackTrace(System.out);
			}
		} catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
			if(DEBUG) {
				System.out.println("Exception caught by SelectionEngine:"); //$NON-NLS-1$
				e.printStackTrace(System.out);
			}
		} finally {
			reset(true);
		}
	}

	private void selectMemberTypeFromImport(CompilationUnitDeclaration parsedUnit, char[] lastToken, ReferenceBinding ref, boolean staticOnly) {
		int fieldLength = lastToken.length;
		ReferenceBinding[] memberTypes = ref.memberTypes();
		next : for (int j = 0; j < memberTypes.length; j++) {
			ReferenceBinding memberType = memberTypes[j];

			if (fieldLength > memberType.sourceName.length)
				continue next;

			if (staticOnly && !memberType.isStatic())
				continue next;

			if (!CharOperation.equals(lastToken, memberType.sourceName, true))
				continue next;

			selectFrom(memberType, parsedUnit, false);
		}
	}

	private void selectStaticFieldFromStaticImport(CompilationUnitDeclaration parsedUnit, char[] lastToken, ReferenceBinding ref) {
		int fieldLength = lastToken.length;
		FieldBinding[] fields = ref.availableFields();
		next : for (int j = 0; j < fields.length; j++) {
			FieldBinding field = fields[j];

			if (fieldLength > field.name.length)
				continue next;

			if (field.isSynthetic())
				continue next;

			if (!field.isStatic())
				continue next;

			if (!CharOperation.equals(lastToken, field.name, true))
				continue next;

			selectFrom(field, parsedUnit, false);
		}
	}

	private void selectStaticMethodFromStaticImport(CompilationUnitDeclaration parsedUnit, char[] lastToken, ReferenceBinding ref) {
		int methodLength = lastToken.length;
		MethodBinding[] methods = ref.availableMethods();
		next : for (int j = 0; j < methods.length; j++) {
			MethodBinding method = methods[j];

			if (method.isSynthetic()) continue next;

			if (method.isDefaultAbstract())	continue next;

			if (method.isConstructor()) continue next;

			if (!method.isStatic()) continue next;

			if (methodLength > method.selector.length)
				continue next;

			if (!CharOperation.equals(lastToken, method.selector, true))
				continue next;

			selectFrom(method, parsedUnit, false);
		}
	}

	private void selectFrom(Binding binding, CompilationUnitDeclaration parsedUnit, boolean isDeclaration) {
		selectFrom(binding, parsedUnit, null, isDeclaration);
	}
	private void selectFrom(Binding binding, CompilationUnitDeclaration parsedUnit, ICompilationUnit unit, boolean isDeclaration) {
		if(binding instanceof TypeVariableBinding) {
			TypeVariableBinding typeVariableBinding = (TypeVariableBinding) binding;
			Binding enclosingElement = typeVariableBinding.declaringElement;
			this.noProposal = false;

			if(enclosingElement instanceof SourceTypeBinding) {
				SourceTypeBinding enclosingType = (SourceTypeBinding) enclosingElement;
				if (isLocal(enclosingType) && this.requestor instanceof SelectionRequestor) {
					((SelectionRequestor)this.requestor).acceptLocalTypeParameter(typeVariableBinding);
				} else {
					this.requestor.acceptTypeParameter(
						enclosingType.qualifiedPackageName(),
						enclosingType.qualifiedSourceName(),
						typeVariableBinding.sourceName(),
						false,
						this.actualSelectionStart,
						this.actualSelectionEnd);
				}
			} else if(enclosingElement instanceof MethodBinding) {
				MethodBinding enclosingMethod = (MethodBinding) enclosingElement;
				if (isLocal(enclosingMethod.declaringClass) && this.requestor instanceof SelectionRequestor) {
					((SelectionRequestor)this.requestor).acceptLocalMethodTypeParameter(typeVariableBinding);
				} else {
					this.requestor.acceptMethodTypeParameter(
						enclosingMethod.declaringClass.qualifiedPackageName(),
						enclosingMethod.declaringClass.qualifiedSourceName(),
						enclosingMethod.isConstructor()
								? enclosingMethod.declaringClass.sourceName()
								: enclosingMethod.selector,
						enclosingMethod.sourceStart(),
						enclosingMethod.sourceEnd(),
						typeVariableBinding.sourceName(),
						false,
						this.actualSelectionStart,
						this.actualSelectionEnd);
				}
			}
			this.acceptedAnswer = true;
		} else if (binding instanceof ReferenceBinding) {
			ReferenceBinding typeBinding = (ReferenceBinding) binding;
			if(typeBinding instanceof ProblemReferenceBinding) {
				TypeBinding closestMatch = typeBinding.closestMatch();
				if (closestMatch instanceof ReferenceBinding) {
					typeBinding = (ReferenceBinding) closestMatch;
				} else {
					typeBinding = null;
				}
			}
			if (typeBinding == null) return;
			if (isLocal(typeBinding) && this.requestor instanceof SelectionRequestor) {
				this.noProposal = false;
				((SelectionRequestor)this.requestor).acceptLocalType(typeBinding);
			} else {
				this.noProposal = false;

				this.requestor.acceptType(
					typeBinding.qualifiedPackageName(),
					typeBinding.qualifiedSourceName(),
					typeBinding.modifiers,
					false,
					typeBinding.computeUniqueKey(),
					this.actualSelectionStart,
					this.actualSelectionEnd);
			}
			this.acceptedAnswer = true;
		} else if (binding instanceof MethodBinding) {
			MethodBinding methodBinding = getCorrectMethodBinding((MethodBinding) binding);
			this.noProposal = false;

			boolean isValuesOrValueOf = false;
			if(binding instanceof SyntheticMethodBinding) {
				SyntheticMethodBinding syntheticMethodBinding = (SyntheticMethodBinding) binding;
				if(syntheticMethodBinding.purpose  == SyntheticMethodBinding.EnumValues
						|| syntheticMethodBinding.purpose  == SyntheticMethodBinding.EnumValueOf) {
					isValuesOrValueOf =  true;
				}
			}

			if(!isValuesOrValueOf && !methodBinding.isSynthetic()) {
				TypeBinding[] parameterTypes = methodBinding.original().parameters;
				int length = parameterTypes.length;
				char[][] parameterPackageNames = new char[length][];
				char[][] parameterTypeNames = new char[length][];
				String[] parameterSignatures = new String[length];
				for (int i = 0; i < length; i++) {
					parameterPackageNames[i] = parameterTypes[i].qualifiedPackageName();
					parameterTypeNames[i] = parameterTypes[i].qualifiedSourceName();
					parameterSignatures[i] = new String(getSignature(parameterTypes[i])).replace('/', '.');
				}

				TypeVariableBinding[] typeVariables = methodBinding.original().typeVariables;
				length = typeVariables == null ? 0 : typeVariables.length;
				char[][] typeParameterNames = new char[length][];
				char[][][] typeParameterBoundNames = new char[length][][];
				for (int i = 0; i < length; i++) {
					TypeVariableBinding typeVariable = typeVariables[i];
					typeParameterNames[i] = typeVariable.sourceName;
					if (typeVariable.firstBound == null) {
						typeParameterBoundNames[i] = new char[0][];
					} else if (TypeBinding.equalsEquals(typeVariable.firstBound, typeVariable.superclass)) {
						int boundCount = 1 + (typeVariable.superInterfaces == null ? 0 : typeVariable.superInterfaces.length);
						typeParameterBoundNames[i] = new char[boundCount][];
						typeParameterBoundNames[i][0] = typeVariable.superclass.sourceName;
						for (int j = 1; j < boundCount; j++) {
							typeParameterBoundNames[i][j] = typeVariables[i].superInterfaces[j - 1].sourceName;
						}
					} else {
						int boundCount = typeVariable.superInterfaces == null ? 0 : typeVariable.superInterfaces.length;
						typeParameterBoundNames[i] = new char[boundCount][];
						for (int j = 0; j < boundCount; j++) {
							typeParameterBoundNames[i][j] = typeVariables[i].superInterfaces[j].sourceName;
						}
					}
				}

				ReferenceBinding declaringClass = methodBinding.declaringClass;
				if (isLocal(declaringClass) && this.requestor instanceof SelectionRequestor) {
					((SelectionRequestor)this.requestor).acceptLocalMethod(methodBinding);
				} else {
					this.requestor.acceptMethod(
						declaringClass.qualifiedPackageName(),
						declaringClass.qualifiedSourceName(),
						declaringClass.enclosingType() == null ? null : new String(getSignature(declaringClass.enclosingType())),
						methodBinding.isConstructor()
							? declaringClass.sourceName()
							: methodBinding.selector,
						parameterPackageNames,
						parameterTypeNames,
						parameterSignatures,
						typeParameterNames,
						typeParameterBoundNames,
						methodBinding.isConstructor(),
						isDeclaration,
						methodBinding.computeUniqueKey(),
						this.actualSelectionStart,
						this.actualSelectionEnd);
				}
			}
			this.acceptedAnswer = true;
		} else if (binding instanceof FieldBinding) {
			FieldBinding fieldBinding = (FieldBinding) binding;
			ReferenceBinding declaringClass = fieldBinding.declaringClass;
			if (declaringClass != null) { // arraylength
				this.noProposal = false;
				if (isLocal(declaringClass) && this.requestor instanceof SelectionRequestor) {
					((SelectionRequestor)this.requestor).acceptLocalField(fieldBinding);
				} else {
					// if the binding is a problem field binding, we want to make sure
					// we can retrieve the closestMatch if the problem reason is NotVisible
					FieldBinding currentFieldBinding = fieldBinding;
					while (currentFieldBinding instanceof ProblemFieldBinding) {
						ProblemFieldBinding problemFieldBinding = (ProblemFieldBinding) currentFieldBinding;
						if (problemFieldBinding.problemId() == ProblemReasons.NotVisible) {
							currentFieldBinding = problemFieldBinding.closestMatch;
						} else {
							currentFieldBinding = null;
						}
					}
					char[] fieldName = null;
					char[] key = null;
					if (currentFieldBinding != null) {
						fieldName = currentFieldBinding.name;
						key = currentFieldBinding.computeUniqueKey();
					} else {
						fieldName = fieldBinding.name;
						key = fieldBinding.computeUniqueKey();
					}
					this.requestor.acceptField(
						declaringClass.qualifiedPackageName(),
						declaringClass.qualifiedSourceName(),
						fieldName,
						false,
						key,
						this.actualSelectionStart,
						this.actualSelectionEnd);
				}
				this.acceptedAnswer = true;
			}
		} else if (binding instanceof LocalVariableBinding) {
			if (this.requestor instanceof SelectionRequestor) {
				((SelectionRequestor)this.requestor).acceptLocalVariable((LocalVariableBinding)binding, unit);
				this.acceptedAnswer = true;
			} else {
				// open on the type of the variable
				selectFrom(((LocalVariableBinding) binding).type, parsedUnit, false);
			}
		} else if (binding instanceof ArrayBinding) {
			selectFrom(((ArrayBinding) binding).leafComponentType, parsedUnit, false);
			// open on the type of the array
		} else if (binding instanceof PackageBinding) {
			PackageBinding packageBinding = (PackageBinding) binding;
			this.noProposal = false;
			this.requestor.acceptPackage(packageBinding.readableName());
			this.acceptedAnswer = true;
		} else if(binding instanceof BaseTypeBinding) {
			this.acceptedAnswer = true;
		}
	}
	/*
	 * Checks if a local declaration got selected in this method/initializer/field.
	 */
	private void selectLocalDeclaration(ASTNode node) {
		// the selected identifier is not identical to the parser one (equals but not identical),
		// for traversing the parse tree, the parser assist identifier is necessary for identitiy checks
		final char[] assistIdentifier = getParser().assistIdentifier();
		if (assistIdentifier == null) return;

		class Visitor extends ASTVisitor {
			public boolean visit(ConstructorDeclaration constructorDeclaration, ClassScope scope) {
				if (constructorDeclaration.selector == assistIdentifier){
					if (constructorDeclaration.binding != null) {
						throw new SelectionNodeFound(constructorDeclaration.binding);
					} else {
						if (constructorDeclaration.scope != null) {
							throw new SelectionNodeFound(new MethodBinding(constructorDeclaration.modifiers, constructorDeclaration.selector, null, null, null, constructorDeclaration.scope.referenceType().binding));
						}
					}
				}
				return true;
			}
			public boolean visit(FieldDeclaration fieldDeclaration, MethodScope scope) {
				if (fieldDeclaration.name == assistIdentifier){
					throw new SelectionNodeFound(fieldDeclaration.binding);
				}
				return true;
			}
			public boolean visit(TypeDeclaration localTypeDeclaration, BlockScope scope) {
				if (localTypeDeclaration.name == assistIdentifier) {
					throw new SelectionNodeFound(localTypeDeclaration.binding);
				}
				return true;
			}
			public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
				if (memberTypeDeclaration.name == assistIdentifier) {
					throw new SelectionNodeFound(memberTypeDeclaration.binding);
				}
				return true;
			}
			public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) {
				if (methodDeclaration.selector == assistIdentifier){
					if (methodDeclaration.binding != null) {
						throw new SelectionNodeFound(methodDeclaration.binding);
					} else {
						if (methodDeclaration.scope != null) {
							throw new SelectionNodeFound(new MethodBinding(methodDeclaration.modifiers, methodDeclaration.selector, null, null, null, methodDeclaration.scope.referenceType().binding));
						}
					}
				}
				return true;
			}
			public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
				if (typeDeclaration.name == assistIdentifier) {
					throw new SelectionNodeFound(typeDeclaration.binding);
				}
				return true;
			}
			public boolean visit(TypeParameter typeParameter, BlockScope scope) {
				if (typeParameter.name == assistIdentifier) {
					throw new SelectionNodeFound(typeParameter.binding);
				}
				return true;
			}
			public boolean visit(TypeParameter typeParameter, ClassScope scope) {
				if (typeParameter.name == assistIdentifier) {
					throw new SelectionNodeFound(typeParameter.binding);
				}
				return true;
			}
		}

		if (node instanceof AbstractMethodDeclaration) {
			((AbstractMethodDeclaration)node).traverse(new Visitor(), (ClassScope)null);
		} else {
			((FieldDeclaration)node).traverse(new Visitor(), (MethodScope)null);
		}
	}

	/**
	 * Asks the engine to compute the selection of the given type
	 * from the given context
	 *
	 *  @param typeName char[]
	 *      a type name which is to be resolved in the context of a compilation unit.
	 *		NOTE: the type name is supposed to be correctly reduced (no whitespaces, no unicodes left)
	 *
	 *  @param context org.aspectj.org.eclipse.jdt.core.IType
	 *      the context in which code assist is invoked.
	 */
	public void selectType(char[] typeName, IType context) throws JavaModelException {
		try {
			this.acceptedAnswer = false;

			// only the type erasure are returned by IType.resolvedType(...)
			if (CharOperation.indexOf('<', typeName) != -1) {
				char[] typeSig = Signature.createCharArrayTypeSignature(typeName, false/*not resolved*/);
				typeSig = Signature.getTypeErasure(typeSig);
				typeName = Signature.toCharArray(typeSig);
			}

			CompilationUnitDeclaration parsedUnit = null;
			TypeDeclaration typeDeclaration = null;
			org.aspectj.org.eclipse.jdt.core.ICompilationUnit cu = context.getCompilationUnit();
			if (cu != null) {
			 	IType[] topLevelTypes = cu.getTypes();
			 	int length = topLevelTypes.length;
			 	SourceTypeElementInfo[] topLevelInfos = new SourceTypeElementInfo[length];
			 	for (int i = 0; i < length; i++) {
					topLevelInfos[i] = (SourceTypeElementInfo) ((SourceType)topLevelTypes[i]).getElementInfo();
				}
				CompilationResult result = new CompilationResult((org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit) cu, 1, 1, this.compilerOptions.maxProblemsPerUnit);
				int flags = SourceTypeConverter.FIELD_AND_METHOD | SourceTypeConverter.MEMBER_TYPE;
				if (context.isAnonymous() || context.isLocal())
					flags |= SourceTypeConverter.LOCAL_TYPE;
				parsedUnit =
					SourceTypeConverter.buildCompilationUnit(
							topLevelInfos,
							flags,
							this.parser.problemReporter(),
							result);
				if (parsedUnit != null && parsedUnit.types != null) {
					if(DEBUG) {
						System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$
						System.out.println(parsedUnit.toString());
					}
					// find the type declaration that corresponds to the original source type
					typeDeclaration = new ASTNodeFinder(parsedUnit).findType(context);
				}
			} else { // binary type
				ClassFile classFile = (ClassFile) context.getClassFile();
				ClassFileReader reader = (ClassFileReader) classFile.getBinaryTypeInfo((IFile) classFile.resource(), false/*don't fully initialize so as to keep constant pool (used below)*/);
				CompilationResult result = new CompilationResult(reader.getFileName(), 1, 1, this.compilerOptions.maxProblemsPerUnit);
				parsedUnit = new CompilationUnitDeclaration(this.parser.problemReporter(), result, 0);
				HashSetOfCharArrayArray typeNames = new HashSetOfCharArrayArray();

				BinaryTypeConverter converter = new BinaryTypeConverter(this.parser.problemReporter(), result, typeNames);
				typeDeclaration = converter.buildTypeDeclaration(context, parsedUnit);
				parsedUnit.imports = converter.buildImports(reader);
			}

			if (typeDeclaration != null) {

				// add fake field with the type we're looking for
				// note: since we didn't ask for fields above, there is no field defined yet
				FieldDeclaration field = new FieldDeclaration();
				int dot;
				if ((dot = CharOperation.lastIndexOf('.', typeName)) == -1) {
					this.selectedIdentifier = typeName;
					field.type = new SelectionOnSingleTypeReference(typeName, -1);
					// position not used
				} else {
					char[][] previousIdentifiers = CharOperation.splitOn('.', typeName, 0, dot);
					char[] selectionIdentifier =
						CharOperation.subarray(typeName, dot + 1, typeName.length);
					this.selectedIdentifier = selectionIdentifier;
					field.type =
						new SelectionOnQualifiedTypeReference(
							previousIdentifiers,
							selectionIdentifier,
							new long[previousIdentifiers.length + 1]);
				}
				field.name = "<fakeField>".toCharArray(); //$NON-NLS-1$
				typeDeclaration.fields = new FieldDeclaration[] { field };

				// build bindings
				this.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
				if ((this.unitScope = parsedUnit.scope) != null) {
					try {
						// build fields
						// note: this builds fields only in the parsed unit (the buildFieldsAndMethods flag is not passed along)
						this.lookupEnvironment.completeTypeBindings(parsedUnit, true);

						// resolve
						parsedUnit.scope.faultInTypes();
						parsedUnit.resolve();
					} catch (SelectionNodeFound e) {
						if (e.binding != null) {
							if(DEBUG) {
								System.out.println("SELECTION - Selection binding :"); //$NON-NLS-1$
								System.out.println(e.binding.toString());
							}
							// if null then we found a problem in the selection node
							selectFrom(e.binding, parsedUnit, e.isDeclaration);
						}
					}
				}
			}
			if(this.noProposal && this.problem != null) {
				this.requestor.acceptError(this.problem);
			}
		} catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
		} finally {
			reset(true);
		}
	}

	// Check if a declaration got selected in this unit
	private boolean selectDeclaration(CompilationUnitDeclaration compilationUnit){

		// the selected identifier is not identical to the parser one (equals but not identical),
		// for traversing the parse tree, the parser assist identifier is necessary for identitiy checks
		char[] assistIdentifier = getParser().assistIdentifier();
		if (assistIdentifier == null) return false;

		ImportReference currentPackage = compilationUnit.currentPackage;
		char[] packageName = currentPackage == null ? CharOperation.NO_CHAR : CharOperation.concatWith(currentPackage.tokens, '.');
		// iterate over the types
		TypeDeclaration[] types = compilationUnit.types;
		for (int i = 0, length = types == null ? 0 : types.length; i < length; i++){
			if(selectDeclaration(types[i], assistIdentifier, packageName))
				return true;
		}
		return false;
	}

	// Check if a declaration got selected in this type
	private boolean selectDeclaration(TypeDeclaration typeDeclaration, char[] assistIdentifier, char[] packageName){

		if (typeDeclaration.name == assistIdentifier){
			char[] qualifiedSourceName = null;

			TypeDeclaration enclosingType = typeDeclaration;
			while(enclosingType != null) {
				qualifiedSourceName = CharOperation.concat(enclosingType.name, qualifiedSourceName, '.');
				enclosingType = enclosingType.enclosingType;
			}
			char[] uniqueKey = typeDeclaration.binding != null ? typeDeclaration.binding.computeUniqueKey() : null;

			this.requestor.acceptType(
				packageName,
				qualifiedSourceName,
				typeDeclaration.modifiers,
				true,
				uniqueKey,
				this.actualSelectionStart,
				this.actualSelectionEnd);

			this.noProposal = false;
			return true;
		}
		TypeDeclaration[] memberTypes = typeDeclaration.memberTypes;
		for (int i = 0, length = memberTypes == null ? 0 : memberTypes.length; i < length; i++){
			if(selectDeclaration(memberTypes[i], assistIdentifier, packageName))
				return true;
		}
		FieldDeclaration[] fields = typeDeclaration.fields;
		for (int i = 0, length = fields == null ? 0 : fields.length; i < length; i++){
			if (fields[i].name == assistIdentifier){
				char[] qualifiedSourceName = null;

				TypeDeclaration enclosingType = typeDeclaration;
				while(enclosingType != null) {
					qualifiedSourceName = CharOperation.concat(enclosingType.name, qualifiedSourceName, '.');
					enclosingType = enclosingType.enclosingType;
				}
				FieldDeclaration field = fields[i];
				this.requestor.acceptField(
					packageName,
					qualifiedSourceName,
					field.name,
					true,
					field.binding != null ? field.binding.computeUniqueKey() : null,
					this.actualSelectionStart,
					this.actualSelectionEnd);

				this.noProposal = false;
				return true;
			}
		}
		AbstractMethodDeclaration[] methods = typeDeclaration.methods;
		for (int i = 0, length = methods == null ? 0 : methods.length; i < length; i++){
			AbstractMethodDeclaration method = methods[i];

			if (method.selector == assistIdentifier){
				char[] qualifiedSourceName = null;

				TypeDeclaration enclosingType = typeDeclaration;
				while(enclosingType != null) {
					qualifiedSourceName = CharOperation.concat(enclosingType.name, qualifiedSourceName, '.');
					enclosingType = enclosingType.enclosingType;
				}

				this.requestor.acceptMethod(
					packageName,
					qualifiedSourceName,
					null, // SelectionRequestor does not need of declaring type signature for method declaration
					method.selector,
					null, // SelectionRequestor does not need of parameters type for method declaration
					null, // SelectionRequestor does not need of parameters type for method declaration
					null, // SelectionRequestor does not need of parameters type for method declaration
					null, // SelectionRequestor does not need of type parameters name for method declaration
					null, // SelectionRequestor does not need of type parameters bounds for method declaration
					method.isConstructor(),
					true,
					method.binding != null ? method.binding.computeUniqueKey() : null,
					this.actualSelectionStart,
					this.actualSelectionEnd);

				this.noProposal = false;
				return true;
			}

			TypeParameter[] methodTypeParameters = method.typeParameters();
			for (int j = 0, length2 = methodTypeParameters == null ? 0 : methodTypeParameters.length; j < length2; j++){
				TypeParameter methodTypeParameter = methodTypeParameters[j];

				if(methodTypeParameter.name == assistIdentifier) {
					char[] qualifiedSourceName = null;

					TypeDeclaration enclosingType = typeDeclaration;
					while(enclosingType != null) {
						qualifiedSourceName = CharOperation.concat(enclosingType.name, qualifiedSourceName, '.');
						enclosingType = enclosingType.enclosingType;
					}

					this.requestor.acceptMethodTypeParameter(
						packageName,
						qualifiedSourceName,
						method.selector,
						method.sourceStart,
						method.sourceEnd,
						methodTypeParameter.name,
						true,
						this.actualSelectionStart,
						this.actualSelectionEnd);

					this.noProposal = false;
					return true;
				}
			}
		}

		TypeParameter[] typeParameters = typeDeclaration.typeParameters;
		for (int i = 0, length = typeParameters == null ? 0 : typeParameters.length; i < length; i++){
			TypeParameter typeParameter = typeParameters[i];
			if(typeParameter.name == assistIdentifier) {
				char[] qualifiedSourceName = null;

				TypeDeclaration enclosingType = typeDeclaration;
				while(enclosingType != null) {
					qualifiedSourceName = CharOperation.concat(enclosingType.name, qualifiedSourceName, '.');
					enclosingType = enclosingType.enclosingType;
				}

				this.requestor.acceptTypeParameter(
					packageName,
					qualifiedSourceName,
					typeParameter.name,
					true,
					this.actualSelectionStart,
					this.actualSelectionEnd);

				this.noProposal = false;
				return true;
			}
		}

		return false;
	}
	
	/*
	 * Returns the correct method binding according to whether the selection is on the method declaration
	 * or on the inheritDoc tag in its javadoc.
	 */
	private MethodBinding getCorrectMethodBinding(MethodBinding binding) {
		if (this.parser.javadocParser instanceof SelectionJavadocParser) {
			if (((SelectionJavadocParser)this.parser.javadocParser).inheritDocTagSelected){
				try {
					Object res = findMethodWithAttachedDocInHierarchy(binding);
					if (res instanceof MethodBinding) {
						return (MethodBinding) res;
					}
				} catch (JavaModelException e) {
					return null;
				}
			}
		}
		return binding;
	}
	
	protected MethodBinding findOverriddenMethodInType(ReferenceBinding overriddenType, MethodBinding overriding) throws JavaModelException {
		if (overriddenType == null)
			return null;
		MethodBinding[] overriddenMethods= overriddenType.availableMethods();
		LookupEnvironment lookupEnv = this.lookupEnvironment;
		if (lookupEnv != null && overriddenMethods != null) {
			for (int i= 0; i < overriddenMethods.length; i++) {
				if (lookupEnv.methodVerifier().isMethodSubsignature(overriding, overriddenMethods[i])) {
					return overriddenMethods[i];
				}
			}
		}
		return null;
	}
	
	private Object findMethodWithAttachedDocInHierarchy(final MethodBinding method) throws JavaModelException {
		ReferenceBinding type= method.declaringClass;
		final SelectionRequestor requestor1 = (SelectionRequestor) this.requestor;
		return new InheritDocVisitor() {
			public Object visit(ReferenceBinding currType) throws JavaModelException {
				MethodBinding overridden =  findOverriddenMethodInType(currType, method);
				if (overridden == null)
					return InheritDocVisitor.CONTINUE;
				TypeBinding args[] = overridden.parameters;
				String names[] = new String[args.length];
				for (int i = 0; i < args.length; i++) {
					names[i] = Signature.createTypeSignature(args[i].sourceName(), false);
				}
				IMember member = (IMember) requestor1.findMethodFromBinding(overridden, names, overridden.declaringClass);
				if (member == null)
					return InheritDocVisitor.CONTINUE;
				if (member.getAttachedJavadoc(null) != null ) {  
					// for binary methods with attached javadoc and no source attached
					return overridden;
				}
				IOpenable openable = member.getOpenable();
				if (openable == null)
					return InheritDocVisitor.CONTINUE;
				IBuffer buf= openable.getBuffer();
				if (buf == null) {
					// no source attachment found. This method maybe the one. Stop.
					return InheritDocVisitor.STOP_BRANCH;
				}

				ISourceRange javadocRange= member.getJavadocRange();
				if (javadocRange == null)
					return InheritDocVisitor.CONTINUE;	// this method doesn't have javadoc, continue to look.
				String rawJavadoc= buf.getText(javadocRange.getOffset(), javadocRange.getLength());
				if (rawJavadoc != null) {
					return overridden;
				}
				return InheritDocVisitor.CONTINUE;
			}
		}.visitInheritDoc(type);
	}
	
	/**
	 * Implements the "Algorithm for Inheriting Method Comments" as specified for
	 * <a href="http://download.oracle.com/javase/6/docs/technotes/tools/windows/javadoc.html#inheritingcomments">1.6</a>.
	 *
	 * <p>
	 * Unfortunately, the implementation is broken in Javadoc implementations since 1.5, see
	 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6376959">Sun's bug</a>.
	 * </p>
	 *
	 * <p>
	 * We adhere to the spec.
	 * </p>
	 */
	static abstract class InheritDocVisitor {
		public static final Object STOP_BRANCH= new Object() {
			public String toString() { return "STOP_BRANCH"; } //$NON-NLS-1$
		};
		public static final Object CONTINUE= new Object() {
			public String toString() { return "CONTINUE"; } //$NON-NLS-1$
		};

		/**
		 * Visits a type and decides how the visitor should proceed.
		 *
		 * @param currType the current type
		 * @return <ul>
		 *         <li>{@link #STOP_BRANCH} to indicate that no Javadoc has been found and visiting
		 *         super types should stop here</li>
		 *         <li>{@link #CONTINUE} to indicate that no Javadoc has been found and visiting
		 *         super types should continue</li>
		 *         <li>an {@link Object} or <code>null</code>, to indicate that visiting should be
		 *         cancelled immediately. The returned value is the result of
		 *         {@link #visitInheritDoc(ReferenceBinding)}</li>
		 *         </ul>
		 * @throws JavaModelException unexpected problem
		 * @see #visitInheritDoc(ReferenceBinding)
		 */
		public abstract Object visit(ReferenceBinding currType) throws JavaModelException;

		/**
		 * Visits the super types of the given <code>currentType</code>.
		 *
		 * @param currentType the starting type
		 * @return the result from a call to {@link #visit(ReferenceBinding)}, or <code>null</code> if none of
		 *         the calls returned a result
		 * @throws JavaModelException unexpected problem
		 */
		public Object visitInheritDoc(ReferenceBinding currentType) throws JavaModelException {
			ArrayList visited= new ArrayList();
			visited.add(currentType);
			Object result= visitInheritDocInterfaces(visited, currentType);
			if (result != InheritDocVisitor.CONTINUE)
				return result;

			ReferenceBinding superClass= currentType.superclass();

			while (superClass != null && ! visited.contains(superClass)) {
				result= visit(superClass);
				if (result == InheritDocVisitor.STOP_BRANCH) {
					return null;
				} else if (result == InheritDocVisitor.CONTINUE) {
					visited.add(superClass);
					result= visitInheritDocInterfaces(visited, superClass);
					if (result != InheritDocVisitor.CONTINUE)
						return result;
					else
						superClass= superClass.superclass();
				} else {
					return result;
				}
			}

			return null;
		}

		/**
		 * Visits the super interfaces of the given type in the given hierarchy, thereby skipping already visited types.
		 * 
		 * @param visited set of visited types
		 * @param currentType type whose super interfaces should be visited
		 * @return the result, or {@link #CONTINUE} if no result has been found
		 * @throws JavaModelException unexpected problem
		 */
		private Object visitInheritDocInterfaces(ArrayList visited, ReferenceBinding currentType) throws JavaModelException {
			ArrayList toVisitChildren= new ArrayList();
			ReferenceBinding[] superInterfaces= currentType.superInterfaces();
			for (int i= 0; i < superInterfaces.length; i++) {
				ReferenceBinding superInterface= superInterfaces[i];
				if (visited.contains(superInterface))
					continue;
				visited.add(superInterface);
				Object result= visit(superInterface);
				if (result == InheritDocVisitor.STOP_BRANCH) {
					//skip
				} else if (result == InheritDocVisitor.CONTINUE) {
					toVisitChildren.add(superInterface);
				} else {
					return result;
				}
			}
			for (Iterator iter= toVisitChildren.iterator(); iter.hasNext(); ) {
				ReferenceBinding child= (ReferenceBinding) iter.next();
				Object result= visitInheritDocInterfaces(visited, child);
				if (result != InheritDocVisitor.CONTINUE)
					return result;
			}
			return InheritDocVisitor.CONTINUE;
		}
	}
}
