/*******************************************************************************
 * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v0.5 
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
package net.sourceforge.phpdt.internal.codeassist;

import java.util.Locale;
import java.util.Map;

import net.sourceforge.phpdt.core.compiler.IProblem;
import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
import net.sourceforge.phpdt.core.compiler.InvalidInputException;
import net.sourceforge.phpdt.internal.codeassist.impl.AssistParser;
import net.sourceforge.phpdt.internal.codeassist.impl.Engine;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionNodeFound;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnImportReference;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnPackageReference;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnQualifiedTypeReference;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnSingleTypeReference;
import net.sourceforge.phpdt.internal.codeassist.select.SelectionParser;
import net.sourceforge.phpdt.internal.compiler.CompilationResult;
import net.sourceforge.phpdt.internal.compiler.DefaultErrorHandlingPolicies;
import net.sourceforge.phpdt.internal.compiler.ast.AbstractMethodDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.CompilationUnitDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.FieldDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.ImportReference;
import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration;
import net.sourceforge.phpdt.internal.compiler.env.ICompilationUnit;
import net.sourceforge.phpdt.internal.compiler.env.ISourceType;
import net.sourceforge.phpdt.internal.compiler.impl.ReferenceContext;
import net.sourceforge.phpdt.internal.compiler.lookup.ArrayBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.BaseTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.Binding;
import net.sourceforge.phpdt.internal.compiler.lookup.FieldBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.LookupEnvironment;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.PackageBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.ProblemReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
import net.sourceforge.phpdt.internal.compiler.parser.SourceTypeConverter;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;
import net.sourceforge.phpdt.internal.compiler.problem.DefaultProblemFactory;
import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter;
import net.sourceforge.phpdt.internal.compiler.util.CharOperation;

/**
 * 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.
 */
public final class SelectionEngine extends Engine implements ISearchRequestor {

	public static boolean DEBUG = false;
	
	SelectionParser parser;
	ISelectionRequestor requestor;

	boolean acceptedAnswer;

	private int actualSelectionStart;
	private int actualSelectionEnd;
	private char[] qualifiedSelection;
	private char[] selectedIdentifier;
	
	private char[][][] acceptedClasses;
	private char[][][] acceptedInterfaces;
	int acceptedClassesCount;
	int acceptedInterfacesCount;

	/**
	 * 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 net.sourceforge.phpdt.internal.codeassist.ISearchableNameEnvironment
	 *      used to resolve type/package references and search for types/packages
	 *      based on partial names.
	 *
	 *  @param requestor net.sourceforge.phpdt.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(
		ISearchableNameEnvironment nameEnvironment,
		ISelectionRequestor requestor,
		Map settings) {

		super(settings);

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

		ProblemReporter problemReporter =
			new ProblemReporter(
				DefaultErrorHandlingPolicies.proceedWithAllProblems(),
				this.compilerOptions,
				new DefaultProblemFactory(Locale.getDefault())) {
			public void record(IProblem problem, CompilationResult unitResult, ReferenceContext referenceContext) {
				unitResult.record(problem, referenceContext);
				SelectionEngine.this.requestor.acceptError(problem);
			}
		};
		this.parser = new SelectionParser(problemReporter, this.compilerOptions.assertMode);
		this.lookupEnvironment =
			new LookupEnvironment(this, this.compilerOptions, problemReporter, nameEnvironment);
	}

	/**
	 * One result of the search consists of a new class.
	 * @param packageName char[]
	 * @param className char[]
	 * @param modifiers int
	 *
	 * NOTE - All package and type names are presented in their readable form:
	 *    Package names are in the form "a.b.c".
	 *    Nested type names are in the qualified form "A.M".
	 *    The default package is represented by an empty array.
	 */
	public void acceptClass(char[] packageName, char[] className, int modifiers) {
		if (CharOperation.equals(className, selectedIdentifier)) {
			if (qualifiedSelection != null
				&& !CharOperation.equals(
					qualifiedSelection,
					CharOperation.concat(packageName, className, '.'))) {
				return;
			}
			
			if(mustQualifyType(packageName, className)) {
				char[][] acceptedClass = new char[2][];
				acceptedClass[0] = packageName;
				acceptedClass[1] = className;
				
				if(acceptedClasses == null) {
					acceptedClasses = new char[10][][];
					acceptedClassesCount = 0;
				}
				int length = acceptedClasses.length;
				if(length == acceptedClassesCount) {
					System.arraycopy(acceptedClasses, 0, acceptedClasses = new char[(length + 1)* 2][][], 0, length);
				}
				acceptedClasses[acceptedClassesCount++] = acceptedClass;
				
			} else {
				requestor.acceptClass(
					packageName,
					className,
					false);
				acceptedAnswer = true;
			}
		}
	}

	/**
	 * One result of the search consists of a new interface.
	 *
	 * NOTE - All package and type names are presented in their readable form:
	 *    Package names are in the form "a.b.c".
	 *    Nested type names are in the qualified form "A.I".
	 *    The default package is represented by an empty array.
	 */
	public void acceptInterface(
		char[] packageName,
		char[] interfaceName,
		int modifiers) {

		if (CharOperation.equals(interfaceName, selectedIdentifier)) {
			if (qualifiedSelection != null
				&& !CharOperation.equals(
					qualifiedSelection,
					CharOperation.concat(packageName, interfaceName, '.'))) {
				return;
			}
			
			if(mustQualifyType(packageName, interfaceName)) {
				char[][] acceptedInterface= new char[2][];
				acceptedInterface[0] = packageName;
				acceptedInterface[1] = interfaceName;
				
				if(acceptedInterfaces == null) {
					acceptedInterfaces = new char[10][][];
					acceptedInterfacesCount = 0;
				}
				int length = acceptedInterfaces.length;
				if(length == acceptedInterfacesCount) {
					System.arraycopy(acceptedInterfaces, 0, acceptedInterfaces = new char[(length + 1) * 2][][], 0, length);
				}
				acceptedInterfaces[acceptedInterfacesCount++] = acceptedInterface;
				
			} else {
				requestor.acceptInterface(
					packageName,
					interfaceName,
					false);
				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) {
	}

	private void acceptQualifiedTypes() {
		if(acceptedClasses != null){
			acceptedAnswer = true;
			for (int i = 0; i < acceptedClassesCount; i++) {
				requestor.acceptClass(
					acceptedClasses[i][0],
					acceptedClasses[i][1],
					true);
			}
			acceptedClasses = null;
			acceptedClassesCount = 0;
		}
		if(acceptedInterfaces != null){
			acceptedAnswer = true;
			for (int i = 0; i < acceptedInterfacesCount; i++) {
				requestor.acceptInterface(
					acceptedInterfaces[i][0],
					acceptedInterfaces[i][1],
					true);
			}
			acceptedInterfaces = null;
			acceptedInterfacesCount = 0;
		}
	}
	
	/**
	 * One result of the search consists of a new type.
	 * @param packageName char[]
	 * @param typeName char[]
	 * 
	 * NOTE - All package and type names are presented in their readable form:
	 *    Package names are in the form "a.b.c".
	 *    Nested type names are in the qualified form "A.M".
	 *    The default package is represented by an empty array.
	 */
	public void acceptType(char[] packageName, char[] typeName) {
		acceptClass(packageName, typeName, 0);
	}

	private boolean checkSelection(
		char[] source,
		int selectionStart,
		int selectionEnd) {

		Scanner scanner = new Scanner();
		scanner.setSource(source);
		
		int lastIdentifierStart = -1;
		int lastIdentifierEnd = -1;
		char[] lastIdentifier = null;
		int token, identCount = 0;
		StringBuffer entireSelection = new StringBuffer(selectionEnd - selectionStart + 1);
		
		if(selectionStart > selectionEnd){
			
			// compute start position of current line
			int currentPosition = selectionStart - 1;
			int nextCharacterPosition = selectionStart;
			char currentCharacter = ' ';
			try {
				while(currentPosition > 0 || currentCharacter == '\r' || currentCharacter == '\n'){
					
					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++;
						}
						if ((c1 = Character.getNumericValue(source[pos++])) > 15
							|| c1 < 0
							|| (c2 = Character.getNumericValue(source[pos++])) > 15
							|| c2 < 0
							|| (c3 = Character.getNumericValue(source[pos++])) > 15
							|| c3 < 0
							|| (c4 = Character.getNumericValue(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;
					}
					
					if(currentCharacter == '\r' || currentCharacter == '\n') {
						break;
					}
					currentPosition--;
				}
			}
			catch (ArrayIndexOutOfBoundsException e) {
				return false;
			}
			
			// compute start and end of the last token
			scanner.resetTo(nextCharacterPosition, selectionEnd);
			do {
				try {
					token = scanner.getNextToken();
				} catch (InvalidInputException e) {
					return false;
				}
				if((
        // token == ITerminalSymbols.TokenNamethis ||
				// token == ITerminalSymbols.TokenNamesuper ||
					token == ITerminalSymbols.TokenNameIdentifier) &&
					scanner.startPosition <= selectionStart &&
					selectionStart <= scanner.currentPosition) {
					lastIdentifierStart = scanner.startPosition;
					lastIdentifierEnd = scanner.currentPosition - 1;
					lastIdentifier = scanner.getCurrentTokenSource();
				}
			} while (token != ITerminalSymbols.TokenNameEOF);
		} else {
			scanner.resetTo(selectionStart, selectionEnd);
	
			boolean expectingIdentifier = true;
			
			do {
				try {
					token = scanner.getNextToken();
				} catch (InvalidInputException e) {
					return false;
				}
				switch (token) {
//					case ITerminalSymbols.TokenNamethis :
//					case ITerminalSymbols.TokenNamesuper :
					case ITerminalSymbols.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);
						}
						entireSelection.append(lastIdentifier);
							
						identCount++;
						expectingIdentifier = false;
						break;
					case ITerminalSymbols.TokenNameDOT :
						if (expectingIdentifier)
							return false;
						entireSelection.append('.');
						expectingIdentifier = true;
						break;
					case ITerminalSymbols.TokenNameEOF :
						if (expectingIdentifier)
							return false;
						break;
					default :
						return false;
				}
			} while (token != ITerminalSymbols.TokenNameEOF);
		}
		if (lastIdentifierStart > 0) {
			actualSelectionStart = lastIdentifierStart;
			actualSelectionEnd = lastIdentifierEnd;
			selectedIdentifier = lastIdentifier;
			if (identCount > 1)
				qualifiedSelection = entireSelection.toString().toCharArray();
			return true;
		}
		return false;
	}

	public AssistParser getParser() {
		return parser;
	}

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

	 *  @param sourceUnit net.sourceforge.phpdt.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;
		try {
			acceptedAnswer = false;
			CompilationResult result = new CompilationResult(sourceUnit, 1, 1, this.compilerOptions.maxProblemsPerUnit);
			CompilationUnitDeclaration parsedUnit =
				parser.dietParse(sourceUnit, result, actualSelectionStart, 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;
					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;
							requestor.acceptPackage(CharOperation.concatWith(tokens, '.'));
							nameEnvironment.findTypes(CharOperation.concatWith(tokens, '.'), this);
							// accept qualified types only if no unqualified type was accepted
							if(!acceptedAnswer) {
								acceptQualifiedTypes();
								if (!acceptedAnswer) {
									nameEnvironment.findTypes(selectedIdentifier, this);
									// try with simple type name
									if(!acceptedAnswer) {
										acceptQualifiedTypes();
									}
								}
							}
							return;
						}
					}
				}
				if (parsedUnit.types != null) {
					lookupEnvironment.buildTypeBindings(parsedUnit);
					if ((this.unitScope = parsedUnit.scope)  != null) {
						try {
							lookupEnvironment.completeTypeBindings(parsedUnit, true);
							parsedUnit.scope.faultInTypes();
							selectDeclaration(parsedUnit);
							parseMethod(parsedUnit, selectionSourceStart);
							if(DEBUG) {
								System.out.println("SELECTION - AST :"); //$NON-NLS-1$
								System.out.println(parsedUnit.toString());
							}
							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);
							}
						}
					}
				}
			}
			// 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 (!acceptedAnswer) {
				nameEnvironment.findTypes(selectedIdentifier, this);
				
				// accept qualified types only if no unqualified type was accepted
				if(!acceptedAnswer) {
					acceptQualifiedTypes();
				}
			}
		} catch (IndexOutOfBoundsException e) { // work-around internal failure - 1GEMF6D		
		} catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
		} finally {
			reset();
		}
	}

	private void selectFrom(Binding binding) {
		if (binding instanceof ReferenceBinding) {
			ReferenceBinding typeBinding = (ReferenceBinding) binding;
			if (qualifiedSelection != null
				&& !CharOperation.equals(qualifiedSelection, typeBinding.readableName())) {
				return;
			}
			if (typeBinding.isInterface()) {
				requestor.acceptInterface(
					typeBinding.qualifiedPackageName(),
					typeBinding.qualifiedSourceName(),
					false);
			} else if(typeBinding instanceof ProblemReferenceBinding){
				ProblemReferenceBinding problemBinding = (ProblemReferenceBinding)typeBinding;
				if(problemBinding.original == null
					|| !(problemBinding.original instanceof ReferenceBinding)) {
					return;
				}
				ReferenceBinding original = (ReferenceBinding) problemBinding.original;

				requestor.acceptClass(
					original.qualifiedPackageName(),
					original.qualifiedSourceName(),
					false);
			} else {
				requestor.acceptClass(
					typeBinding.qualifiedPackageName(),
					typeBinding.qualifiedSourceName(),
					false);
			}
			acceptedAnswer = true;
		} else
			if (binding instanceof MethodBinding) {
				MethodBinding methodBinding = (MethodBinding) binding;
				TypeBinding[] parameterTypes = methodBinding.parameters;
				int length = parameterTypes.length;
				char[][] parameterPackageNames = new char[length][];
				char[][] parameterTypeNames = new char[length][];
				for (int i = 0; i < length; i++) {
					parameterPackageNames[i] = parameterTypes[i].qualifiedPackageName();
					parameterTypeNames[i] = parameterTypes[i].qualifiedSourceName();
				}
				requestor.acceptMethod(
					methodBinding.declaringClass.qualifiedPackageName(),
					methodBinding.declaringClass.qualifiedSourceName(),
					methodBinding.isConstructor()
						? methodBinding.declaringClass.sourceName()
						: methodBinding.selector,
					parameterPackageNames,
					parameterTypeNames,
					methodBinding.isConstructor());
				acceptedAnswer = true;
			} else
				if (binding instanceof FieldBinding) {
					FieldBinding fieldBinding = (FieldBinding) binding;
					if (fieldBinding.declaringClass != null) { // arraylength
						requestor.acceptField(
							fieldBinding.declaringClass.qualifiedPackageName(),
							fieldBinding.declaringClass.qualifiedSourceName(),
							fieldBinding.name);
						acceptedAnswer = true;
					}
				} else
					if (binding instanceof LocalVariableBinding) {
						selectFrom(((LocalVariableBinding) binding).type);
						// open on the type of the variable
					} else
						if (binding instanceof ArrayBinding) {
							selectFrom(((ArrayBinding) binding).leafComponentType);
							// open on the type of the array
						} else
							if (binding instanceof PackageBinding) {
								PackageBinding packageBinding = (PackageBinding) binding;
								requestor.acceptPackage(packageBinding.readableName());
								acceptedAnswer = true;
							} else
								if(binding instanceof BaseTypeBinding) {
									acceptedAnswer = true;
								}
	}

	/**
	 * Asks the engine to compute the selection of the given type
	 * from the source type.
	 *
	 *  @param sourceType net.sourceforge.phpdt.internal.compiler.env.ISourceType
	 *      a source form of the current type in which code assist is invoked.
	 *
	 *  @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 searchInEnvironment
	 * 	if <code>true</code> and no selection could be found in context then search type in environment.
	 */
	public void selectType(ISourceType sourceType, char[] typeName, boolean searchInEnvironment) {
		try {
			acceptedAnswer = false;

			// find the outer most type
			ISourceType outerType = sourceType;
			ISourceType parent = sourceType.getEnclosingType();
			while (parent != null) {
				outerType = parent;
				parent = parent.getEnclosingType();
			}
			// compute parse tree for this most outer type
			CompilationResult result = new CompilationResult(outerType.getFileName(), 1, 1, this.compilerOptions.maxProblemsPerUnit);
			CompilationUnitDeclaration parsedUnit =
				SourceTypeConverter
					.buildCompilationUnit(
						new ISourceType[] { outerType },
						false,
			// don't need field and methods
			true, // by default get member types
			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
				char[] packageName = sourceType.getPackageName();
				char[] sourceTypeName = sourceType.getQualifiedName();
				// the fully qualified name without the package name
				if (packageName != null) {
					// remove the package name if necessary
					sourceTypeName =
						CharOperation.subarray(
							sourceType.getQualifiedName(),
							packageName.length + 1,
							sourceTypeName.length);
				};
				TypeDeclaration typeDecl =
					parsedUnit.declarationOfType(CharOperation.splitOn('.', sourceTypeName));
				if (typeDecl != 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 {
						qualifiedSelection = typeName;
						char[][] previousIdentifiers = CharOperation.splitOn('.', typeName, 0, dot - 1);
						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$
					typeDecl.fields = new FieldDeclaration[] { field };

					// build bindings
					lookupEnvironment.buildTypeBindings(parsedUnit);
					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);
							}
						}
					}
				}
			}
			// 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 (!acceptedAnswer && searchInEnvironment) {
				if (this.selectedIdentifier != null) {
					nameEnvironment.findTypes(typeName, this);
					
					// accept qualified types only if no unqualified type was accepted
					if(!acceptedAnswer) {
						acceptQualifiedTypes();
					}
				}
			}
		} catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
		} finally {
			qualifiedSelection = null;
			reset();
		}
	}

	// Check if a declaration got selected in this unit
	private void 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 = this.getParser().assistIdentifier();
		if (assistIdentifier == null) return;
		
		// iterate over the types
		TypeDeclaration[] types = compilationUnit.types;
		for (int i = 0, length = types == null ? 0 : types.length; i < length; i++){
			selectDeclaration(types[i], assistIdentifier);
		}
	}

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

		if (typeDeclaration.name == assistIdentifier){
			throw new SelectionNodeFound(typeDeclaration.binding);
		}
		TypeDeclaration[] memberTypes = typeDeclaration.memberTypes;
		for (int i = 0, length = memberTypes == null ? 0 : memberTypes.length; i < length; i++){
			selectDeclaration(memberTypes[i], assistIdentifier);
		}
		FieldDeclaration[] fields = typeDeclaration.fields;
		for (int i = 0, length = fields == null ? 0 : fields.length; i < length; i++){
			if (fields[i].name == assistIdentifier){
				throw new SelectionNodeFound(fields[i].binding);
			}
		}
		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){
				if(method.binding != null) {
					throw new SelectionNodeFound(method.binding);
				} else {
					if(method.scope != null) {
						throw new SelectionNodeFound(new MethodBinding(method.modifiers, method.selector, null, null, null, method.scope.referenceType().binding));
					}
				}
		}
		}
	}
}