/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.dom;

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

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.ISourceType;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.CompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToInt;
import org.eclipse.jdt.internal.compiler.util.Messages;
import org.eclipse.jdt.internal.core.BinaryMember;
import org.eclipse.jdt.internal.core.CancelableNameEnvironment;
import org.eclipse.jdt.internal.core.CancelableProblemFactory;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.NameLookup;
import org.eclipse.jdt.internal.core.SourceRefElement;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
import org.eclipse.jdt.internal.core.util.BindingKeyResolver;
import org.eclipse.jdt.internal.core.util.CommentRecorderParser;
import org.eclipse.jdt.internal.core.util.DOMFinder;

class CompilationUnitResolver extends Compiler {
	
	/* A list of int */
	static class IntArrayList {
		public int[] list = new int[5];
		public int length = 0;
		public void add(int i) {
			if (this.list.length == this.length) {
				System.arraycopy(this.list, 0, this.list = new int[this.length*2], 0, this.length);
			}
				this.list[this.length++] = i;
			}
		}
		
	/*
	 * The sources that were requested.
	 * Map from file name (char[]) to ICompilationUnit.
	 */
	HashtableOfObject requestedSources;
	
	/*
	 * The binding keys that were requested.
	 * Map from file name (char[]) to BindingKey (or ArrayList if multiple keys in the same file).
	 */
	HashtableOfObject requestedKeys;
	
	DefaultBindingResolver.BindingTables bindingTables;
	
	boolean hasCompilationAborted;
	
	private IProgressMonitor monitor;

	/**
	 * Answer a new CompilationUnitVisitor using the given name environment and compiler options.
	 * The environment and options will be in effect for the lifetime of the compiler.
	 * When the compiler is run, compilation results are sent to the given requestor.
	 *
	 *  @param environment org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
	 *      Environment used by the compiler in order to resolve type and package
	 *      names. The name environment implements the actual connection of the compiler
	 *      to the outside world (for example, in batch mode the name environment is performing
	 *      pure file accesses, reuse previous build state or connection to repositories).
	 *      Note: the name environment is responsible for implementing the actual classpath
	 *            rules.
	 *
	 *  @param policy org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
	 *      Configurable part for problem handling, allowing the compiler client to
	 *      specify the rules for handling problems (stop on first error or accumulate
	 *      them all) and at the same time perform some actions such as opening a dialog
	 *      in UI when compiling interactively.
	 *      @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
	 * 
	 *	@param settings The settings to use for the resolution.
	 *      
	 *  @param requestor org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
	 *      Component which will receive and persist all compilation results and is intended
	 *      to consume them as they are produced. Typically, in a batch compiler, it is 
	 *      responsible for writing out the actual .class files to the file system.
	 *      @see org.eclipse.jdt.internal.compiler.CompilationResult
	 *
	 *  @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
	 *      Factory used inside the compiler to create problem descriptors. It allows the
	 *      compiler client to supply its own representation of compilation problems in
	 *      order to avoid object conversions. Note that the factory is not supposed
	 *      to accumulate the created problems, the compiler will gather them all and hand
	 *      them back as part of the compilation unit result.
	 */
	public CompilationUnitResolver(
		INameEnvironment environment,
		IErrorHandlingPolicy policy,
		Map settings,
		ICompilerRequestor requestor,
		IProblemFactory problemFactory,
		IProgressMonitor monitor) {

		super(environment, policy, settings, requestor, problemFactory, false);
		this.hasCompilationAborted = false;
		this.monitor =monitor;
	}
	
	/*
	 * Add additional source types
	 */
	public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding, AccessRestriction accessRestriction) {
		// Need to reparse the entire source of the compilation unit so as to get source positions
		// (case of processing a source that was not known by beginToCompile (e.g. when asking to createBinding))
		SourceTypeElementInfo sourceType = (SourceTypeElementInfo) sourceTypes[0];
		accept((org.eclipse.jdt.internal.compiler.env.ICompilationUnit) sourceType.getHandle().getCompilationUnit(), accessRestriction);
	}

	/**
	 * Add the initial set of compilation units into the loop
	 *  ->  build compilation unit declarations, their bindings and record their results.
	 */
	protected void beginToCompile(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] sourceUnits, String[] bindingKeys) {
		int sourceLength = sourceUnits.length;
		int keyLength = bindingKeys.length;
		int maxUnits = sourceLength + keyLength;
		this.totalUnits = 0;
		this.unitsToProcess = new CompilationUnitDeclaration[maxUnits];
		int index = 0;

		// walks the source units
		this.requestedSources = new HashtableOfObject();
		for (int i = 0; i < sourceLength; i++) {
			org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = sourceUnits[i];
			CompilationUnitDeclaration parsedUnit;
			CompilationResult unitResult =
				new CompilationResult(sourceUnit, index++, maxUnits, this.options.maxProblemsPerUnit);
			try {
				if (options.verbose) {
					System.out.println(
						Messages.bind(Messages.compilation_request,
						new String[] {
							String.valueOf(index++ + 1),
							String.valueOf(maxUnits),
							new String(sourceUnit.getFileName())
						}));
				}
				// diet parsing for large collection of units
				if (this.totalUnits < this.parseThreshold) {
					parsedUnit = this.parser.parse(sourceUnit, unitResult);
				} else {
					parsedUnit = this.parser.dietParse(sourceUnit, unitResult);
				}
				// initial type binding creation
				this.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
				addCompilationUnit(sourceUnit, parsedUnit);
				this.requestedSources.put(unitResult.getFileName(), sourceUnit);
				worked(1);
			} finally {
				sourceUnits[i] = null; // no longer hold onto the unit
			}
		}

		// walk the binding keys
		this.requestedKeys = new HashtableOfObject();
		for (int i = 0; i < keyLength; i++) {
			BindingKeyResolver resolver = new BindingKeyResolver(bindingKeys[i], this, this.lookupEnvironment);
			resolver.parse(true/*pause after fully qualified name*/);
			CompilationUnitDeclaration parsedUnit = resolver.getCompilationUnitDeclaration();
			if (parsedUnit != null) {
				char[] fileName = parsedUnit.compilationResult.getFileName();
				Object existing = this.requestedKeys.get(fileName);
				if (existing == null)
					this.requestedKeys.put(fileName, resolver);
				else if (existing instanceof ArrayList)
					((ArrayList) existing).add(resolver);
				else {
					ArrayList list = new ArrayList();
					list.add(existing);
					list.add(resolver);
					this.requestedKeys.put(fileName, list);
				} 
					
			} else {
				char[] key = resolver.hasTypeName() 
					? resolver.getKey().toCharArray() // binary binding
					: CharOperation.concatWith(resolver.compoundName(), '.'); // package binding or base type binding
				this.requestedKeys.put(key, resolver);
			}
			worked(1);
		}
		
		// binding resolution
		lookupEnvironment.completeTypeBindings();
	}
	
	IBinding createBinding(String key) {
		if (this.bindingTables == null)
			throw new RuntimeException("Cannot be called outside ASTParser#createASTs(...)"); //$NON-NLS-1$
		BindingKeyResolver keyResolver = new BindingKeyResolver(key, this, this.lookupEnvironment);
		Binding compilerBinding = keyResolver.getCompilerBinding();
		if (compilerBinding == null) return null;
		DefaultBindingResolver resolver = new DefaultBindingResolver(this.lookupEnvironment, null/*no owner*/, this.bindingTables);
		return resolver.getBinding(compilerBinding);
	}
	
	public static CompilationUnit convert(CompilationUnitDeclaration compilationUnitDeclaration, char[] source, int apiLevel, Map options, boolean needToResolveBindings, WorkingCopyOwner owner, DefaultBindingResolver.BindingTables bindingTables, IProgressMonitor monitor) {
		BindingResolver resolver = null;
		AST ast = AST.newAST(apiLevel);
		ast.setDefaultNodeFlag(ASTNode.ORIGINAL);
		CompilationUnit compilationUnit = null;
		ASTConverter converter = new ASTConverter(options, needToResolveBindings, monitor);
		if (needToResolveBindings) {
			resolver = new DefaultBindingResolver(compilationUnitDeclaration.scope, owner, bindingTables);
		} else {
			resolver = new BindingResolver();
		}
		ast.setBindingResolver(resolver);
		converter.setAST(ast);
		compilationUnit = converter.convert(compilationUnitDeclaration, source);
		compilationUnit.setLineEndTable(compilationUnitDeclaration.compilationResult.lineSeparatorPositions);
		ast.setDefaultNodeFlag(0);
		ast.setOriginalModificationCount(ast.modificationCount());
		return compilationUnit;
	}
	
	/*
	 *  Low-level API performing the actual compilation
	 */
	protected static IErrorHandlingPolicy getHandlingPolicy() {

		// passes the initial set of files to the batch oracle (to avoid finding more than once the same units when case insensitive match)	
		return new IErrorHandlingPolicy() {
			public boolean stopOnFirstError() {
				return false;
			}
			public boolean proceedOnErrors() {
				return false; // stop if there are some errors 
			}
		};
	}

	/*
	 * Answer the component to which will be handed back compilation results from the compiler
	 */
	protected static ICompilerRequestor getRequestor() {
		return new ICompilerRequestor() {
			public void acceptResult(CompilationResult compilationResult) {
				// do nothing
			}
		};
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.internal.compiler.Compiler#initializeParser()
	 */
	public void initializeParser() {
		this.parser = new CommentRecorderParser(this.problemReporter, false);
	}
	public void process(CompilationUnitDeclaration unit, int i) {
		// don't resolve a second time the same unit (this would create the same binding twice)
		char[] fileName = unit.compilationResult.getFileName();
		if (!this.requestedKeys.containsKey(fileName) && !this.requestedSources.containsKey(fileName))
			super.process(unit, i);
	}
	/*
	 * Compiler crash recovery in case of unexpected runtime exceptions
	 */
	protected void handleInternalException(
			Throwable internalException,
			CompilationUnitDeclaration unit,
			CompilationResult result) {
		super.handleInternalException(internalException, unit, result);
		if (unit != null) {
			removeUnresolvedBindings(unit);
		}
	}
	
	/*
	 * Compiler recovery in case of internal AbortCompilation event
	 */
	protected void handleInternalException(
			AbortCompilation abortException,
			CompilationUnitDeclaration unit) {
		super.handleInternalException(abortException, unit);
		if (unit != null) {
			removeUnresolvedBindings(unit);
		}
		this.hasCompilationAborted = true;
	}	
	
	public static void parse(ICompilationUnit[] compilationUnits, ASTRequestor astRequestor, int apiLevel, Map options, IProgressMonitor monitor) {
		try {
			CompilerOptions compilerOptions = new CompilerOptions(options);
			Parser parser = new CommentRecorderParser(
				new ProblemReporter(
						DefaultErrorHandlingPolicies.proceedWithAllProblems(), 
						compilerOptions, 
						new DefaultProblemFactory()),
				false);
			int length = compilationUnits.length;
			if (monitor != null) monitor.beginTask("", length); //$NON-NLS-1$
			for (int i = 0; i < length; i++) {
				org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) compilationUnits[i];
				CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
				CompilationUnitDeclaration compilationUnitDeclaration = parser.dietParse(sourceUnit, compilationResult);
				
				if (compilationUnitDeclaration.ignoreMethodBodies) {
					compilationUnitDeclaration.ignoreFurtherInvestigation = true;
					// if initial diet parse did not work, no need to dig into method bodies.
					continue; 
				}
				
				//fill the methods bodies in order for the code to be generated
				//real parse of the method....
				parser.scanner.setSource(compilationResult);
				org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] types = compilationUnitDeclaration.types;
				if (types != null) {
					for (int j = types.length; --j >= 0;)
						types[j].parseMethod(parser, compilationUnitDeclaration);
				}
				
				// convert AST
				CompilationUnit node = convert(compilationUnitDeclaration, sourceUnit.getContents(), apiLevel, options, false/*don't resolve binding*/, null/*no owner needed*/, null/*no binding table needed*/, monitor);
				node.setJavaElement(compilationUnits[i]);
				
				// accept AST
				astRequestor.acceptAST(compilationUnits[i], node);
				
				if (monitor != null) monitor.worked(1);
			}
		} finally {
			if (monitor != null) monitor.done();
		}
	}
	
	public static CompilationUnitDeclaration parse(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, NodeSearcher nodeSearcher, Map settings) {
		if (sourceUnit == null) {
			throw new IllegalStateException();
		}
		CompilerOptions compilerOptions = new CompilerOptions(settings);
		Parser parser = new CommentRecorderParser(
			new ProblemReporter(
					DefaultErrorHandlingPolicies.proceedWithAllProblems(), 
					compilerOptions, 
					new DefaultProblemFactory()),
			false);
		CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
		CompilationUnitDeclaration compilationUnitDeclaration = parser.dietParse(sourceUnit, compilationResult);
		
		if (compilationUnitDeclaration.ignoreMethodBodies) {
			compilationUnitDeclaration.ignoreFurtherInvestigation = true;
			// if initial diet parse did not work, no need to dig into method bodies.
			return null; 
		}
		
		if (nodeSearcher != null) {
			char[] source = sourceUnit.getContents();
			int searchPosition = nodeSearcher.position;
			if (searchPosition < 0 || searchPosition > source.length) {
				// the position is out of range. There is no need to search for a node.
	 			return compilationUnitDeclaration;
			}
		
			compilationUnitDeclaration.traverse(nodeSearcher, compilationUnitDeclaration.scope);
			
			org.eclipse.jdt.internal.compiler.ast.ASTNode node = nodeSearcher.found;
	 		if (node == null) {
	 			return compilationUnitDeclaration;
	 		}
	 		
	 		org.eclipse.jdt.internal.compiler.ast.TypeDeclaration enclosingTypeDeclaration = nodeSearcher.enclosingType;
	 		
			if (node instanceof AbstractMethodDeclaration) {
				((AbstractMethodDeclaration)node).parseStatements(parser, compilationUnitDeclaration);
			} else if (enclosingTypeDeclaration != null) {
				if (node instanceof org.eclipse.jdt.internal.compiler.ast.Initializer) {
					((org.eclipse.jdt.internal.compiler.ast.Initializer) node).parseStatements(parser, enclosingTypeDeclaration, compilationUnitDeclaration);
				} else {  					
					((org.eclipse.jdt.internal.compiler.ast.TypeDeclaration)node).parseMethod(parser, compilationUnitDeclaration);
				} 				
			}
		} else {
			//fill the methods bodies in order for the code to be generated
			//real parse of the method....
			parser.scanner.setSource(compilationResult);
			org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] types = compilationUnitDeclaration.types;
			if (types != null) {
				for (int i = types.length; --i >= 0;)
					types[i].parseMethod(parser, compilationUnitDeclaration);
			}
		}
		return compilationUnitDeclaration;
	}

	public static void resolve(
		ICompilationUnit[] compilationUnits,
		String[] bindingKeys,
		ASTRequestor requestor,
		int apiLevel,
		Map options,
		IJavaProject javaProject,
		WorkingCopyOwner owner,
		IProgressMonitor monitor) {
	
		CancelableNameEnvironment environment = null;
		CancelableProblemFactory problemFactory = null;
		try {
			if (monitor != null) {
				int amountOfWork = (compilationUnits.length + bindingKeys.length) * 2; // 1 for beginToCompile, 1 for resolve
				monitor.beginTask("", amountOfWork); //$NON-NLS-1$
			}
			environment = new CancelableNameEnvironment(((JavaProject) javaProject), owner, monitor);
			problemFactory = new CancelableProblemFactory(monitor);
			CompilationUnitResolver resolver =
				new CompilationUnitResolver(
					environment,
					getHandlingPolicy(),
					options,
					getRequestor(),
					problemFactory, 
					monitor);

			resolver.resolve(compilationUnits, bindingKeys, requestor, apiLevel, options, owner);
			if (NameLookup.VERBOSE) {
				System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
				System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
			}
		} catch (JavaModelException e) {
			// project doesn't exist -> simple parse without resolving
			parse(compilationUnits, requestor, apiLevel, options, monitor);
		} finally {
			if (monitor != null) monitor.done();
			if (environment != null) {
				environment.monitor = null; // don't hold a reference to this external object
			}
			if (problemFactory != null) {
				problemFactory.monitor = null; // don't hold a reference to this external object
			}
		}
	}
	public static CompilationUnitDeclaration resolve(
		org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit,
		IJavaProject javaProject,
		NodeSearcher nodeSearcher,
		Map options,
		WorkingCopyOwner owner,
		IProgressMonitor monitor) throws JavaModelException {
	
		CompilationUnitDeclaration unit = null;
		CancelableNameEnvironment environment = null;
		CancelableProblemFactory problemFactory = null;
		CompilationUnitResolver resolver = null;
		try {
			environment = new CancelableNameEnvironment(((JavaProject)javaProject), owner, monitor);
			problemFactory = new CancelableProblemFactory(monitor);
			resolver =
				new CompilationUnitResolver(
					environment,
					getHandlingPolicy(),
					options,
					getRequestor(),
					problemFactory, 
					monitor);

			unit = 
				resolver.resolve(
					null, // no existing compilation unit declaration
					sourceUnit,
					nodeSearcher,
					true, // method verification
					true, // analyze code
					true); // generate code
			if (resolver.hasCompilationAborted) {
				// the bindings could not be resolved due to missing types in name environment
				// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=86541
				CompilationUnitDeclaration unitDeclaration = parse(sourceUnit, nodeSearcher, options);
				final int problemCount = unit.compilationResult.problemCount;
				if (problemCount != 0) {
					unitDeclaration.compilationResult.problems = new IProblem[problemCount];
					System.arraycopy(unit.compilationResult.problems, 0, unitDeclaration.compilationResult.problems, 0, problemCount);
					unitDeclaration.compilationResult.problemCount = problemCount;
				}
				return unitDeclaration;
			}
			if (NameLookup.VERBOSE) {
				System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
				System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
			}	
			return unit;
		} finally {
			if (environment != null) {
				environment.monitor = null; // don't hold a reference to this external object
			}
			if (problemFactory != null) {
				problemFactory.monitor = null; // don't hold a reference to this external object
			}
			// first unit cleanup is done by caller, but cleanup all enqueued requested units (not processed)
//			if (resolver != null) {
//				for (int i = 1; i <  resolver.totalUnits; i++) { // could be more requested units
//					CompilationUnitDeclaration parsedUnit = resolver.unitsToProcess[i];
//					if (parsedUnit.scope != null) 
//						parsedUnit.scope.faultInTypes(); // force resolution of signatures, so clients can query DOM AST
//					parsedUnit.cleanUp();
//				}
//			}
		}
	}
	public static IBinding[] resolve(
		final IJavaElement[] elements,
		int apiLevel,
		Map compilerOptions,
		IJavaProject javaProject,
		WorkingCopyOwner owner,
		IProgressMonitor monitor) {

		final int length = elements.length;
		final HashMap sourceElementPositions = new HashMap(); // a map from ICompilationUnit to int[] (positions in elements)
		int cuNumber = 0;
		final HashtableOfObjectToInt binaryElementPositions = new HashtableOfObjectToInt(); // a map from String (binding key) to int (position in elements)
		for (int i = 0; i < length; i++) {
			IJavaElement element = elements[i];
			if (!(element instanceof SourceRefElement))
				throw new IllegalStateException(element + " is not part of a compilation unit or class file"); //$NON-NLS-1$
			Object cu = element.getAncestor(IJavaElement.COMPILATION_UNIT);
			if (cu != null) {
				// source member
				IntArrayList intList = (IntArrayList) sourceElementPositions.get(cu);
				if (intList == null) {
					sourceElementPositions.put(cu, intList = new IntArrayList());
					cuNumber++;
				}
				intList.add(i);
			} else {
				// binary member
				try {
					String key = ((BinaryMember) element).getKey(true/*open to get resolved info*/);
					binaryElementPositions.put(key, i);
				} catch (JavaModelException e) {
					throw new IllegalArgumentException(element + " does not exist"); //$NON-NLS-1$
				}
			}	
		}
		ICompilationUnit[] cus = new ICompilationUnit[cuNumber];
		sourceElementPositions.keySet().toArray(cus);
		
		int bindingKeyNumber = binaryElementPositions.size();
		String[] bindingKeys = new String[bindingKeyNumber];
		binaryElementPositions.keysToArray(bindingKeys);
		
		class Requestor extends ASTRequestor {
			IBinding[] bindings = new IBinding[length];
			public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
				// TODO (jerome) optimize to visit the AST only once
				IntArrayList intList = (IntArrayList) sourceElementPositions.get(source);
				for (int i = 0; i < intList.length; i++) {
					final int index = intList.list[i];
					SourceRefElement element = (SourceRefElement) elements[index];
					DOMFinder finder = new DOMFinder(ast, element, true/*resolve binding*/);
					try {
						finder.search();
					} catch (JavaModelException e) {
						throw new IllegalArgumentException(element + " does not exist"); //$NON-NLS-1$
					}
					this.bindings[index] = finder.foundBinding;
				}
			}
			public void acceptBinding(String bindingKey, IBinding binding) {
				int index = binaryElementPositions.get(bindingKey);
				this.bindings[index] = binding;
			}
		}
		Requestor requestor = new Requestor();
		resolve(cus, bindingKeys, requestor, apiLevel, compilerOptions, javaProject, owner, monitor);
		return requestor.bindings;
	}
	/*
	 * When unit result is about to be accepted, removed back pointers
	 * to unresolved bindings
	 */
	public void removeUnresolvedBindings(CompilationUnitDeclaration compilationUnitDeclaration) {
		final org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] types = compilationUnitDeclaration.types;
		if (types != null) {
			for (int i = 0, max = types.length; i < max; i++) {
				removeUnresolvedBindings(types[i]);
			}
		}
	}
	private void removeUnresolvedBindings(org.eclipse.jdt.internal.compiler.ast.TypeDeclaration type) {
		final org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] memberTypes = type.memberTypes;
		if (memberTypes != null) {
			for (int i = 0, max = memberTypes.length; i < max; i++){
				removeUnresolvedBindings(memberTypes[i]);
			}
		}
		if (type.binding != null && (type.binding.modifiers & CompilerModifiers.AccUnresolved) != 0) {
			type.binding = null;
		}
		
		final org.eclipse.jdt.internal.compiler.ast.FieldDeclaration[] fields = type.fields;
		if (fields != null) {
			for (int i = 0, max = fields.length; i < max; i++){
				if (fields[i].binding != null && (fields[i].binding.modifiers & CompilerModifiers.AccUnresolved) != 0) {
					fields[i].binding = null;
				}
			}
		}
	
		final AbstractMethodDeclaration[] methods = type.methods;
		if (methods != null) {
			for (int i = 0, max = methods.length; i < max; i++){
				if (methods[i].binding !=  null && (methods[i].binding.modifiers & CompilerModifiers.AccUnresolved) != 0) {
					methods[i].binding = null;
				}
			}
		}
	}

	private void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor astRequestor, int apiLevel, Map compilerOptions, WorkingCopyOwner owner) {
	
		// temporararily connect ourselves to the ASTResolver - must disconnect when done
		astRequestor.compilationUnitResolver = this;
		this.bindingTables = new DefaultBindingResolver.BindingTables();
		CompilationUnitDeclaration unit = null;
		int i = 0;
		try {
			int length = compilationUnits.length;
			org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] sourceUnits = new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[length];
			System.arraycopy(compilationUnits, 0, sourceUnits, 0, length);
			beginToCompile(sourceUnits, bindingKeys);
			// process all units (some more could be injected in the loop by the lookup environment)
			for (; i < this.totalUnits; i++) {
				unit = this.unitsToProcess[i];
				try {
					char[] fileName = unit.compilationResult.getFileName();
					
					// only process requested units
					if (this.requestedKeys.containsKey(fileName) || this.requestedSources.containsKey(fileName)) {
						super.process(unit, i); // this.process(...) is optimized to not process already known units
						
						ICompilationUnit source = (ICompilationUnit) this.requestedSources.get(fileName);
						if (source != null) {
							// convert AST
							CompilationResult compilationResult = unit.compilationResult;
							org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = compilationResult.compilationUnit;
							char[] contents = sourceUnit.getContents();
							AST ast = AST.newAST(apiLevel);
							ast.setDefaultNodeFlag(ASTNode.ORIGINAL);
							ASTConverter converter = new ASTConverter(compilerOptions, true/*need to resolve bindings*/, this.monitor);
							BindingResolver resolver = new DefaultBindingResolver(unit.scope, owner, this.bindingTables);
							ast.setBindingResolver(resolver);
							converter.setAST(ast);
							CompilationUnit compilationUnit = converter.convert(unit, contents);
							compilationUnit.setJavaElement(source);
							compilationUnit.setLineEndTable(compilationResult.lineSeparatorPositions);
							ast.setDefaultNodeFlag(0);
							ast.setOriginalModificationCount(ast.modificationCount());
							
							// pass it to requestor
							astRequestor.acceptAST(source, compilationUnit);
							
							worked(1);
						} 
						
						Object key = this.requestedKeys.get(fileName);
						if (key instanceof BindingKeyResolver) {
							reportBinding(key, astRequestor, owner, unit);
							worked(1);
						} else if (key instanceof ArrayList) {
							Iterator iterator = ((ArrayList) key).iterator();
							while (iterator.hasNext()) {
								reportBinding(iterator.next(), astRequestor, owner, unit);
								worked(1);
							}
						}
						
						// remove at the end so that we don't resolve twice if a source and a key for the same file name have been requested
						this.requestedSources.removeKey(fileName);
						this.requestedKeys.removeKey(fileName);
					} else {
						if (unit.scope != null)
							unit.scope.faultInTypes(); // still force resolution of signatures, so clients can query DOM AST
					}
				} finally {
					// cleanup compilation unit result
					unit.cleanUp();
				}
				this.unitsToProcess[i] = null; // release reference to processed unit declaration
				this.requestor.acceptResult(unit.compilationResult.tagAsAccepted());
			}
			
			// remaining binding keys
			DefaultBindingResolver resolver = new DefaultBindingResolver(this.lookupEnvironment, owner, this.bindingTables);
			Object[] keys = this.requestedKeys.valueTable;
			for (int j = 0, keysLength = keys.length; j < keysLength; j++) {
				BindingKeyResolver keyResolver = (BindingKeyResolver) keys[j];
				if (keyResolver == null) continue;
				Binding compilerBinding = keyResolver.getCompilerBinding();
				IBinding binding = compilerBinding == null ? null : resolver.getBinding(compilerBinding);
				// pass it to requestor
				astRequestor.acceptBinding(((BindingKeyResolver) this.requestedKeys.valueTable[j]).getKey(), binding);
				worked(1);
			}
		} catch (OperationCanceledException e) {
			throw e;
		} catch (AbortCompilation e) {
			this.handleInternalException(e, unit);
		} catch (Error e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} catch (RuntimeException e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} finally {
			// disconnect ourselves from ast requestor
            astRequestor.compilationUnitResolver = null;
		}
	}

	private void reportBinding(Object key, ASTRequestor astRequestor, WorkingCopyOwner owner, CompilationUnitDeclaration unit) {
		BindingKeyResolver keyResolver = (BindingKeyResolver) key;
		Binding compilerBinding = keyResolver.getCompilerBinding();
		if (compilerBinding != null) {
			DefaultBindingResolver resolver = new DefaultBindingResolver(unit.scope, owner, this.bindingTables);
			IBinding binding = resolver.getBinding(compilerBinding);
			if (binding != null)
				astRequestor.acceptBinding(keyResolver.getKey(), binding);
		}
	}
	
	private CompilationUnitDeclaration resolve(
			CompilationUnitDeclaration unit,
			org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit,
			NodeSearcher nodeSearcher,
			boolean verifyMethods,
			boolean analyzeCode,
			boolean generateCode) {

		try {

			if (unit == null) {
				// build and record parsed units
				this.parseThreshold = 0; // will request a full parse
				beginToCompile(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] { sourceUnit });
				// process all units (some more could be injected in the loop by the lookup environment)
				unit = this.unitsToProcess[0];
			} else {
				// initial type binding creation
				this.lookupEnvironment.buildTypeBindings(unit, null /*no access restriction*/);

				// binding resolution
				this.lookupEnvironment.completeTypeBindings();
			}

			if (nodeSearcher == null) {
				this.parser.getMethodBodies(unit); // no-op if method bodies have already been parsed
			} else {
				int searchPosition = nodeSearcher.position;
				if (searchPosition >= 0 && searchPosition <= sourceUnit.getContents().length) {
					unit.traverse(nodeSearcher, unit.scope);
					
					org.eclipse.jdt.internal.compiler.ast.ASTNode node = nodeSearcher.found;
					
					this.parser.scanner.setSource(unit.compilationResult);
					
		 			if (node != null) {
						org.eclipse.jdt.internal.compiler.ast.TypeDeclaration enclosingTypeDeclaration = nodeSearcher.enclosingType;
		  				if (node instanceof AbstractMethodDeclaration) {
							((AbstractMethodDeclaration)node).parseStatements(this.parser, unit);
		 				} else if (enclosingTypeDeclaration != null) {
							if (node instanceof org.eclipse.jdt.internal.compiler.ast.Initializer) {
			 					((org.eclipse.jdt.internal.compiler.ast.Initializer) node).parseStatements(this.parser, enclosingTypeDeclaration, unit);
		 					} else if (node instanceof org.eclipse.jdt.internal.compiler.ast.TypeDeclaration) {  					
								((org.eclipse.jdt.internal.compiler.ast.TypeDeclaration)node).parseMethod(this.parser, unit);
							} 				
		 				}
		 			}
				}
			}
			
			if (unit.scope != null) {
				// fault in fields & methods
				unit.scope.faultInTypes();
				if (unit.scope != null && verifyMethods) {
					// http://dev.eclipse.org/bugs/show_bug.cgi?id=23117
 					// verify inherited methods
					unit.scope.verifyMethods(this.lookupEnvironment.methodVerifier());
				}
				// type checking
				unit.resolve();		

				// flow analysis
				if (analyzeCode) unit.analyseCode();
		
				// code generation
				if (generateCode) unit.generateCode();
			}
			if (this.unitsToProcess != null) this.unitsToProcess[0] = null; // release reference to processed unit declaration
			this.requestor.acceptResult(unit.compilationResult.tagAsAccepted());
			return unit;
		} catch (AbortCompilation e) {
			this.handleInternalException(e, unit);
			return unit == null ? this.unitsToProcess[0] : unit;
		} catch (Error e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} catch (RuntimeException e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} finally {
			// No reset is performed there anymore since,
			// within the CodeAssist (or related tools),
			// the compiler may be called *after* a call
			// to this resolve(...) method. And such a call
			// needs to have a compiler with a non-empty
			// environment.
			// this.reset();
		}
	}
	/*
	 * Internal API used to resolve a given compilation unit. Can run a subset of the compilation process
	 */
	public CompilationUnitDeclaration resolve(
			org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, 
			boolean verifyMethods,
			boolean analyzeCode,
			boolean generateCode) {
				
		return resolve(
			null, /* no existing compilation unit declaration*/
			sourceUnit,
			null/*no node searcher*/, 
			verifyMethods,
			analyzeCode,
			generateCode);
	}

	/*
	 * Internal API used to resolve a given compilation unit. Can run a subset of the compilation process
	 */
	public CompilationUnitDeclaration resolve(
			CompilationUnitDeclaration unit, 
			org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, 
			boolean verifyMethods,
			boolean analyzeCode,
			boolean generateCode) {
		
		return resolve(
			unit, 
			sourceUnit, 
			null/*no node searcher*/, 
			verifyMethods, 
			analyzeCode, 
			generateCode);
	}
	
	private void worked(int work) {
		if (this.monitor != null) {
			if (this.monitor.isCanceled())
				throw new OperationCanceledException();
			this.monitor.worked(work);
		}
	}
}
