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

import java.util.*;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
import org.eclipse.jdt.internal.compiler.util.ObjectVector;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;

public abstract class Scope implements TypeConstants, TypeIds {
	
	/* Scope kinds */
	public final static int BLOCK_SCOPE = 1;
	public final static int CLASS_SCOPE = 3;
	public final static int COMPILATION_UNIT_SCOPE = 4;
	public final static int METHOD_SCOPE = 2;

	/* Argument Compatibilities */
	public final static int NOT_COMPATIBLE = -1;
	public final static int COMPATIBLE = 0;
	public final static int AUTOBOX_COMPATIBLE = 1;
	public final static int VARARGS_COMPATIBLE = 2;

	/* Type Compatibilities */
	public static final int EQUAL_OR_MORE_SPECIFIC = -1;
	public static final int NOT_RELATED = 0;
	public static final int MORE_GENERIC = 1;	

	public int kind;
	public Scope parent;
	
	/* Answer an int describing the relationship between the given types.
	*
	* 		NOT_RELATED 
	* 		EQUAL_OR_MORE_SPECIFIC : left is compatible with right
	* 		MORE_GENERIC : right is compatible with left
	*/
	public static int compareTypes(TypeBinding left, TypeBinding right) {
		if (left.isCompatibleWith(right))
			return Scope.EQUAL_OR_MORE_SPECIFIC;
		if (right.isCompatibleWith(left))
			return Scope.MORE_GENERIC;
		return Scope.NOT_RELATED;
	}
	public static TypeBinding getBaseType(char[] name) {
		// list should be optimized (with most often used first)
		int length = name.length;
		if (length > 2 && length < 8) {
			switch (name[0]) {
				case 'i' :
					if (length == 3 && name[1] == 'n' && name[2] == 't')
						return TypeBinding.INT;
					break;
				case 'v' :
					if (length == 4 && name[1] == 'o' && name[2] == 'i' && name[3] == 'd')
						return TypeBinding.VOID;
					break;
				case 'b' :
					if (length == 7
						&& name[1] == 'o'
						&& name[2] == 'o'
						&& name[3] == 'l'
						&& name[4] == 'e'
						&& name[5] == 'a'
						&& name[6] == 'n')
						return TypeBinding.BOOLEAN;
					if (length == 4 && name[1] == 'y' && name[2] == 't' && name[3] == 'e')
						return TypeBinding.BYTE;
					break;
				case 'c' :
					if (length == 4 && name[1] == 'h' && name[2] == 'a' && name[3] == 'r')
						return TypeBinding.CHAR;
					break;
				case 'd' :
					if (length == 6
						&& name[1] == 'o'
						&& name[2] == 'u'
						&& name[3] == 'b'
						&& name[4] == 'l'
						&& name[5] == 'e')
						return TypeBinding.DOUBLE;
					break;
				case 'f' :
					if (length == 5
						&& name[1] == 'l'
						&& name[2] == 'o'
						&& name[3] == 'a'
						&& name[4] == 't')
						return TypeBinding.FLOAT;
					break;
				case 'l' :
					if (length == 4 && name[1] == 'o' && name[2] == 'n' && name[3] == 'g')
						return TypeBinding.LONG;
					break;
				case 's' :
					if (length == 5
						&& name[1] == 'h'
						&& name[2] == 'o'
						&& name[3] == 'r'
						&& name[4] == 't')
						return TypeBinding.SHORT;
			}
		}
		return null;
	}

   // 5.1.10
	public static ReferenceBinding[] greaterLowerBound(ReferenceBinding[] types) {
		if (types == null) return null;
		int length = types.length;
		if (length == 0) return null;
		ReferenceBinding[] result = types;
		int removed = 0;
		for (int i = 0; i < length; i++) {
			ReferenceBinding iType = result[i];
			if (iType == null) continue;
			for (int j = 0; j < length; j++) {
				if (i == j) continue;
				ReferenceBinding jType = result[j];
				if (jType == null) continue;
				if (iType.isCompatibleWith(jType)) { // if Vi <: Vj, Vj is removed
					if (result == types) { // defensive copy
						System.arraycopy(result, 0, result = new ReferenceBinding[length], 0, length);
					}
					result[j] = null;
					removed ++;
				}
			}
		}
		if (removed == 0) return result;
		if (length == removed) return null;
		ReferenceBinding[] trimmedResult = new ReferenceBinding[length - removed];
		for (int i = 0, index = 0; i < length; i++) {
			ReferenceBinding iType = result[i];
			if (iType != null) {
				trimmedResult[index++] = iType;
			}
		}
		return trimmedResult;
	}
	
	// 5.1.10
	public static TypeBinding[] greaterLowerBound(TypeBinding[] types) {
		if (types == null) return null;
		int length = types.length;
		if (length == 0) return null;
		TypeBinding[] result = types;
		int removed = 0;
		for (int i = 0; i < length; i++) {
			TypeBinding iType = result[i];
			if (iType == null) continue;
			for (int j = 0; j < length; j++) {
				if (i == j) continue;
				TypeBinding jType = result[j];
				if (jType == null) continue;
				if (iType.isCompatibleWith(jType)) { // if Vi <: Vj, Vj is removed
					if (result == types) { // defensive copy
						System.arraycopy(result, 0, result = new TypeBinding[length], 0, length);
					}
					result[j] = null;
					removed ++;
				}
			}
		}
		if (removed == 0) return result;
		if (length == removed) return null;
		TypeBinding[] trimmedResult = new TypeBinding[length - removed];
		for (int i = 0, index = 0; i < length; i++) {
			TypeBinding iType = result[i];
			if (iType != null) {
				trimmedResult[index++] = iType;
			}
		}
		return trimmedResult;
	}
	
	/**
	 * Returns an array of types, where original types got substituted given a substitution.
	 * Only allocate an array if anything is different.
	 */
	public static ReferenceBinding[] substitute(Substitution substitution, ReferenceBinding[] originalTypes) {
		if (originalTypes == null) return null;
	    ReferenceBinding[] substitutedTypes = originalTypes;
	    for (int i = 0, length = originalTypes.length; i < length; i++) {
	        ReferenceBinding originalType = originalTypes[i];
	        ReferenceBinding substitutedParameter = (ReferenceBinding)substitute(substitution, originalType);
	        if (substitutedParameter != originalType) {
	            if (substitutedTypes == originalTypes) {
	                System.arraycopy(originalTypes, 0, substitutedTypes = new ReferenceBinding[length], 0, i);
	            }
	            substitutedTypes[i] = substitutedParameter;
	        } else if (substitutedTypes != originalTypes) {
	            substitutedTypes[i] = originalType;
	        }
	    }
	    return substitutedTypes;
	}

	/**
	 * Returns a type, where original type was substituted using the receiver
	 * parameterized type.
	 * In raw mode, all parameterized type denoting same original type are converted
	 * to raw types. e.g. 
	 * class X <T> {
	 *   X<T> foo;
	 *   X<String> bar;
	 * } when used in raw fashion, then type of both foo and bar is raw type X.
	 * 
	 */
	public static TypeBinding substitute(Substitution substitution, TypeBinding originalType) {
		if (originalType == null) return null;
		switch (originalType.kind()) {
			
			case Binding.TYPE_PARAMETER:
				return substitution.substitute((TypeVariableBinding) originalType);
				
			case Binding.PARAMETERIZED_TYPE:
				ParameterizedTypeBinding originalParameterizedType = (ParameterizedTypeBinding) originalType;
				ReferenceBinding originalEnclosing = originalType.enclosingType();
				ReferenceBinding substitutedEnclosing = originalEnclosing;
				if (originalEnclosing != null) {
					substitutedEnclosing = (ReferenceBinding) substitute(substitution, originalEnclosing);
				}
				TypeBinding[] originalArguments = originalParameterizedType.arguments;
				TypeBinding[] substitutedArguments = originalArguments;
				if (originalArguments != null) {
					if (substitution.isRawSubstitution()) {
						return originalParameterizedType.environment.createRawType(originalParameterizedType.genericType(), substitutedEnclosing);
					}
					substitutedArguments = substitute(substitution, originalArguments);
				}
				if (substitutedArguments != originalArguments || substitutedEnclosing != originalEnclosing) {
//					identicalVariables: { // if substituted with original variables, then answer the generic type itself
//						if (substitutedEnclosing != null) {
//							//if (!(substitutedEnclosing instanceof SourceTypeBinding)) break identicalVariables;
//							if (substitutedEnclosing != originalEnclosing) break identicalVariables;						
//						}
//						if (originalParameterizedType.type.isBinaryBinding()) break identicalVariables; // generic binary is never used as is, see 85262
//						TypeVariableBinding[] originalVariables = originalParameterizedType.type.typeVariables();
//						for (int i = 0, length = originalVariables.length; i < length; i++) {
//							if (substitutedArguments[i] != originalVariables[i]) break identicalVariables;
//						}
//						return originalParameterizedType.type;
//					}
					return originalParameterizedType.environment.createParameterizedType(
							originalParameterizedType.genericType(), substitutedArguments, substitutedEnclosing);
				}
				break;
				
			case Binding.ARRAY_TYPE:
				ArrayBinding originalArrayType = (ArrayBinding) originalType;
				TypeBinding originalLeafComponentType = originalArrayType.leafComponentType;
				TypeBinding substitute = substitute(substitution, originalLeafComponentType); // substitute could itself be array type
				if (substitute != originalLeafComponentType) {
					return originalArrayType.environment.createArrayType(substitute.leafComponentType(), substitute.dimensions() + originalType.dimensions());
				}
				break;

			case Binding.WILDCARD_TYPE:
		        WildcardBinding wildcard = (WildcardBinding) originalType;
		        if (wildcard.boundKind != Wildcard.UNBOUND) {
			        TypeBinding originalBound = wildcard.bound;
			        TypeBinding substitutedBound = substitute(substitution, originalBound);
			        TypeBinding[] originalOtherBounds = wildcard.otherBounds;
			        TypeBinding[] substitutedOtherBounds = substitute(substitution, originalOtherBounds);
			        if (substitutedBound != originalBound || originalOtherBounds != substitutedOtherBounds) {
		        		return wildcard.environment.createWildcard(wildcard.genericType, wildcard.rank, substitutedBound, substitutedOtherBounds, wildcard.boundKind);
			        }
		        }
				break;

			case Binding.TYPE:
				if (!originalType.isMemberType()) break;
				ReferenceBinding originalReferenceType = (ReferenceBinding) originalType;
				originalEnclosing = originalType.enclosingType();
				substitutedEnclosing = originalEnclosing;
				if (originalEnclosing != null) {
					substitutedEnclosing = (ReferenceBinding) substitute(substitution, originalEnclosing);
				}
				
			    // treat as if parameterized with its type variables (non generic type gets 'null' arguments)
				if (substitutedEnclosing != originalEnclosing) {
					return substitution.isRawSubstitution() 
						? substitution.environment().createRawType(originalReferenceType, substitutedEnclosing)
						:  substitution.environment().createParameterizedType(originalReferenceType, null, substitutedEnclosing);
				}
				break;
			case Binding.GENERIC_TYPE:
				originalReferenceType = (ReferenceBinding) originalType;
				originalEnclosing = originalType.enclosingType();
				substitutedEnclosing = originalEnclosing;
				if (originalEnclosing != null) {
					substitutedEnclosing = (ReferenceBinding) substitute(substitution, originalEnclosing);
				}
				
				if (substitution.isRawSubstitution()) {
					return substitution.environment().createRawType(originalReferenceType, substitutedEnclosing);
				}
			    // treat as if parameterized with its type variables (non generic type gets 'null' arguments)
				originalArguments = originalReferenceType.typeVariables();
				substitutedArguments = substitute(substitution, originalArguments);
//				if (substitutedArguments != originalArguments || substitutedEnclosing != originalEnclosing) {
				return substitution.environment().createParameterizedType(originalReferenceType, substitutedArguments, substitutedEnclosing);
//				}
//				break;
		}
		return originalType;
	}	

	/**
	 * Returns an array of types, where original types got substituted given a substitution.
	 * Only allocate an array if anything is different.
	 */
	public static TypeBinding[] substitute(Substitution substitution, TypeBinding[] originalTypes) {
		if (originalTypes == null) return null;
	    TypeBinding[] substitutedTypes = originalTypes;
	    for (int i = 0, length = originalTypes.length; i < length; i++) {
	        TypeBinding originalType = originalTypes[i];
	        TypeBinding substitutedParameter = substitute(substitution, originalType);
	        if (substitutedParameter != originalType) {
	            if (substitutedTypes == originalTypes) {
	                System.arraycopy(originalTypes, 0, substitutedTypes = new TypeBinding[length], 0, i);
	            }
	            substitutedTypes[i] = substitutedParameter;
	        } else if (substitutedTypes != originalTypes) {
	            substitutedTypes[i] = originalType;
	        }
	    }
	    return substitutedTypes;
	}

	protected Scope(int kind, Scope parent) {
		this.kind = kind;
		this.parent = parent;
	}	

	/*
	 * Boxing primitive
	 */
	public TypeBinding boxing(TypeBinding type) {
		if (type.isBaseType())
			return environment().computeBoxingType(type);
		return type;
	}

	public final ClassScope classScope() {
		Scope scope = this;
		do {
			if (scope instanceof ClassScope)
				return (ClassScope) scope;
			scope = scope.parent;
		} while (scope != null);
		return null;
	}
	
	public final CompilationUnitScope compilationUnitScope() {
		Scope lastScope = null;
		Scope scope = this;
		do {
			lastScope = scope;
			scope = scope.parent;
		} while (scope != null);
		return (CompilationUnitScope) lastScope;
	}
	
	/**
	 * Finds the most specific compiler options
	 */
	public final CompilerOptions compilerOptions() {

		return compilationUnitScope().environment.globalOptions;
	}

	/**
	 * Internal use only
	 * Given a method, returns null if arguments cannot be converted to parameters.
	 * Will answer a subsituted method in case the method was generic and type inference got triggered;
	 * in case the method was originally compatible, then simply answer it back.
	 */
	protected final MethodBinding computeCompatibleMethod(MethodBinding method, TypeBinding[] arguments, InvocationSite invocationSite) {

		TypeBinding[] genericTypeArguments = invocationSite.genericTypeArguments();
		TypeBinding[] parameters = method.parameters;
		TypeVariableBinding[] typeVariables = method.typeVariables;
		if (parameters == arguments
			&& (method.returnType.tagBits & TagBits.HasTypeVariable) == 0 
			&& genericTypeArguments == null
			&& typeVariables == Binding.NO_TYPE_VARIABLES)
				return method;

		int argLength = arguments.length;
		int paramLength = parameters.length;
		boolean isVarArgs = method.isVarargs();
		if (argLength != paramLength)
			if (!isVarArgs || argLength < paramLength - 1)
				return null; // incompatible

		if (typeVariables != Binding.NO_TYPE_VARIABLES) { // generic method
			TypeBinding[] newArgs = null;
			for (int i = 0; i < argLength; i++) {
				TypeBinding param = i < paramLength ? parameters[i] : parameters[paramLength - 1];
				if (arguments[i].isBaseType() != param.isBaseType()) {
					if (newArgs == null) {
						newArgs = new TypeBinding[argLength];
						System.arraycopy(arguments, 0, newArgs, 0, argLength);
					}
					newArgs[i] = environment().computeBoxingType(arguments[i]);
				}	
			}
			if (newArgs != null)
				arguments = newArgs;
			method = ParameterizedGenericMethodBinding.computeCompatibleMethod(method, arguments, this, invocationSite);
			if (method == null) return null; // incompatible
			if (!method.isValidBinding()) return method; // bound check issue is taking precedence
			parameters = method.parameters; // reacquire them after type inference has performed
		} else if (genericTypeArguments != null) {
			if (method instanceof ParameterizedGenericMethodBinding) {
				if (!((ParameterizedGenericMethodBinding) method).wasInferred) {
					// attempt to invoke generic method of raw type with type hints <String>foo()
					return new ProblemMethodBinding(method, method.selector, genericTypeArguments, ProblemReasons.TypeArgumentsForRawGenericMethod);
				}
			} else {
				return new ProblemMethodBinding(method, method.selector, genericTypeArguments, ProblemReasons.TypeParameterArityMismatch);
			}
		}

		if (parameterCompatibilityLevel(method, arguments) > NOT_COMPATIBLE)
			return method;
		if (genericTypeArguments != null)
			return new ProblemMethodBinding(method, method.selector, arguments, ProblemReasons.ParameterizedMethodTypeMismatch);
		return null; // incompatible
	}
	
	protected boolean connectTypeVariables(TypeParameter[] typeParameters, boolean checkForErasedCandidateCollisions) {
		if (typeParameters == null || compilerOptions().sourceLevel < ClassFileConstants.JDK1_5) return true;
		boolean noProblems = true;
		Map invocations = new HashMap(2);
		nextVariable : for (int i = 0, paramLength = typeParameters.length; i < paramLength; i++) {
			TypeParameter typeParameter = typeParameters[i];
			TypeVariableBinding typeVariable = typeParameter.binding;
			if (typeVariable == null) return false;

			typeVariable.superclass = getJavaLangObject();
			typeVariable.superInterfaces = Binding.NO_SUPERINTERFACES;
			// set firstBound to the binding of the first explicit bound in parameter declaration
			typeVariable.firstBound = null; // first bound used to compute erasure

			TypeReference typeRef = typeParameter.type;
			if (typeRef == null)
				continue nextVariable;
			TypeBinding superType = this.kind == METHOD_SCOPE
				? typeRef.resolveType((BlockScope)this, false/*no bound check*/)
				: typeRef.resolveType((ClassScope)this);
			if (superType == null) {
				typeVariable.tagBits |= TagBits.HierarchyHasProblems;
				noProblems = false;
				continue nextVariable;
			}
			typeRef.resolvedType = superType; // hold onto the problem type
			if (superType.isArrayType()) {
				problemReporter().boundCannotBeArray(typeRef, superType);
				continue nextVariable;
			}
			boolean isTypeVariableFirstBound =  superType.isTypeVariable();
			if (isTypeVariableFirstBound) {
				TypeVariableBinding varSuperType = (TypeVariableBinding) superType;
				if (varSuperType.rank >= typeVariable.rank && varSuperType.declaringElement == typeVariable.declaringElement) {
					problemReporter().forwardTypeVariableReference(typeParameter, varSuperType);
					typeVariable.tagBits |= TagBits.HierarchyHasProblems;
					noProblems = false;
					continue nextVariable;
				}
			}
			ReferenceBinding superRefType = (ReferenceBinding) superType;
			if (superRefType.isFinal())
				problemReporter().finalVariableBound(typeVariable, typeRef);
			if (!superType.isInterface()) {
				typeVariable.superclass = superRefType;
			} else {
				typeVariable.superInterfaces = new ReferenceBinding[] {superRefType};
			}
			typeVariable.firstBound = superRefType; // first bound used to compute erasure
			TypeReference[] boundRefs = typeParameter.bounds;
			if (boundRefs != null) {
				for (int j = 0, boundLength = boundRefs.length; j < boundLength; j++) {
					typeRef = boundRefs[j];
					superType = this.kind == METHOD_SCOPE
						? typeRef.resolveType((BlockScope)this, false)
						: typeRef.resolveType((ClassScope)this);
					if (superType == null) {
						typeVariable.tagBits |= TagBits.HierarchyHasProblems;
						noProblems = false;
						continue nextVariable;
					}
					typeRef.resolvedType = superType; // hold onto the problem type
					if (isTypeVariableFirstBound && j == 0) {
						problemReporter().noAdditionalBoundAfterTypeVariable(typeRef);
					}
					if (superType.isArrayType()) {
						problemReporter().boundCannotBeArray(typeRef, superType);
						continue nextVariable;
					}
					superRefType = (ReferenceBinding) superType;
					if (!superType.isInterface()) {
						problemReporter().boundMustBeAnInterface(typeRef, superType);
						typeVariable.tagBits |= TagBits.HierarchyHasProblems;
						noProblems = false;
						continue nextVariable;
					}
					// check against superclass
					if (checkForErasedCandidateCollisions && typeVariable.firstBound == typeVariable.superclass) {
						if (hasErasedCandidatesCollisions(superType, typeVariable.superclass, invocations, typeVariable, typeRef)) {
							noProblems = false;
							continue nextVariable;
						}
					}
					// check against superinterfaces
					for (int index = typeVariable.superInterfaces.length; --index >= 0;) {
						ReferenceBinding previousInterface = typeVariable.superInterfaces[index];
						if (previousInterface == superRefType) {
							problemReporter().duplicateBounds(typeRef, superType);
							typeVariable.tagBits |= TagBits.HierarchyHasProblems;
							noProblems = false;
							continue nextVariable;
						}
						if (checkForErasedCandidateCollisions) {
							if (hasErasedCandidatesCollisions(superType, previousInterface, invocations, typeVariable, typeRef)) {
								noProblems = false;
								continue nextVariable;
							}
						}
					}
					int size = typeVariable.superInterfaces.length;
					System.arraycopy(typeVariable.superInterfaces, 0, typeVariable.superInterfaces = new ReferenceBinding[size + 1], 0, size);
					typeVariable.superInterfaces[size] = superRefType;
				}
			}
		}
		return noProblems;
	}

	public ArrayBinding createArrayType(TypeBinding type, int dimension) {
		if (type.isValidBinding())
			return environment().createArrayType(type, dimension);
		// do not cache obvious invalid types
		return new ArrayBinding(type, dimension, environment());
	}

	public TypeVariableBinding[] createTypeVariables(TypeParameter[] typeParameters, Binding declaringElement) {
		// do not construct type variables if source < 1.5
		if (typeParameters == null || compilerOptions().sourceLevel < ClassFileConstants.JDK1_5)
			return Binding.NO_TYPE_VARIABLES;

		PackageBinding unitPackage = compilationUnitScope().fPackage;
		int length = typeParameters.length;
		TypeVariableBinding[] typeVariableBindings = new TypeVariableBinding[length];
		int count = 0;
		for (int i = 0; i < length; i++) {
			TypeParameter typeParameter = typeParameters[i];
			TypeVariableBinding parameterBinding = new TypeVariableBinding(typeParameter.name, declaringElement, i);
			parameterBinding.fPackage = unitPackage;
			typeParameter.binding = parameterBinding;

			// detect duplicates, but keep each variable to reduce secondary errors with instantiating this generic type (assume number of variables is correct)
			for (int j = 0; j < count; j++) {
				TypeVariableBinding knownVar = typeVariableBindings[j];
				if (CharOperation.equals(knownVar.sourceName, typeParameter.name))
					problemReporter().duplicateTypeParameterInType(typeParameter);
			}
			typeVariableBindings[count++] = parameterBinding;
//				TODO should offer warnings to inform about hiding declaring, enclosing or member types				
//				ReferenceBinding type = sourceType;
//				// check that the member does not conflict with an enclosing type
//				do {
//					if (CharOperation.equals(type.sourceName, memberContext.name)) {
//						problemReporter().hidingEnclosingType(memberContext);
//						continue nextParameter;
//					}
//					type = type.enclosingType();
//				} while (type != null);
//				// check that the member type does not conflict with another sibling member type
//				for (int j = 0; j < i; j++) {
//					if (CharOperation.equals(referenceContext.memberTypes[j].name, memberContext.name)) {
//						problemReporter().duplicateNestedType(memberContext);
//						continue nextParameter;
//					}
//				}
		}
		if (count != length)
			System.arraycopy(typeVariableBindings, 0, typeVariableBindings = new TypeVariableBinding[count], 0, count);
		return typeVariableBindings;
	}

	public final ClassScope enclosingClassScope() {
		Scope scope = this;
		while ((scope = scope.parent) != null) {
			if (scope instanceof ClassScope) return (ClassScope) scope;
		}
		return null; // may answer null if no type around
	}
	
	public final MethodScope enclosingMethodScope() {
		Scope scope = this;
		while ((scope = scope.parent) != null) {
			if (scope instanceof MethodScope) return (MethodScope) scope;
		}
		return null; // may answer null if no method around
	}

	/* Answer the scope receiver type (could be parameterized)
	*/
	public final ReferenceBinding enclosingReceiverType() {
		Scope scope = this;
		do {
			if (scope instanceof ClassScope) {
				return environment().convertToParameterizedType(((ClassScope) scope).referenceContext.binding);
			}
			scope = scope.parent;
		} while (scope != null);
		return null;
	}
	/**
	 * Returns the immediately enclosing reference context, starting from current scope parent.
	 * If starting on a class, it will skip current class. If starting on unitScope, returns null.
	 */
	public ReferenceContext enclosingReferenceContext() {
		Scope current = this;
		while ((current = current.parent) != null) {
			switch(current.kind) {
				case METHOD_SCOPE :
					return ((MethodScope) current).referenceContext;
				case CLASS_SCOPE :
					return ((ClassScope) current).referenceContext;
				case COMPILATION_UNIT_SCOPE :
					return ((CompilationUnitScope) current).referenceContext;
			}
		}
		return null;
	}

	/* Answer the scope enclosing source type (could be generic)
	*/
	public final SourceTypeBinding enclosingSourceType() {
		Scope scope = this;
		do {
			if (scope instanceof ClassScope)
				return ((ClassScope) scope).referenceContext.binding;
			scope = scope.parent;
		} while (scope != null);
		return null;
	}

	public final LookupEnvironment environment() {
		Scope scope, unitScope = this;
		while ((scope = unitScope.parent) != null)
			unitScope = scope;
		return ((CompilationUnitScope) unitScope).environment;
	}

	// abstract method lookup lookup (since maybe missing default abstract methods)
	protected MethodBinding findDefaultAbstractMethod(
		ReferenceBinding receiverType, 
		char[] selector,
		TypeBinding[] argumentTypes,
		InvocationSite invocationSite,
		ReferenceBinding classHierarchyStart,
		ObjectVector found,
		MethodBinding concreteMatch) {

		int startFoundSize = found.size;
		ReferenceBinding currentType = classHierarchyStart;
		while (currentType != null) {
			findMethodInSuperInterfaces(currentType, selector, found);
			currentType = currentType.superclass();
		}
		MethodBinding[] candidates = null;
		int candidatesCount = 0;
		MethodBinding problemMethod = null;
		int foundSize = found.size;
		if (foundSize > startFoundSize) {
			// argument type compatibility check
			for (int i = startFoundSize; i < foundSize; i++) {
				MethodBinding methodBinding = (MethodBinding) found.elementAt(i);
				MethodBinding compatibleMethod = computeCompatibleMethod(methodBinding, argumentTypes, invocationSite);
				if (compatibleMethod != null) {
					if (compatibleMethod.isValidBinding()) {
						if (candidatesCount == 0) {
							candidates = new MethodBinding[foundSize - startFoundSize + 1];
							if (concreteMatch != null)
								candidates[candidatesCount++] = concreteMatch;
						}
						candidates[candidatesCount++] = compatibleMethod;
					} else if (problemMethod == null) {
						problemMethod = compatibleMethod;
					}
				}
			}
		}

		if (candidatesCount < 2) {
			if (concreteMatch == null) {
				if (candidatesCount == 0)
					return problemMethod; // can be null
				concreteMatch = candidates[0];
			}
			compilationUnitScope().recordTypeReferences(concreteMatch.thrownExceptions);
			return concreteMatch;
		}
		// no need to check for visibility - interface methods are public
		if (compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4)
			return mostSpecificMethodBinding(candidates, candidatesCount, argumentTypes, invocationSite, receiverType);
		return mostSpecificInterfaceMethodBinding(candidates, candidatesCount, invocationSite);
	}

	// Internal use only
	public ReferenceBinding findDirectMemberType(char[] typeName, ReferenceBinding enclosingType) {
		if ((enclosingType.tagBits & TagBits.HasNoMemberTypes) != 0)
			return null; // know it has no member types (nor inherited member types)

		ReferenceBinding enclosingReceiverType = enclosingReceiverType();
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordReference(enclosingType, typeName);
		ReferenceBinding memberType = enclosingType.getMemberType(typeName);
		if (memberType != null) {
			unitScope.recordTypeReference(memberType);
			if (enclosingReceiverType == null
				? memberType.canBeSeenBy(getCurrentPackage())
				: memberType.canBeSeenBy(enclosingType, enclosingReceiverType))
					return memberType;
			return new ProblemReferenceBinding(typeName, memberType, ProblemReasons.NotVisible);
		}
		return null;
	}

	// Internal use only
	public MethodBinding findExactMethod(
		ReferenceBinding receiverType,
		char[] selector,
		TypeBinding[] argumentTypes,
		InvocationSite invocationSite) {

		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordTypeReferences(argumentTypes);
		MethodBinding exactMethod = receiverType.getExactMethod(selector, argumentTypes, unitScope);
		if (exactMethod != null && exactMethod.typeVariables == Binding.NO_TYPE_VARIABLES && !exactMethod.isBridge()) {
			// must find both methods for this case: <S extends A> void foo() {}  and  <N extends B> N foo() { return null; }
			// or find an inherited method when the exact match is to a bridge method
			unitScope.recordTypeReferences(exactMethod.thrownExceptions);
			// special treatment for Object.getClass() in 1.5 mode (substitute parameterized return type)
			if (receiverType.isInterface() || exactMethod.canBeSeenBy(receiverType, invocationSite, this)) {
				if (receiverType.id != T_JavaLangObject
					&& argumentTypes == Binding.NO_PARAMETERS
				    && CharOperation.equals(selector, GETCLASS)
				    && exactMethod.returnType.isParameterizedType()/*1.5*/) {
						return ParameterizedMethodBinding.instantiateGetClass(receiverType, exactMethod, this);
			    }
				// targeting a generic method could find an exact match with variable return type
				if (invocationSite.genericTypeArguments() != null) {
					exactMethod = computeCompatibleMethod(exactMethod, argumentTypes, invocationSite);
				}
				return exactMethod;
			}
		}
		return null;
	}

	// Internal use only
	/*	Answer the field binding that corresponds to fieldName.
		Start the lookup at the receiverType.
		InvocationSite implements
			isSuperAccess(); this is used to determine if the discovered field is visible.
		Only fields defined by the receiverType or its supertypes are answered;
		a field of an enclosing type will not be found using this API.
	
		If no visible field is discovered, null is answered.
	*/
	public FieldBinding findField(TypeBinding receiverType, char[] fieldName, InvocationSite invocationSite, boolean needResolve) {

		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordTypeReference(receiverType);
		
		checkArrayField: {
			TypeBinding leafType;
			switch (receiverType.kind()) {
				case Binding.BASE_TYPE :
					return null;
				case Binding.WILDCARD_TYPE :
				case Binding.TYPE_PARAMETER : // capture
					TypeBinding receiverErasure = receiverType.erasure();
					if (!receiverErasure.isArrayType())
						break checkArrayField;
					leafType = receiverErasure.leafComponentType();
					break;
				case Binding.ARRAY_TYPE :
					leafType = receiverType.leafComponentType();
					break;
				default:
					break checkArrayField;
			}
			if (leafType instanceof ReferenceBinding)
				if (!((ReferenceBinding) leafType).canBeSeenBy(this))
					return new ProblemFieldBinding((ReferenceBinding)leafType, fieldName, ProblemReasons.ReceiverTypeNotVisible);
			if (CharOperation.equals(fieldName, LENGTH))
				return ArrayBinding.ArrayLength;
			return null;
		}

		ReferenceBinding currentType = (ReferenceBinding) receiverType;
		if (!currentType.canBeSeenBy(this))
			return new ProblemFieldBinding(currentType, fieldName, ProblemReasons.ReceiverTypeNotVisible);

		FieldBinding field = currentType.getField(fieldName, needResolve);
		if (field != null) {
			if (invocationSite == null
				? field.canBeSeenBy(getCurrentPackage())
				: field.canBeSeenBy(currentType, invocationSite, this))
					return field;
			return new ProblemFieldBinding(field /* closest match*/, field.declaringClass, fieldName, ProblemReasons.NotVisible);
		}
		// collect all superinterfaces of receiverType until the field is found in a supertype
		ReferenceBinding[] interfacesToVisit = null;
		int nextPosition = 0;
		FieldBinding visibleField = null;
		boolean keepLooking = true;
		FieldBinding notVisibleField = null;
		// we could hold onto the not visible field for extra error reporting
		while (keepLooking) {
			ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
			if (itsInterfaces == null) { // needed for statically imported types which don't know their hierarchy yet
				((SourceTypeBinding) currentType).scope.connectTypeHierarchy();
				itsInterfaces = currentType.superInterfaces();
			}
			if (itsInterfaces != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
				if (interfacesToVisit == null) {
					interfacesToVisit = itsInterfaces;
					nextPosition = interfacesToVisit.length;
				} else {
					int itsLength = itsInterfaces.length;
					if (nextPosition + itsLength >= interfacesToVisit.length)
						System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition);
					nextInterface : for (int a = 0; a < itsLength; a++) {
						ReferenceBinding next = itsInterfaces[a];
						for (int b = 0; b < nextPosition; b++)
							if (next == interfacesToVisit[b]) continue nextInterface;
						interfacesToVisit[nextPosition++] = next;
					}
				}
			}
			if ((currentType = currentType.superclass()) == null)
				break;

			unitScope.recordTypeReference(currentType);
			if ((field = currentType.getField(fieldName, needResolve)) != null) {
				keepLooking = false;
				if (field.canBeSeenBy(receiverType, invocationSite, this)) {
					if (visibleField == null)
						visibleField = field;
					else
						return new ProblemFieldBinding(visibleField /* closest match*/, visibleField.declaringClass, fieldName, ProblemReasons.Ambiguous);
				} else {
					if (notVisibleField == null)
						notVisibleField = field;
				}
			}
		}

		// walk all visible interfaces to find ambiguous references
		if (interfacesToVisit != null) {
			ProblemFieldBinding ambiguous = null;
			done : for (int i = 0; i < nextPosition; i++) {
				ReferenceBinding anInterface = interfacesToVisit[i];
				unitScope.recordTypeReference(anInterface);
				if ((field = anInterface.getField(fieldName, true /*resolve*/)) != null) {
					if (visibleField == null) {
						visibleField = field;
					} else {
						ambiguous = new ProblemFieldBinding(visibleField /* closest match*/, visibleField.declaringClass, fieldName, ProblemReasons.Ambiguous);
						break done;
					}
				} else {
					ReferenceBinding[] itsInterfaces = anInterface.superInterfaces();
					if (itsInterfaces != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
						int itsLength = itsInterfaces.length;
						if (nextPosition + itsLength >= interfacesToVisit.length)
							System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition);
						nextInterface : for (int a = 0; a < itsLength; a++) {
							ReferenceBinding next = itsInterfaces[a];
							for (int b = 0; b < nextPosition; b++)
								if (next == interfacesToVisit[b]) continue nextInterface;
							interfacesToVisit[nextPosition++] = next;
						}
					}
				}
			}
			if (ambiguous != null)
				return ambiguous;
		}

		if (visibleField != null)
			return visibleField;
		if (notVisibleField != null) {
			return new ProblemFieldBinding(notVisibleField, currentType, fieldName, ProblemReasons.NotVisible);
		}
		return null;
	}
	
	// Internal use only
	public ReferenceBinding findMemberType(char[] typeName, ReferenceBinding enclosingType) {
		if ((enclosingType.tagBits & TagBits.HasNoMemberTypes) != 0)
			return null; // know it has no member types (nor inherited member types)

		ReferenceBinding enclosingSourceType = enclosingSourceType();
		PackageBinding currentPackage = getCurrentPackage();
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordReference(enclosingType, typeName);
		ReferenceBinding memberType = enclosingType.getMemberType(typeName);
		if (memberType != null) {
			unitScope.recordTypeReference(memberType);
			if (enclosingSourceType == null
				? memberType.canBeSeenBy(currentPackage)
				: memberType.canBeSeenBy(enclosingType, enclosingSourceType))
					return memberType;
			return new ProblemReferenceBinding(typeName, memberType, ProblemReasons.NotVisible);
		}

		// collect all superinterfaces of receiverType until the memberType is found in a supertype
		ReferenceBinding currentType = enclosingType;
		ReferenceBinding[] interfacesToVisit = null;
		int nextPosition = 0;
		ReferenceBinding visibleMemberType = null;
		boolean keepLooking = true;
		ReferenceBinding notVisible = null;
		// we could hold onto the not visible field for extra error reporting
		while (keepLooking) {
			ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
			if (itsInterfaces == null) { // needed for statically imported types which don't know their hierarchy yet
				ReferenceBinding sourceType = currentType.isParameterizedType()
					? ((ParameterizedTypeBinding) currentType).genericType()
					: currentType;
				if (sourceType.isHierarchyBeingConnected())
					return null; // looking for an undefined member type in its own superclass ref
				((SourceTypeBinding) sourceType).scope.connectTypeHierarchy();
				itsInterfaces = currentType.superInterfaces();
			}
			if (itsInterfaces != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
				if (interfacesToVisit == null) {
					interfacesToVisit = itsInterfaces;
					nextPosition = interfacesToVisit.length;
				} else {
					int itsLength = itsInterfaces.length;
					if (nextPosition + itsLength >= interfacesToVisit.length)
						System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition);
					nextInterface : for (int a = 0; a < itsLength; a++) {
						ReferenceBinding next = itsInterfaces[a];
						for (int b = 0; b < nextPosition; b++)
							if (next == interfacesToVisit[b]) continue nextInterface;
						interfacesToVisit[nextPosition++] = next;
					}
				}
			}
			if ((currentType = currentType.superclass()) == null)
				break;

			unitScope.recordReference(currentType, typeName);
			if ((memberType = currentType.getMemberType(typeName)) != null) {
				unitScope.recordTypeReference(memberType);
				keepLooking = false;
				if (enclosingSourceType == null
					? memberType.canBeSeenBy(currentPackage)
					: memberType.canBeSeenBy(enclosingType, enclosingSourceType)) {
						if (visibleMemberType == null)
							visibleMemberType = memberType;
						else
							return new ProblemReferenceBinding(typeName, visibleMemberType, ProblemReasons.Ambiguous);
				} else {
					notVisible = memberType;
				}
			}
		}
		// walk all visible interfaces to find ambiguous references
		if (interfacesToVisit != null) {
			ProblemReferenceBinding ambiguous = null;
			done : for (int i = 0; i < nextPosition; i++) {
				ReferenceBinding anInterface = interfacesToVisit[i];
				unitScope.recordReference(anInterface, typeName);
				if ((memberType = anInterface.getMemberType(typeName)) != null) {
					unitScope.recordTypeReference(memberType);
					if (visibleMemberType == null) {
						visibleMemberType = memberType;
					} else {
						ambiguous = new ProblemReferenceBinding(typeName, visibleMemberType, ProblemReasons.Ambiguous);
						break done;
					}
				} else {
					ReferenceBinding[] itsInterfaces = anInterface.superInterfaces();
					if (itsInterfaces != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
						int itsLength = itsInterfaces.length;
						if (nextPosition + itsLength >= interfacesToVisit.length)
							System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition);
						nextInterface : for (int a = 0; a < itsLength; a++) {
							ReferenceBinding next = itsInterfaces[a];
							for (int b = 0; b < nextPosition; b++)
								if (next == interfacesToVisit[b]) continue nextInterface;
							interfacesToVisit[nextPosition++] = next;
						}
					}
				}
			}
			if (ambiguous != null)
				return ambiguous;
		}
		if (visibleMemberType != null)
			return visibleMemberType;
		if (notVisible != null)
			return new ProblemReferenceBinding(typeName, notVisible, ProblemReasons.NotVisible);
		return null;
	}

	// Internal use only - use findMethod()
	public MethodBinding findMethod(ReferenceBinding receiverType, char[] selector, TypeBinding[] argumentTypes, InvocationSite invocationSite) {
		ReferenceBinding currentType = receiverType;
		boolean receiverTypeIsInterface = receiverType.isInterface();
		ObjectVector found = new ObjectVector(3);
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordTypeReferences(argumentTypes);

		if (receiverTypeIsInterface) {
			unitScope.recordTypeReference(receiverType);
			MethodBinding[] receiverMethods = receiverType.getMethods(selector);
			if (receiverMethods.length > 0)
				found.addAll(receiverMethods);
			findMethodInSuperInterfaces(receiverType, selector, found);
			currentType = getJavaLangObject();
		}

		// superclass lookup
		long complianceLevel = compilerOptions().complianceLevel;
		boolean isCompliant14 = complianceLevel >= ClassFileConstants.JDK1_4;
		boolean isCompliant15 = complianceLevel >= ClassFileConstants.JDK1_5;
		ReferenceBinding classHierarchyStart = currentType;
		while (currentType != null) {
			unitScope.recordTypeReference(currentType);
			MethodBinding[] currentMethods = currentType.getMethods(selector);
			int currentLength = currentMethods.length;
			if (currentLength > 0) {
				if (isCompliant14 && (receiverTypeIsInterface || found.size > 0)) {
					nextMethod: for (int i = 0, l = currentLength; i < l; i++) { // currentLength can be modified inside the loop
						MethodBinding currentMethod = currentMethods[i];
						if (currentMethod == null) continue nextMethod;
						if (receiverTypeIsInterface && !currentMethod.isPublic()) { // only public methods from Object are visible to interface receiverTypes
							currentLength--;
							currentMethods[i] = null;
							continue nextMethod;
						}

						// if 1.4 compliant, must filter out redundant protected methods from superclasses
						// protected method need to be checked only - default access is already dealt with in #canBeSeen implementation
						// when checking that p.C -> q.B -> p.A cannot see default access members from A through B.
						// if ((currentMethod.modifiers & AccProtected) == 0) continue nextMethod;
						// BUT we can also ignore any overridden method since we already know the better match (fixes 80028)
						for (int j = 0, max = found.size; j < max; j++) {
							MethodBinding matchingMethod = (MethodBinding) found.elementAt(j);
							if (currentMethod.areParametersEqual(matchingMethod)) {
								if (isCompliant15) {
									if (matchingMethod.typeVariables != Binding.NO_TYPE_VARIABLES && invocationSite.genericTypeArguments() == null)
										continue nextMethod; // keep inherited substituted methods to detect anonymous errors
									if (matchingMethod.hasSubstitutedParameters() && !currentMethod.original().areParametersEqual(matchingMethod.original()))
										continue nextMethod; // keep inherited substituted methods to detect anonymous errors
									if (matchingMethod.isBridge() && !currentMethod.isBridge())
										continue nextMethod; // keep inherited methods to find concrete method over a bridge method
								}
								currentLength--;
								currentMethods[i] = null;
								continue nextMethod;
							}
						}
					}
				}

				if (currentLength > 0) {
					// append currentMethods, filtering out null entries
					if (currentMethods.length == currentLength) {
						found.addAll(currentMethods);
					} else {
						for (int i = 0, max = currentMethods.length; i < max; i++) {
							MethodBinding currentMethod = currentMethods[i];
							if (currentMethod != null)
								found.add(currentMethod);
						}
					}
				}
			}
			currentType = currentType.superclass();
		}

		// if found several candidates, then eliminate those not matching argument types
		int foundSize = found.size;
		MethodBinding[] candidates = null;
		int candidatesCount = 0;
		MethodBinding problemMethod = null;
		boolean searchForDefaultAbstractMethod = isCompliant14 && ! receiverTypeIsInterface && (receiverType.isAbstract() || receiverType.isTypeVariable());
		if (foundSize > 0) {
			// argument type compatibility check
			for (int i = 0; i < foundSize; i++) {
				MethodBinding methodBinding = (MethodBinding) found.elementAt(i);
				MethodBinding compatibleMethod = computeCompatibleMethod(methodBinding, argumentTypes, invocationSite);
				if (compatibleMethod != null) {
					if (compatibleMethod.isValidBinding()) {
						if (foundSize == 1 && compatibleMethod.canBeSeenBy(receiverType, invocationSite, this)) {
							// return the single visible match now
							if (searchForDefaultAbstractMethod)
								return findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, compatibleMethod);
							unitScope.recordTypeReferences(compatibleMethod.thrownExceptions);
							return compatibleMethod;
						}
						if (candidatesCount == 0)
							candidates = new MethodBinding[foundSize];
						candidates[candidatesCount++] = compatibleMethod;
					} else if (problemMethod == null) {
						problemMethod = compatibleMethod;
					}
				}
			}
		}

		// no match was found
		if (candidatesCount == 0) {
			// abstract classes may get a match in interfaces; for non abstract
			// classes, reduces secondary errors since missing interface method 
			// error is already reported
			MethodBinding interfaceMethod =
				findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, null);
			if (interfaceMethod != null) return interfaceMethod;
			if (found.size == 0) return null;
			if (problemMethod != null) return problemMethod;

			// still no match; try to find a close match when the parameter 
			// order is wrong or missing some parameters

			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=69471
			// bad guesses are foo(), when argument types have been supplied
			// and foo(X, Y), when the argument types are (int, float, Y)
			// so answer the method with the most argType matches and least parameter type mismatches
			int bestArgMatches = -1;
			MethodBinding bestGuess = (MethodBinding) found.elementAt(0); // if no good match so just use the first one found
			int argLength = argumentTypes.length;
			foundSize = found.size;
			nextMethod : for (int i = 0; i < foundSize; i++) {
				MethodBinding methodBinding = (MethodBinding) found.elementAt(i);
				TypeBinding[] params = methodBinding.parameters;
				int paramLength = params.length;
				int argMatches = 0;
				next: for (int a = 0; a < argLength; a++) {
					TypeBinding arg = argumentTypes[a];
					for (int p = a == 0 ? 0 : a - 1; p < paramLength && p < a + 1; p++) { // look one slot before & after to see if the type matches
						if (params[p] == arg) {
							argMatches++;
							continue next;
						}
					}
				}
				if (argMatches < bestArgMatches)
					continue nextMethod;
				if (argMatches == bestArgMatches) {
					int diff1 = paramLength < argLength ? 2 * (argLength - paramLength) : paramLength - argLength;
					int bestLength = bestGuess.parameters.length;
					int diff2 = bestLength < argLength ? 2 * (argLength - bestLength) : bestLength - argLength;
					if (diff1 >= diff2)
						continue nextMethod;
				}
				bestArgMatches = argMatches;
				bestGuess = methodBinding;
			}
			return new ProblemMethodBinding(bestGuess, bestGuess.selector, argumentTypes, ProblemReasons.NotFound);
		}

		// tiebreak using visibility check
		int visiblesCount = 0;
		if (receiverTypeIsInterface) {
			if (candidatesCount == 1) {
				unitScope.recordTypeReferences(candidates[0].thrownExceptions);
				return candidates[0];
			}
			visiblesCount = candidatesCount;
		} else {
			for (int i = 0; i < candidatesCount; i++) {
				MethodBinding methodBinding = candidates[i];
				if (methodBinding.canBeSeenBy(receiverType, invocationSite, this)) {
					if (visiblesCount != i) {
						candidates[i] = null;
						candidates[visiblesCount] = methodBinding;
					}
					visiblesCount++;
				}
			}
			if (visiblesCount == 1) {
				if (searchForDefaultAbstractMethod)
					return findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, candidates[0]);
				unitScope.recordTypeReferences(candidates[0].thrownExceptions);
				return candidates[0];
			}
			if (visiblesCount == 0) {
				MethodBinding interfaceMethod =
					findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, null);
				if (interfaceMethod != null) return interfaceMethod;
				return new ProblemMethodBinding(candidates[0], candidates[0].selector, candidates[0].parameters, ProblemReasons.NotVisible);
			}
		}

		if (complianceLevel <= ClassFileConstants.JDK1_3) {
			ReferenceBinding declaringClass = candidates[0].declaringClass;
			return !declaringClass.isInterface()
				? mostSpecificClassMethodBinding(candidates, visiblesCount, invocationSite)
				: mostSpecificInterfaceMethodBinding(candidates, visiblesCount, invocationSite);
		}

		// check for duplicate parameterized methods
		if (compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) {
			for (int i = 0; i < visiblesCount; i++) {
				MethodBinding current = candidates[i];
				if (current instanceof ParameterizedGenericMethodBinding)
					current = ((ParameterizedGenericMethodBinding) current).originalMethod;
				if (current instanceof ParameterizedMethodBinding)
					for (int j = i + 1; j < visiblesCount; j++)
						if (current.declaringClass == candidates[j].declaringClass && current.areParametersEqual(candidates[j]))
							return new ProblemMethodBinding(candidates[i], candidates[i].selector, candidates[i].parameters, ProblemReasons.Ambiguous);
			}
		}

		MethodBinding mostSpecificMethod = mostSpecificMethodBinding(candidates, visiblesCount, argumentTypes, invocationSite, receiverType);
		if (searchForDefaultAbstractMethod) { // search interfaces for a better match
			if (mostSpecificMethod.isValidBinding())
				// see if there is a better match in the interfaces - see AutoBoxingTest 99, LookupTest#81
				return findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, mostSpecificMethod);
			// see if there is a match in the interfaces - see LookupTest#84
			MethodBinding interfaceMethod = findDefaultAbstractMethod(receiverType, selector, argumentTypes, invocationSite, classHierarchyStart, found, null);
			if (interfaceMethod != null && interfaceMethod.isValidBinding() /* else return the same error as before */)
				return interfaceMethod;
		}
		return mostSpecificMethod;
	}

	// Internal use only
	public MethodBinding findMethodForArray(
		ArrayBinding receiverType,
		char[] selector,
		TypeBinding[] argumentTypes,
		InvocationSite invocationSite) {

		TypeBinding leafType = receiverType.leafComponentType();
		if (leafType instanceof ReferenceBinding) {
			if (!((ReferenceBinding) leafType).canBeSeenBy(this))
				return new ProblemMethodBinding(selector, Binding.NO_PARAMETERS, (ReferenceBinding)leafType, ProblemReasons.ReceiverTypeNotVisible);
		}

		ReferenceBinding object = getJavaLangObject();
		MethodBinding methodBinding = object.getExactMethod(selector, argumentTypes, null);
		if (methodBinding != null) {
			// handle the method clone() specially... cannot be protected or throw exceptions
			if (argumentTypes == Binding.NO_PARAMETERS) {
			    switch (selector[0]) {
			        case 'c': 
			            if (CharOperation.equals(selector, CLONE)) {
							return new UpdatedMethodBinding(
								compilerOptions().targetJDK >= ClassFileConstants.JDK1_4 ? (TypeBinding)receiverType : (TypeBinding)object, // remember its array type for codegen purpose on target>=1.4.0
								(methodBinding.modifiers & ~ClassFileConstants.AccProtected) | ClassFileConstants.AccPublic,
								CLONE,
								methodBinding.returnType,
								argumentTypes,
								null,
								object);
			            }
			            break;
			        case 'g': 
			            if (CharOperation.equals(selector, GETCLASS) && methodBinding.returnType.isParameterizedType()/*1.5*/) {
							return ParameterizedMethodBinding.instantiateGetClass(receiverType, methodBinding, this);
			            }
			            break;
			    }
			}
			if (methodBinding.canBeSeenBy(receiverType, invocationSite, this))
				return methodBinding;
		}
		methodBinding = findMethod(object, selector, argumentTypes, invocationSite);
		if (methodBinding == null)
			return new ProblemMethodBinding(selector, argumentTypes, ProblemReasons.NotFound);
		return methodBinding;
	}

	protected void findMethodInSuperInterfaces(ReferenceBinding currentType, char[] selector, ObjectVector found) {
		ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
		if (itsInterfaces != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
			ReferenceBinding[] interfacesToVisit = itsInterfaces;
			int nextPosition = interfacesToVisit.length;
			for (int i = 0; i < nextPosition; i++) {
				currentType = interfacesToVisit[i];
				compilationUnitScope().recordTypeReference(currentType);
				MethodBinding[] currentMethods = currentType.getMethods(selector);
				if (currentMethods.length > 0) {
					int foundSize = found.size;
					if (foundSize > 0) {
						// its possible to walk the same superinterface from different classes in the hierarchy
						next : for (int c = 0, l = currentMethods.length; c < l; c++) {
							MethodBinding current = currentMethods[c];
							for (int f = 0; f < foundSize; f++)
								if (current == found.elementAt(f)) continue next;
							found.add(current);
						}
					} else {
						found.addAll(currentMethods);
					}
				}
				if ((itsInterfaces = currentType.superInterfaces()) != null && itsInterfaces != Binding.NO_SUPERINTERFACES) {
					int itsLength = itsInterfaces.length;
					if (nextPosition + itsLength >= interfacesToVisit.length)
						System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition);
					nextInterface : for (int a = 0; a < itsLength; a++) {
						ReferenceBinding next = itsInterfaces[a];
						for (int b = 0; b < nextPosition; b++)
							if (next == interfacesToVisit[b]) continue nextInterface;
						interfacesToVisit[nextPosition++] = next;
					}
				}
			}
		}
	}

	// Internal use only
	public ReferenceBinding findType(
		char[] typeName,
		PackageBinding declarationPackage,
		PackageBinding invocationPackage) {

		compilationUnitScope().recordReference(declarationPackage.compoundName, typeName);
		ReferenceBinding typeBinding = declarationPackage.getType(typeName);
		if (typeBinding == null)
			return null;

		if (typeBinding.isValidBinding()) {
			if (declarationPackage != invocationPackage && !typeBinding.canBeSeenBy(invocationPackage))
				return new ProblemReferenceBinding(typeName, typeBinding, ProblemReasons.NotVisible);
		}
		return typeBinding;
	}

	public LocalVariableBinding findVariable(char[] variable) {

		return null;
	}

	/* API
	 *	
	 *	Answer the binding that corresponds to the argument name.
	 *	flag is a mask of the following values VARIABLE (= FIELD or LOCAL), TYPE, PACKAGE.
	 *	Only bindings corresponding to the mask can be answered.
	 *
	 *	For example, getBinding("foo", VARIABLE, site) will answer
	 *	the binding for the field or local named "foo" (or an error binding if none exists).
	 *	If a type named "foo" exists, it will not be detected (and an error binding will be answered)
	 *
	 *	The VARIABLE mask has precedence over the TYPE mask.
	 *
	 *	If the VARIABLE mask is not set, neither fields nor locals will be looked for.
	 *
	 *	InvocationSite implements:
	 *		isSuperAccess(); this is used to determine if the discovered field is visible.
	 *
	 *	Limitations: cannot request FIELD independently of LOCAL, or vice versa
	 */
	public Binding getBinding(char[] name, int mask, InvocationSite invocationSite, boolean needResolve) {
		CompilationUnitScope unitScope = compilationUnitScope();
		LookupEnvironment env = unitScope.environment;
		try {
			env.missingClassFileLocation = invocationSite;
			Binding binding = null;
			FieldBinding problemField = null;
			if ((mask & Binding.VARIABLE) != 0) {
				boolean insideStaticContext = false;
				boolean insideConstructorCall = false;
				boolean insideTypeAnnotation = false;
				
				FieldBinding foundField = null;
				// can be a problem field which is answered if a valid field is not found
				ProblemFieldBinding foundInsideProblem = null;
				// inside Constructor call or inside static context
				Scope scope = this;
				int depth = 0;
				int foundDepth = 0;
				ReferenceBinding foundActualReceiverType = null;
				done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found
					switch (scope.kind) {
						case METHOD_SCOPE :
							MethodScope methodScope = (MethodScope) scope;
							insideStaticContext |= methodScope.isStatic;
							insideConstructorCall |= methodScope.isConstructorCall;
							insideTypeAnnotation = methodScope.insideTypeAnnotation;
							
							// Fall through... could duplicate the code below to save a cast - questionable optimization
						case BLOCK_SCOPE :
							LocalVariableBinding variableBinding = scope.findVariable(name);
							// looks in this scope only
							if (variableBinding != null) {
								if (foundField != null && foundField.isValidBinding())
									return new ProblemFieldBinding(
										foundField, // closest match
										foundField.declaringClass,
										name,
										ProblemReasons.InheritedNameHidesEnclosingName);
								if (depth > 0)
									invocationSite.setDepth(depth);
								return variableBinding;
							}
							break;
						case CLASS_SCOPE :
							ClassScope classScope = (ClassScope) scope;
							ReferenceBinding receiverType = classScope.enclosingReceiverType();
							if (!insideTypeAnnotation) {
								FieldBinding fieldBinding = classScope.findField(receiverType, name, invocationSite, needResolve);
								// Use next line instead if willing to enable protected access accross inner types
								// FieldBinding fieldBinding = findField(enclosingType, name, invocationSite);
								
								if (fieldBinding != null) { // skip it if we did not find anything
									if (fieldBinding.problemId() == ProblemReasons.Ambiguous) {
										if (foundField == null || foundField.problemId() == ProblemReasons.NotVisible)
											// supercedes any potential InheritedNameHidesEnclosingName problem
											return fieldBinding;
										// make the user qualify the field, likely wants the first inherited field (javac generates an ambiguous error instead)
										return new ProblemFieldBinding(
											foundField, // closest match
											foundField.declaringClass,
											name,
											ProblemReasons.InheritedNameHidesEnclosingName);
									}
	
									ProblemFieldBinding insideProblem = null;
									if (fieldBinding.isValidBinding()) {
										if (!fieldBinding.isStatic()) {
											if (insideConstructorCall) {
												insideProblem =
													new ProblemFieldBinding(
														fieldBinding, // closest match
														fieldBinding.declaringClass,
														name,
														ProblemReasons.NonStaticReferenceInConstructorInvocation);
											} else if (insideStaticContext) {
												insideProblem =
													new ProblemFieldBinding(
														fieldBinding, // closest match
														fieldBinding.declaringClass,
														name,
														ProblemReasons.NonStaticReferenceInStaticContext);
											}
										}
										if (receiverType == fieldBinding.declaringClass || compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) {
											// found a valid field in the 'immediate' scope (ie. not inherited)
											// OR in 1.4 mode (inherited shadows enclosing)
											if (foundField == null) {
												if (depth > 0){
													invocationSite.setDepth(depth);
													invocationSite.setActualReceiverType(receiverType);
												}
												// return the fieldBinding if it is not declared in a superclass of the scope's binding (that is, inherited)
												return insideProblem == null ? fieldBinding : insideProblem;
											}
											if (foundField.isValidBinding())
												// if a valid field was found, complain when another is found in an 'immediate' enclosing type (that is, not inherited)
												if (foundField.declaringClass != fieldBinding.declaringClass)
													// ie. have we found the same field - do not trust field identity yet
													return new ProblemFieldBinding(
														foundField, // closest match
														foundField.declaringClass,
														name,
														ProblemReasons.InheritedNameHidesEnclosingName);
										}
									}
	
									if (foundField == null || (foundField.problemId() == ProblemReasons.NotVisible && fieldBinding.problemId() != ProblemReasons.NotVisible)) {
										// only remember the fieldBinding if its the first one found or the previous one was not visible & fieldBinding is...
										foundDepth = depth;
										foundActualReceiverType = receiverType;
										foundInsideProblem = insideProblem;
										foundField = fieldBinding;
									}
								}
							}
							insideTypeAnnotation = false;
							depth++;
							insideStaticContext |= receiverType.isStatic();
							// 1EX5I8Z - accessing outer fields within a constructor call is permitted
							// in order to do so, we change the flag as we exit from the type, not the method
							// itself, because the class scope is used to retrieve the fields.
							MethodScope enclosingMethodScope = scope.methodScope();
							insideConstructorCall = enclosingMethodScope == null ? false : enclosingMethodScope.isConstructorCall;
							break;
						case COMPILATION_UNIT_SCOPE :
							break done;
					}
					scope = scope.parent;
				}

				if (foundInsideProblem != null)
					return foundInsideProblem;
				if (foundField != null) {
					if (foundField.isValidBinding()) {
						if (foundDepth > 0) {
							invocationSite.setDepth(foundDepth);
							invocationSite.setActualReceiverType(foundActualReceiverType);
						}
						return foundField;
					}
					problemField = foundField;
					foundField = null;
				}

				if (compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) {
					// at this point the scope is a compilation unit scope & need to check for imported static fields
					unitScope.faultInImports(); // ensure static imports are resolved
					ImportBinding[] imports = unitScope.imports;
					if (imports != null) {
						// check single static imports
						for (int i = 0, length = imports.length; i < length; i++) {
							ImportBinding importBinding = imports[i];
							if (importBinding.isStatic() && !importBinding.onDemand) {
								if (CharOperation.equals(importBinding.compoundName[importBinding.compoundName.length - 1], name)) {
									if (unitScope.resolveSingleImport(importBinding) != null && importBinding.resolvedImport instanceof FieldBinding) {
										foundField = (FieldBinding) importBinding.resolvedImport;
										ImportReference importReference = importBinding.reference;
										if (importReference != null && needResolve) {
											importReference.bits |= ASTNode.Used;
										}
										invocationSite.setActualReceiverType(foundField.declaringClass);											
										if (foundField.isValidBinding()) {
											return foundField;
										}
										if (problemField == null)
											problemField = foundField;
									}
								}
							}
						}
						// check on demand imports
						boolean foundInImport = false;
						for (int i = 0, length = imports.length; i < length; i++) {
							ImportBinding importBinding = imports[i];
							if (importBinding.isStatic() && importBinding.onDemand) {
								Binding resolvedImport = importBinding.resolvedImport;
								if (resolvedImport instanceof ReferenceBinding) {
									FieldBinding temp = findField((ReferenceBinding) resolvedImport, name, invocationSite, needResolve);
									if (temp != null) {
										if (!temp.isValidBinding()) {
											if (problemField == null)
												problemField = temp;
										} else if (temp.isStatic()) {
											if (foundField == temp) continue;
											ImportReference importReference = importBinding.reference;
											if (importReference != null && needResolve) {
												importReference.bits |= ASTNode.Used;
											}
											if (foundInImport)
												// Answer error binding -- import on demand conflict; name found in two import on demand packages.
												return new ProblemReferenceBinding(name, null, ProblemReasons.Ambiguous);
											foundField = temp;
											foundInImport = true;
										}
									}
								}
							}
						}
						if (foundField != null) {
							invocationSite.setActualReceiverType(foundField.declaringClass);							
							return foundField;
						}
					}
				}
			}

			// We did not find a local or instance variable.
			if ((mask & Binding.TYPE) != 0) {
				if ((binding = getBaseType(name)) != null)
					return binding;
				binding = getTypeOrPackage(name, (mask & Binding.PACKAGE) == 0 ? Binding.TYPE : Binding.TYPE | Binding.PACKAGE);
				if (binding.isValidBinding() || mask == Binding.TYPE)
					return binding;
				// answer the problem type binding if we are only looking for a type
			} else if ((mask & Binding.PACKAGE) != 0) {
				unitScope.recordSimpleReference(name);
				if ((binding = env.getTopLevelPackage(name)) != null)
					return binding;
			}
			if (problemField != null) return problemField;
			if (binding != null && binding.problemId() != ProblemReasons.NotFound)
				return binding; // answer the better problem binding
			return new ProblemBinding(name, enclosingSourceType(), ProblemReasons.NotFound);
		} catch (AbortCompilation e) {
			e.updateContext(invocationSite, referenceCompilationUnit().compilationResult);
			throw e;
		} finally {
			env.missingClassFileLocation = null;
		}
	}

	public MethodBinding getConstructor(ReferenceBinding receiverType, TypeBinding[] argumentTypes, InvocationSite invocationSite) {
		CompilationUnitScope unitScope = compilationUnitScope();
		LookupEnvironment env = unitScope.environment;
		try {
			env.missingClassFileLocation = invocationSite;
			unitScope.recordTypeReference(receiverType);
			unitScope.recordTypeReferences(argumentTypes);
			MethodBinding methodBinding = receiverType.getExactConstructor(argumentTypes);
			if (methodBinding != null && methodBinding.canBeSeenBy(invocationSite, this)) {
			    // targeting a non generic constructor with type arguments ?
			    if (invocationSite.genericTypeArguments() != null)
			    	methodBinding = computeCompatibleMethod(methodBinding, argumentTypes, invocationSite);				
				return methodBinding;
			}
			MethodBinding[] methods = receiverType.getMethods(TypeConstants.INIT);
			if (methods == Binding.NO_METHODS)
				return new ProblemMethodBinding(
					TypeConstants.INIT,
					argumentTypes,
					ProblemReasons.NotFound);

			MethodBinding[] compatible = new MethodBinding[methods.length];
			int compatibleIndex = 0;
			MethodBinding problemMethod = null;
			for (int i = 0, length = methods.length; i < length; i++) {
				MethodBinding compatibleMethod = computeCompatibleMethod(methods[i], argumentTypes, invocationSite);
				if (compatibleMethod != null) {
					if (compatibleMethod.isValidBinding())
						compatible[compatibleIndex++] = compatibleMethod;
					else if (problemMethod == null)
						problemMethod = compatibleMethod;
				}
			}
			if (compatibleIndex == 0) {
				if (problemMethod == null)
					return new ProblemMethodBinding(TypeConstants.INIT, argumentTypes, ProblemReasons.NotFound);
				return problemMethod;
			}
			// need a more descriptive error... cannot convert from X to Y
	
			MethodBinding[] visible = new MethodBinding[compatibleIndex];
			int visibleIndex = 0;
			for (int i = 0; i < compatibleIndex; i++) {
				MethodBinding method = compatible[i];
				if (method.canBeSeenBy(invocationSite, this))
					visible[visibleIndex++] = method;
			}
			if (visibleIndex == 1) return visible[0];
			if (visibleIndex == 0)
				return new ProblemMethodBinding(
					compatible[0],
					TypeConstants.INIT,
					compatible[0].parameters,
					ProblemReasons.NotVisible);
			// all of visible are from the same declaringClass, even before 1.4 we can call this method instead of mostSpecificClassMethodBinding
			return mostSpecificMethodBinding(visible, visibleIndex, argumentTypes, invocationSite, receiverType);
		} catch (AbortCompilation e) {
			e.updateContext(invocationSite, referenceCompilationUnit().compilationResult);
			throw e;
		} finally {
			env.missingClassFileLocation = null;
		}
	}

	public final PackageBinding getCurrentPackage() {
		Scope scope, unitScope = this;
		while ((scope = unitScope.parent) != null)
			unitScope = scope;
		return ((CompilationUnitScope) unitScope).fPackage;
	}

	/**
	 * Returns the modifiers of the innermost enclosing declaration.
	 * @return modifiers
	 */
	public int getDeclarationModifiers(){
		switch(this.kind){
			case Scope.BLOCK_SCOPE :
			case Scope.METHOD_SCOPE :
				MethodScope methodScope = methodScope();
				if (!methodScope.isInsideInitializer()){
					// check method modifiers to see if deprecated
					MethodBinding context = ((AbstractMethodDeclaration)methodScope.referenceContext).binding;
					if (context != null)
						return context.modifiers;
				} else {
					SourceTypeBinding type = ((BlockScope) this).referenceType().binding;

					// inside field declaration ? check field modifier to see if deprecated
					if (methodScope.initializedField != null)
						return methodScope.initializedField.modifiers;
					if (type != null)
						return type.modifiers;
				}
				break;
			case Scope.CLASS_SCOPE :
				ReferenceBinding context = ((ClassScope)this).referenceType().binding;
				if (context != null)
					return context.modifiers;
				break;
		}
		return -1;
	}

	public FieldBinding getField(TypeBinding receiverType, char[] fieldName, InvocationSite invocationSite) {
		LookupEnvironment env = environment();
		try {
			env.missingClassFileLocation = invocationSite;
			FieldBinding field = findField(receiverType, fieldName, invocationSite, true /*resolve*/);
			if (field != null) return field;
	
			return new ProblemFieldBinding(
				receiverType instanceof ReferenceBinding ? (ReferenceBinding) receiverType : null,
				fieldName,
				ProblemReasons.NotFound);
		} catch (AbortCompilation e) {
			e.updateContext(invocationSite, referenceCompilationUnit().compilationResult);
			throw e;
		} finally {
			env.missingClassFileLocation = null;
		}
	}
	
	/* API
	 *	
	 *	Answer the method binding that corresponds to selector, argumentTypes.
	 *	Start the lookup at the enclosing type of the receiver.
	 *	InvocationSite implements 
	 *		isSuperAccess(); this is used to determine if the discovered method is visible.
	 *		setDepth(int); this is used to record the depth of the discovered method
	 *			relative to the enclosing type of the receiver. (If the method is defined
	 *			in the enclosing type of the receiver, the depth is 0; in the next enclosing
	 *			type, the depth is 1; and so on
	 * 
	 *	If no visible method is discovered, an error binding is answered.
	 */
	public MethodBinding getImplicitMethod(char[] selector, TypeBinding[] argumentTypes, InvocationSite invocationSite) {

		boolean insideStaticContext = false;
		boolean insideConstructorCall = false;
		boolean insideTypeAnnotation = false;
		MethodBinding foundMethod = null;
		MethodBinding foundProblem = null;
		boolean foundProblemVisible = false;
		Scope scope = this;
		int depth = 0;
		// in 1.4 mode (inherited visible shadows enclosing)
		CompilerOptions options;
		boolean inheritedHasPrecedence = (options = compilerOptions()).complianceLevel >= ClassFileConstants.JDK1_4;
		
		done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found
			switch (scope.kind) {
				case METHOD_SCOPE :
					MethodScope methodScope = (MethodScope) scope;
					insideStaticContext |= methodScope.isStatic;
					insideConstructorCall |= methodScope.isConstructorCall;
					insideTypeAnnotation = methodScope.insideTypeAnnotation;
					break;
				case CLASS_SCOPE :
					ClassScope classScope = (ClassScope) scope;
					ReferenceBinding receiverType = classScope.enclosingReceiverType();
					if (!insideTypeAnnotation) {
						// retrieve an exact visible match (if possible)
						// compilationUnitScope().recordTypeReference(receiverType);   not needed since receiver is the source type
						MethodBinding methodBinding = classScope.findExactMethod(receiverType, selector, argumentTypes, invocationSite);
						if (methodBinding == null)
							methodBinding = classScope.findMethod(receiverType, selector, argumentTypes, invocationSite);
						if (methodBinding != null) { // skip it if we did not find anything
							if (foundMethod == null) {
								if (methodBinding.isValidBinding()) {
									if (!methodBinding.isStatic() && (insideConstructorCall || insideStaticContext)) {
										if (foundProblem != null && foundProblem.problemId() != ProblemReasons.NotVisible)
											return foundProblem; // takes precedence
										return new ProblemMethodBinding(
											methodBinding, // closest match
											methodBinding.selector,
											methodBinding.parameters,
											insideConstructorCall
												? ProblemReasons.NonStaticReferenceInConstructorInvocation
												: ProblemReasons.NonStaticReferenceInStaticContext);
									}
									if (inheritedHasPrecedence
											|| receiverType == methodBinding.declaringClass
											|| (receiverType.getMethods(selector)) != Binding.NO_METHODS) {
										// found a valid method in the 'immediate' scope (ie. not inherited)
										// OR in 1.4 mode (inherited visible shadows enclosing)
										// OR the receiverType implemented a method with the correct name
										// return the methodBinding if it is not declared in a superclass of the scope's binding (that is, inherited)
										if (foundProblemVisible) {
											return foundProblem;
										}
										if (depth > 0) {
											invocationSite.setDepth(depth);
											invocationSite.setActualReceiverType(receiverType);
										}
										return methodBinding;
									}

									if (foundProblem == null || foundProblem.problemId() == ProblemReasons.NotVisible) {
										if (foundProblem != null) foundProblem = null;
										// only remember the methodBinding if its the first one found
										// remember that private methods are visible if defined directly by an enclosing class
										if (depth > 0) {
											invocationSite.setDepth(depth);
											invocationSite.setActualReceiverType(receiverType);
										}
										foundMethod = methodBinding;
									}
								} else { // methodBinding is a problem method
									if (methodBinding.problemId() != ProblemReasons.NotVisible && methodBinding.problemId() != ProblemReasons.NotFound)
										return methodBinding; // return the error now
									if (foundProblem == null) {
										foundProblem = methodBinding; // hold onto the first not visible/found error and keep the second not found if first is not visible
									} 
									if (! foundProblemVisible && methodBinding.problemId() == ProblemReasons.NotFound) {
										MethodBinding closestMatch = ((ProblemMethodBinding) methodBinding).closestMatch;
										if (closestMatch != null && closestMatch.canBeSeenBy(receiverType, invocationSite, this)) {
											foundProblem = methodBinding; // hold onto the first not visible/found error and keep the second not found if first is not visible
											foundProblemVisible = true;
										}
									}
								}
							} else { // found a valid method so check to see if this is a hiding case
								if (methodBinding.problemId() == ProblemReasons.Ambiguous
									|| (foundMethod.declaringClass != methodBinding.declaringClass
										&& (receiverType == methodBinding.declaringClass || receiverType.getMethods(selector) != Binding.NO_METHODS)))
									// ambiguous case -> must qualify the method (javac generates an ambiguous error instead)
									// otherwise if a method was found, complain when another is found in an 'immediate' enclosing type (that is, not inherited)
									// NOTE: Unlike fields, a non visible method hides a visible method
									return new ProblemMethodBinding(
										methodBinding, // closest match
										selector,
										argumentTypes,
										ProblemReasons.InheritedNameHidesEnclosingName);
							}
						}
					}
					insideTypeAnnotation = false;
					depth++;
					insideStaticContext |= receiverType.isStatic();
					// 1EX5I8Z - accessing outer fields within a constructor call is permitted
					// in order to do so, we change the flag as we exit from the type, not the method
					// itself, because the class scope is used to retrieve the fields.
					MethodScope enclosingMethodScope = scope.methodScope();
					insideConstructorCall = enclosingMethodScope == null ? false : enclosingMethodScope.isConstructorCall;
					break;
				case COMPILATION_UNIT_SCOPE :
					break done;
			}
			scope = scope.parent;
		}

		if (insideStaticContext && options.sourceLevel >= ClassFileConstants.JDK1_5) {
			if (foundProblem != null) {
				if (foundProblem.declaringClass != null && foundProblem.declaringClass.id == TypeIds.T_JavaLangObject)
					return foundProblem; // static imports lose to methods from Object
				if (foundProblem.problemId() == ProblemReasons.NotFound && foundProblemVisible) {
					return foundProblem; // visible method selectors take precedence
				}
			}

			// at this point the scope is a compilation unit scope & need to check for imported static methods
			CompilationUnitScope unitScope = (CompilationUnitScope) scope;
			unitScope.faultInImports(); // field constants can cause static imports to be accessed before they're resolved 
			ImportBinding[] imports = unitScope.imports;
			if (imports != null) {
				ObjectVector visible = null;
				boolean skipOnDemand = false; // set to true when matched static import of method name so stop looking for on demand methods
				for (int i = 0, length = imports.length; i < length; i++) {
					ImportBinding importBinding = imports[i];
					if (importBinding.isStatic()) {
						Binding resolvedImport = importBinding.resolvedImport;
						MethodBinding possible = null;
						if (importBinding.onDemand) {
							if (!skipOnDemand && resolvedImport instanceof ReferenceBinding)
								// answers closest approximation, may not check argumentTypes or visibility
								possible = findMethod((ReferenceBinding) resolvedImport, selector, argumentTypes, invocationSite);
						} else {
							if (resolvedImport instanceof MethodBinding) {
								MethodBinding staticMethod = (MethodBinding) resolvedImport;
								if (CharOperation.equals(staticMethod.selector, selector))
									// answers closest approximation, may not check argumentTypes or visibility
									possible = findMethod(staticMethod.declaringClass, selector, argumentTypes, invocationSite);
							} else if (resolvedImport instanceof FieldBinding) {
								// check to see if there are also methods with the same name
								FieldBinding staticField = (FieldBinding) resolvedImport;
								if (CharOperation.equals(staticField.name, selector)) {
									// must find the importRef's type again since the field can be from an inherited type
									char[][] importName = importBinding.reference.tokens;
									TypeBinding referencedType = getType(importName, importName.length - 1);
									if (referencedType != null)
										// answers closest approximation, may not check argumentTypes or visibility
										possible = findMethod((ReferenceBinding) referencedType, selector, argumentTypes, invocationSite);
								}
							}
						}
						if (possible != null && possible != foundProblem) {
							if (!possible.isValidBinding()) {
								if (foundProblem == null)
									foundProblem = possible; // answer as error case match
							} else if (possible.isStatic()) {
								MethodBinding compatibleMethod = computeCompatibleMethod(possible, argumentTypes, invocationSite);
								if (compatibleMethod != null) {
									if (compatibleMethod.isValidBinding()) {
										if (compatibleMethod.canBeSeenBy(unitScope.fPackage)) {
											if (visible == null || !visible.contains(compatibleMethod)) {
												ImportReference importReference = importBinding.reference;
												if (importReference != null) {
													importReference.bits |= ASTNode.Used;
												}
												if (!skipOnDemand && !importBinding.onDemand) {
													visible = null; // forget previous matches from on demand imports
													skipOnDemand = true;
												}
												if (visible == null)
													visible = new ObjectVector(3);
												visible.add(compatibleMethod);
											}
										} else if (foundProblem == null) {
											foundProblem = new ProblemMethodBinding(compatibleMethod, selector, compatibleMethod.parameters, ProblemReasons.NotVisible);
										}
									} else if (foundProblem == null) {
										foundProblem = compatibleMethod;
									}
								} else if (foundProblem == null) {
									foundProblem = new ProblemMethodBinding(possible, selector, argumentTypes, ProblemReasons.NotFound);
								}
							}
						}
					}
				}
				if (visible != null) {
					MethodBinding[] temp = new MethodBinding[visible.size];
					visible.copyInto(temp);
					foundMethod = mostSpecificMethodBinding(temp, temp.length, argumentTypes, invocationSite, null);
				}
			}
		}

		if (foundMethod != null) {
			invocationSite.setActualReceiverType(foundMethod.declaringClass);
			return foundMethod;
		}
		if (foundProblem != null)
			return foundProblem;

		return new ProblemMethodBinding(selector, argumentTypes, ProblemReasons.NotFound);
	}

	public final ReferenceBinding getJavaIoSerializable() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_IO_SERIALIZABLE);
		return unitScope.environment.getResolvedType(JAVA_IO_SERIALIZABLE, this);
	}
	
	public final ReferenceBinding getJavaLangAnnotationAnnotation() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_ANNOTATION_ANNOTATION);
		return unitScope.environment.getResolvedType(JAVA_LANG_ANNOTATION_ANNOTATION, this);
	}
	
	public final ReferenceBinding getJavaLangAssertionError() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_ASSERTIONERROR);
		return unitScope.environment.getResolvedType(JAVA_LANG_ASSERTIONERROR, this);
	}

	public final ReferenceBinding getJavaLangClass() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_CLASS);
		return unitScope.environment.getResolvedType(JAVA_LANG_CLASS, this);
	}

	public final ReferenceBinding getJavaLangCloneable() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_CLONEABLE);
		return unitScope.environment.getResolvedType(JAVA_LANG_CLONEABLE, this);
	}
	public final ReferenceBinding getJavaLangEnum() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_ENUM);
		return unitScope.environment.getResolvedType(JAVA_LANG_ENUM, this);
	}

	public final ReferenceBinding getJavaLangIterable() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_ITERABLE);
		return unitScope.environment.getResolvedType(JAVA_LANG_ITERABLE, this);
	}
	public final ReferenceBinding getJavaLangObject() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_OBJECT);
		return unitScope.environment.getResolvedType(JAVA_LANG_OBJECT, this);
	}

	public final ReferenceBinding getJavaLangString() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_STRING);
		return unitScope.environment.getResolvedType(JAVA_LANG_STRING, this);
	}

	public final ReferenceBinding getJavaLangThrowable() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_LANG_THROWABLE);
		return unitScope.environment.getResolvedType(JAVA_LANG_THROWABLE, this);
	}
	public final ReferenceBinding getJavaUtilIterator() {
		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(JAVA_UTIL_ITERATOR);
		return unitScope.environment.getResolvedType(JAVA_UTIL_ITERATOR, this);
	}

	/* Answer the type binding corresponding to the typeName argument, relative to the enclosingType.
	*/
	public final ReferenceBinding getMemberType(char[] typeName, ReferenceBinding enclosingType) {
		ReferenceBinding memberType = findMemberType(typeName, enclosingType);
		if (memberType != null) return memberType;
		return new ProblemReferenceBinding(typeName, null, ProblemReasons.NotFound);
	}

	public MethodBinding getMethod(TypeBinding receiverType, char[] selector, TypeBinding[] argumentTypes, InvocationSite invocationSite) {
		CompilationUnitScope unitScope = compilationUnitScope();
		LookupEnvironment env = unitScope.environment;
		try {
			env.missingClassFileLocation = invocationSite;
			switch (receiverType.kind()) {
				case Binding.BASE_TYPE :
					return new ProblemMethodBinding(selector, argumentTypes, ProblemReasons.NotFound);
				case Binding.ARRAY_TYPE :
					unitScope.recordTypeReference(receiverType);
					return findMethodForArray((ArrayBinding) receiverType, selector, argumentTypes, invocationSite);
			}
			unitScope.recordTypeReference(receiverType);

			ReferenceBinding currentType = (ReferenceBinding) receiverType;
			if (!currentType.canBeSeenBy(this))
				return new ProblemMethodBinding(selector, argumentTypes, ProblemReasons.ReceiverTypeNotVisible);
	
			// retrieve an exact visible match (if possible)
			MethodBinding methodBinding = findExactMethod(currentType, selector, argumentTypes, invocationSite);
			if (methodBinding != null) return methodBinding;
	
			methodBinding = findMethod(currentType, selector, argumentTypes, invocationSite);
			if (methodBinding == null)
				return new ProblemMethodBinding(selector, argumentTypes, ProblemReasons.NotFound);
			if (!methodBinding.isValidBinding())
				return methodBinding;

			// special treatment for Object.getClass() in 1.5 mode (substitute parameterized return type)
			if (receiverType.id != T_JavaLangObject
				&& argumentTypes == Binding.NO_PARAMETERS
			    && CharOperation.equals(selector, GETCLASS)
			    && methodBinding.returnType.isParameterizedType()/*1.5*/) {
					return ParameterizedMethodBinding.instantiateGetClass(receiverType, methodBinding, this);
		    }			
			return methodBinding;
		} catch (AbortCompilation e) {
			e.updateContext(invocationSite, referenceCompilationUnit().compilationResult);
			throw e;
		} finally {
			env.missingClassFileLocation = null;
		}
	}

	/* Answer the package from the compoundName or null if it begins with a type.
	* Intended to be used while resolving a qualified type name.
	*
	* NOTE: If a problem binding is returned, senders should extract the compound name
	* from the binding & not assume the problem applies to the entire compoundName.
	*/
	public final Binding getPackage(char[][] compoundName) {
		compilationUnitScope().recordQualifiedReference(compoundName);
		Binding binding = getTypeOrPackage(compoundName[0], Binding.TYPE | Binding.PACKAGE);
		if (binding == null)
			return new ProblemReferenceBinding(compoundName[0], null, ProblemReasons.NotFound);
		if (!binding.isValidBinding())
			return binding;

		if (!(binding instanceof PackageBinding)) return null; // compoundName does not start with a package

		int currentIndex = 1;
		PackageBinding packageBinding = (PackageBinding) binding;
		while (currentIndex < compoundName.length) {
			binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++]);
			if (binding == null)
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					null, 
					ProblemReasons.NotFound);
			if (!binding.isValidBinding())
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					binding instanceof ReferenceBinding ? ((ReferenceBinding)binding).closestMatch() : null,
					binding.problemId());
			if (!(binding instanceof PackageBinding))
				return packageBinding;
			packageBinding = (PackageBinding) binding;
		}
		return new ProblemReferenceBinding(compoundName, null, ProblemReasons.NotFound);
	}

	/* Answer the type binding that corresponds the given name, starting the lookup in the receiver.
	* The name provided is a simple source name (e.g., "Object" , "Point", ...)
	*/
	// The return type of this method could be ReferenceBinding if we did not answer base types.
	// NOTE: We could support looking for Base Types last in the search, however any code using
	// this feature would be extraordinarily slow.  Therefore we don't do this
	public final TypeBinding getType(char[] name) {
		// Would like to remove this test and require senders to specially handle base types
		TypeBinding binding = getBaseType(name);
		if (binding != null) return binding;
		return (ReferenceBinding) getTypeOrPackage(name, Binding.TYPE);
	}

	/* Answer the type binding that corresponds to the given name, starting the lookup in the receiver
	* or the packageBinding if provided.
	* The name provided is a simple source name (e.g., "Object" , "Point", ...)
	*/
	public final TypeBinding getType(char[] name, PackageBinding packageBinding) {
		if (packageBinding == null)
			return getType(name);

		Binding binding = packageBinding.getTypeOrPackage(name);
		if (binding == null)
			return new ProblemReferenceBinding(
				CharOperation.arrayConcat(packageBinding.compoundName, name),
				null,
				ProblemReasons.NotFound);
		if (!binding.isValidBinding())
			return new ProblemReferenceBinding(
				CharOperation.arrayConcat(packageBinding.compoundName, name),
				binding instanceof ReferenceBinding ? ((ReferenceBinding)binding).closestMatch() : null,
				binding.problemId());

		ReferenceBinding typeBinding = (ReferenceBinding) binding;
		if (!typeBinding.canBeSeenBy(this))
			return new ProblemReferenceBinding(
				CharOperation.arrayConcat(packageBinding.compoundName, name),
				typeBinding,
				ProblemReasons.NotVisible);
		return typeBinding;
	}

	/* Answer the type binding corresponding to the compoundName.
	*
	* NOTE: If a problem binding is returned, senders should extract the compound name
	* from the binding & not assume the problem applies to the entire compoundName.
	*/
	public final TypeBinding getType(char[][] compoundName, int typeNameLength) {
		if (typeNameLength == 1) {
			// Would like to remove this test and require senders to specially handle base types
			TypeBinding binding = getBaseType(compoundName[0]);
			if (binding != null) return binding;
		}

		CompilationUnitScope unitScope = compilationUnitScope();
		unitScope.recordQualifiedReference(compoundName);
		Binding binding =
			getTypeOrPackage(compoundName[0], typeNameLength == 1 ? Binding.TYPE : Binding.TYPE | Binding.PACKAGE);
		if (binding == null)
			return new ProblemReferenceBinding(compoundName[0], null, ProblemReasons.NotFound);
		if (!binding.isValidBinding())
			return (ReferenceBinding) binding;

		int currentIndex = 1;
		boolean checkVisibility = false;
		if (binding instanceof PackageBinding) {
			PackageBinding packageBinding = (PackageBinding) binding;
			while (currentIndex < typeNameLength) {
				binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++]); // does not check visibility
				if (binding == null)
					return new ProblemReferenceBinding(
						CharOperation.subarray(compoundName, 0, currentIndex),
						null,
						ProblemReasons.NotFound);
				if (!binding.isValidBinding())
					return new ProblemReferenceBinding(
						CharOperation.subarray(compoundName, 0, currentIndex),
						binding instanceof ReferenceBinding ? ((ReferenceBinding)binding).closestMatch() : null,
						binding.problemId());
				if (!(binding instanceof PackageBinding))
					break;
				packageBinding = (PackageBinding) binding;
			}
			if (binding instanceof PackageBinding)
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					null,
					ProblemReasons.NotFound);
			checkVisibility = true;
		}

		// binding is now a ReferenceBinding
		ReferenceBinding typeBinding = (ReferenceBinding) binding;
		unitScope.recordTypeReference(typeBinding);
		if (checkVisibility) // handles the fall through case
			if (!typeBinding.canBeSeenBy(this))
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					typeBinding,
					ProblemReasons.NotVisible);

		while (currentIndex < typeNameLength) {
			typeBinding = getMemberType(compoundName[currentIndex++], typeBinding);
			if (!typeBinding.isValidBinding()) {
				if (typeBinding instanceof ProblemReferenceBinding) {
					ProblemReferenceBinding problemBinding = (ProblemReferenceBinding) typeBinding;
					return new ProblemReferenceBinding(
						CharOperation.subarray(compoundName, 0, currentIndex),
						problemBinding.closestMatch(),
						typeBinding.problemId());
				}
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					((ReferenceBinding)binding).closestMatch(),
					typeBinding.problemId());
			}
		}
		return typeBinding;
	}
	
	/* Internal use only 
	*/
	final Binding getTypeOrPackage(char[] name, int mask) {
		Scope scope = this;
		ReferenceBinding foundType = null;
		boolean insideStaticContext = false;
		boolean insideTypeAnnotation = false;
		if ((mask & Binding.TYPE) == 0) {
			Scope next = scope;
			while ((next = scope.parent) != null)
				scope = next;
		} else {
			done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found
				switch (scope.kind) {
					case METHOD_SCOPE :
						MethodScope methodScope = (MethodScope) scope;
						AbstractMethodDeclaration methodDecl = methodScope.referenceMethod();
						if (methodDecl != null && methodDecl.binding != null) {
							TypeVariableBinding typeVariable = methodDecl.binding.getTypeVariable(name);
							if (typeVariable != null)	return typeVariable;
						}
						insideStaticContext |= methodScope.isStatic;
						insideTypeAnnotation = methodScope.insideTypeAnnotation;
					case BLOCK_SCOPE :
						ReferenceBinding localType = ((BlockScope) scope).findLocalType(name); // looks in this scope only
						if (localType != null) {
							if (foundType != null && foundType != localType)
								return new ProblemReferenceBinding(name, foundType, ProblemReasons.InheritedNameHidesEnclosingName);
							return localType;
						}
						break;
					case CLASS_SCOPE :
						SourceTypeBinding sourceType = ((ClassScope) scope).referenceContext.binding;
						if (scope == this && (sourceType.tagBits & TagBits.TypeVariablesAreConnected) == 0) {
							// type variables take precedence over the source type, ex. class X <X> extends X == class X <Y> extends Y
							// but not when we step out to the enclosing type
							TypeVariableBinding typeVariable = sourceType.getTypeVariable(name);
							if (typeVariable != null)
								return typeVariable;
							if (CharOperation.equals(name, sourceType.sourceName))
								return sourceType;
							insideStaticContext |= sourceType.isStatic();
							break;
						}						
						// member types take precedence over type variables
						if (!insideTypeAnnotation) {
							// 6.5.5.1 - member types have precedence over top-level type in same unit
							ReferenceBinding memberType = findMemberType(name, sourceType);
							if (memberType != null) { // skip it if we did not find anything
								if (memberType.problemId() == ProblemReasons.Ambiguous) {
									if (foundType == null || foundType.problemId() == ProblemReasons.NotVisible)
										// supercedes any potential InheritedNameHidesEnclosingName problem
										return memberType;
									// make the user qualify the type, likely wants the first inherited type
									return new ProblemReferenceBinding(name, foundType, ProblemReasons.InheritedNameHidesEnclosingName);
								}
								if (memberType.isValidBinding()) {
									if (sourceType == memberType.enclosingType() || compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) {
										if (insideStaticContext && !memberType.isStatic() && sourceType.isGenericType())
											return new ProblemReferenceBinding(name, memberType, ProblemReasons.NonStaticReferenceInStaticContext);
										// found a valid type in the 'immediate' scope (ie. not inherited)
										// OR in 1.4 mode (inherited shadows enclosing)
										if (foundType == null)
											return memberType; 
										// if a valid type was found, complain when another is found in an 'immediate' enclosing type (ie. not inherited)
										if (foundType.isValidBinding() && foundType != memberType)
											return new ProblemReferenceBinding(name, foundType, ProblemReasons.InheritedNameHidesEnclosingName);
									}
								}
								if (foundType == null || (foundType.problemId() == ProblemReasons.NotVisible && memberType.problemId() != ProblemReasons.NotVisible))
									// only remember the memberType if its the first one found or the previous one was not visible & memberType is...
									foundType = memberType;
							}
						}
						TypeVariableBinding typeVariable = sourceType.getTypeVariable(name);
						if (typeVariable != null) {
							if (insideStaticContext) // do not consider this type modifiers: access is legite within same type
								return new ProblemReferenceBinding(name, typeVariable, ProblemReasons.NonStaticReferenceInStaticContext);
							return typeVariable;
						}						
						insideStaticContext |= sourceType.isStatic();
						insideTypeAnnotation = false;
						if (CharOperation.equals(sourceType.sourceName, name)) {
							if (foundType != null && foundType != sourceType && foundType.problemId() != ProblemReasons.NotVisible)
								return new ProblemReferenceBinding(name, foundType, ProblemReasons.InheritedNameHidesEnclosingName);
							return sourceType;
						}
						break;
					case COMPILATION_UNIT_SCOPE :
						break done;
				}
				scope = scope.parent;
			}
			if (foundType != null && foundType.problemId() != ProblemReasons.NotVisible)
				return foundType;
		}

		// at this point the scope is a compilation unit scope
		CompilationUnitScope unitScope = (CompilationUnitScope) scope;
		HashtableOfObject typeOrPackageCache = unitScope.typeOrPackageCache;
		if (typeOrPackageCache != null) {
			Binding binding = (Binding) typeOrPackageCache.get(name);
			if (binding != null) { // can also include NotFound ProblemReferenceBindings if we already know this name is not found
				if (binding instanceof ImportBinding) { // single type import cached in faultInImports(), replace it in the cache with the type
					ImportReference importReference = ((ImportBinding) binding).reference;
					if (importReference != null) {
						importReference.bits |= ASTNode.Used;
					}
					if (binding instanceof ImportConflictBinding)
						typeOrPackageCache.put(name, binding = ((ImportConflictBinding) binding).conflictingTypeBinding); // already know its visible
					else
						typeOrPackageCache.put(name, binding = ((ImportBinding) binding).resolvedImport); // already know its visible
				}
				if ((mask & Binding.TYPE) != 0) {
					if (foundType != null && foundType.problemId() != ProblemReasons.NotVisible && binding.problemId() != ProblemReasons.Ambiguous)
						return foundType; // problem type from above supercedes NotFound type but not Ambiguous import case
					if (binding instanceof ReferenceBinding)
						return binding; // cached type found in previous walk below
				}
				if ((mask & Binding.PACKAGE) != 0 && binding instanceof PackageBinding)
					return binding; // cached package found in previous walk below
			}
		}

		// ask for the imports + name
		if ((mask & Binding.TYPE) != 0) {
			ImportBinding[] imports = unitScope.imports;
			if (imports != null && typeOrPackageCache == null) { // walk single type imports since faultInImports() has not run yet
				nextImport : for (int i = 0, length = imports.length; i < length; i++) {
					ImportBinding importBinding = imports[i];
					if (!importBinding.onDemand) {
						if (CharOperation.equals(importBinding.compoundName[importBinding.compoundName.length - 1], name)) {
							Binding resolvedImport = unitScope.resolveSingleImport(importBinding);
							if (resolvedImport == null) continue nextImport;
							if (resolvedImport instanceof MethodBinding) {
								resolvedImport = getType(importBinding.compoundName, importBinding.compoundName.length);
								if (!resolvedImport.isValidBinding()) continue nextImport;
							}
							if (resolvedImport instanceof TypeBinding) {
								ImportReference importReference = importBinding.reference;
								if (importReference != null)
									importReference.bits |= ASTNode.Used;
								return resolvedImport; // already know its visible
							}
						}
					}
				}
			}

			// check if the name is in the current package, skip it if its a sub-package
			PackageBinding currentPackage = unitScope.fPackage; 
			unitScope.recordReference(currentPackage.compoundName, name);
			Binding binding = currentPackage.getTypeOrPackage(name);
			if (binding instanceof ReferenceBinding) {
				if (typeOrPackageCache != null)
					typeOrPackageCache.put(name, binding);
				return binding; // type is always visible to its own package
			}

			// check on demand imports
			if (imports != null) {
				boolean foundInImport = false;
				ReferenceBinding type = null;
				for (int i = 0, length = imports.length; i < length; i++) {
					ImportBinding someImport = imports[i];
					if (someImport.onDemand) {
						Binding resolvedImport = someImport.resolvedImport;
						ReferenceBinding temp = null;
						if (resolvedImport instanceof PackageBinding) {
							temp = findType(name, (PackageBinding) resolvedImport, currentPackage);
						} else if (someImport.isStatic()) {
							temp = findMemberType(name, (ReferenceBinding) resolvedImport); // static imports are allowed to see inherited member types
							if (temp != null && !temp.isStatic())
								temp = null;
						} else {
							temp = findDirectMemberType(name, (ReferenceBinding) resolvedImport);
						}
						if (temp != type && temp != null) {
							if (temp.isValidBinding()) {
								ImportReference importReference = someImport.reference;
								if (importReference != null) {
									importReference.bits |= ASTNode.Used;
								}
								if (foundInImport) {
									// Answer error binding -- import on demand conflict; name found in two import on demand packages.
									temp = new ProblemReferenceBinding(name, type, ProblemReasons.Ambiguous);
									if (typeOrPackageCache != null)
										typeOrPackageCache.put(name, temp);
									return temp;
								}
								type = temp;
								foundInImport = true;
							} else if (foundType == null) {
								foundType = temp;
							}
						}
					}
				}
				if (type != null) {
					if (typeOrPackageCache != null)
						typeOrPackageCache.put(name, type);
					return type;
				}
			}
		}

		unitScope.recordSimpleReference(name);
		if ((mask & Binding.PACKAGE) != 0) {
			PackageBinding packageBinding = unitScope.environment.getTopLevelPackage(name);
			if (packageBinding != null) {
				if (typeOrPackageCache != null)
					typeOrPackageCache.put(name, packageBinding);
				return packageBinding;
			}
		}

		// Answer error binding -- could not find name
		if (foundType == null) {
			foundType = new ProblemReferenceBinding(name, null, ProblemReasons.NotFound);
			if (typeOrPackageCache != null && (mask & Binding.PACKAGE) != 0) // only put NotFound type in cache if you know its not a package
				typeOrPackageCache.put(name, foundType);
		}
		return foundType;
	}
	
	// Added for code assist... NOT Public API
	// DO NOT USE to resolve import references since this method assumes 'A.B' is relative to a single type import of 'p1.A'
	// when it may actually mean the type B in the package A
	// use CompilationUnitScope.getImport(char[][]) instead
	public final Binding getTypeOrPackage(char[][] compoundName) {
		int nameLength = compoundName.length;
		if (nameLength == 1) {
			TypeBinding binding = getBaseType(compoundName[0]);
			if (binding != null) return binding;
		}
		Binding binding = getTypeOrPackage(compoundName[0], Binding.TYPE | Binding.PACKAGE);
		if (!binding.isValidBinding()) return binding;

		int currentIndex = 1;
		boolean checkVisibility = false;
		if (binding instanceof PackageBinding) {
			PackageBinding packageBinding = (PackageBinding) binding;

			while (currentIndex < nameLength) {
				binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++]);
				if (binding == null)
					return new ProblemReferenceBinding(
						CharOperation.subarray(compoundName, 0, currentIndex),
						null,
						ProblemReasons.NotFound);
				if (!binding.isValidBinding())
					return new ProblemReferenceBinding(
						CharOperation.subarray(compoundName, 0, currentIndex),
						binding instanceof ReferenceBinding ? ((ReferenceBinding)binding).closestMatch() : null,
						binding.problemId());
				if (!(binding instanceof PackageBinding))
					break;
				packageBinding = (PackageBinding) binding;
			}
			if (binding instanceof PackageBinding) return binding;
			checkVisibility = true;
		}
		// binding is now a ReferenceBinding
		ReferenceBinding typeBinding = (ReferenceBinding) binding;
		ReferenceBinding qualifiedType = (ReferenceBinding) this.environment().convertToRawType(typeBinding);

		if (checkVisibility) // handles the fall through case
			if (!typeBinding.canBeSeenBy(this))
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					typeBinding,
					ProblemReasons.NotVisible);

		while (currentIndex < nameLength) {
			typeBinding = getMemberType(compoundName[currentIndex++], typeBinding);
			// checks visibility
			if (!typeBinding.isValidBinding())
				return new ProblemReferenceBinding(
					CharOperation.subarray(compoundName, 0, currentIndex),
					((ReferenceBinding)binding).closestMatch(),
					typeBinding.problemId());
			
			if (typeBinding.isGenericType()) {
				qualifiedType = this.environment().createRawType(typeBinding, qualifiedType);
			} else {
				qualifiedType = (qualifiedType != null && (qualifiedType.isRawType() || qualifiedType.isParameterizedType()))
					? this.environment().createParameterizedType(typeBinding, null, qualifiedType)
					: typeBinding;
			}
		}
		return qualifiedType;
	}	

	protected boolean hasErasedCandidatesCollisions(TypeBinding one, TypeBinding two, Map invocations, ReferenceBinding type, ASTNode typeRef) {
		invocations.clear();
		TypeBinding[] mecs = minimalErasedCandidates(new TypeBinding[] {one, two}, invocations);
		if (mecs != null) {
			nextCandidate: for (int k = 0, max = mecs.length; k < max; k++) {
				TypeBinding mec = mecs[k];
				if (mec == null) continue nextCandidate;
				Object value = invocations.get(mec);
				if (value instanceof TypeBinding[]) {
					TypeBinding[] invalidInvocations = (TypeBinding[]) value;
					problemReporter().superinterfacesCollide(invalidInvocations[0].erasure(), typeRef, invalidInvocations[0], invalidInvocations[1]);
					type.tagBits |= TagBits.HierarchyHasProblems;
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Returns the immediately enclosing switchCase statement (carried by closest blockScope),
	 */
	public CaseStatement innermostSwitchCase() {
		Scope scope = this;
		do {
			if (scope instanceof BlockScope)
				return ((BlockScope) scope).enclosingCase;
			scope = scope.parent;
		} while (scope != null);
		return null;
	}

	protected boolean isAcceptableMethod(MethodBinding one, MethodBinding two) {
		TypeBinding[] oneParams = one.parameters;
		TypeBinding[] twoParams = two.parameters;
		int oneParamsLength = oneParams.length;
		int twoParamsLength = twoParams.length;
		if (oneParamsLength == twoParamsLength) {
			for (int i = 0; i < oneParamsLength; i++) {
				TypeBinding oneParam = oneParams[i];
				TypeBinding twoParam = twoParams[i];
				if (oneParam == twoParam) {
					if (oneParam.leafComponentType().isRawType()) {
						// A#RAW is not more specific than a rawified A<T>
						if (oneParam == one.original().parameters[i] && oneParam != two.original().parameters[i])
							return false;
					}
					continue;
				}
				if (oneParam.isCompatibleWith(twoParam)) {
					if (oneParam.leafComponentType().isRawType()) {
						// A#RAW is not more specific than a rawified A<T>
						if (oneParam.needsUncheckedConversion(two.declaringClass.isRawType() ? twoParam : two.original().parameters[i]))
							return false;
					}
				} else {
					if (i == oneParamsLength - 1 && one.isVarargs() && two.isVarargs()) {
						TypeBinding eType = ((ArrayBinding) twoParam).elementsType();
						if (oneParam == eType || oneParam.isCompatibleWith(eType))
							return true; // special case to choose between 2 varargs methods when the last arg is Object[]
					}
					return false;
				}
			}
			return true;
		}

		if (one.isVarargs() && two.isVarargs()) {
			if (oneParamsLength > twoParamsLength) {
				// special case when autoboxing makes (int, int...) better than (Object...) but not (int...) or (Integer, int...)
				if (((ArrayBinding) twoParams[twoParamsLength - 1]).elementsType().id != TypeIds.T_JavaLangObject)
					return false;
			}
			// check that each parameter before the vararg parameters are compatible (no autoboxing allowed here)
			for (int i = (oneParamsLength > twoParamsLength ? twoParamsLength : oneParamsLength) - 2; i >= 0; i--)
				if (oneParams[i] != twoParams[i] && !oneParams[i].isCompatibleWith(twoParams[i]))
					return false;
			if (parameterCompatibilityLevel(one, twoParams) == NOT_COMPATIBLE
				&& parameterCompatibilityLevel(two, oneParams) == VARARGS_COMPATIBLE)
					return true; 
		}
		return false;
	}

	public boolean isBoxingCompatibleWith(TypeBinding expressionType, TypeBinding targetType) {
		LookupEnvironment environment = environment();
		if (environment.globalOptions.sourceLevel < ClassFileConstants.JDK1_5 || expressionType.isBaseType() == targetType.isBaseType())
			return false;
	
		// check if autoboxed type is compatible
		TypeBinding convertedType = environment.computeBoxingType(expressionType);
		return convertedType == targetType || convertedType.isCompatibleWith(targetType);
	}

	/* Answer true if the scope is nested inside a given field declaration.
	 * Note: it works as long as the scope.fieldDeclarationIndex is reflecting the field being traversed 
	 * e.g. during name resolution.
	*/
	public final boolean isDefinedInField(FieldBinding field) {
		Scope scope = this;
		do {
			if (scope instanceof MethodScope) {
				MethodScope methodScope = (MethodScope) scope;
				if (methodScope.initializedField == field) return true;
			}
			scope = scope.parent;
		} while (scope != null);
		return false;
	}

	/* Answer true if the scope is nested inside a given method declaration
	*/
	public final boolean isDefinedInMethod(MethodBinding method) {
		Scope scope = this;
		do {
			if (scope instanceof MethodScope) {
				ReferenceContext refContext = ((MethodScope) scope).referenceContext;
				if (refContext instanceof AbstractMethodDeclaration)
					if (((AbstractMethodDeclaration) refContext).binding == method)
						return true;
			}
			scope = scope.parent;
		} while (scope != null);
		return false;
	}

	/* Answer whether the type is defined in the same compilation unit as the receiver
	*/
	public final boolean isDefinedInSameUnit(ReferenceBinding type) {
		// find the outer most enclosing type
		ReferenceBinding enclosingType = type;
		while ((type = enclosingType.enclosingType()) != null)
			enclosingType = type;

		// find the compilation unit scope
		Scope scope, unitScope = this;
		while ((scope = unitScope.parent) != null)
			unitScope = scope;

		// test that the enclosingType is not part of the compilation unit
		SourceTypeBinding[] topLevelTypes = ((CompilationUnitScope) unitScope).topLevelTypes;
		for (int i = topLevelTypes.length; --i >= 0;)
			if (topLevelTypes[i] == enclosingType)
				return true;
		return false;
	}
		
	/* Answer true if the scope is nested inside a given type declaration
	*/
	public final boolean isDefinedInType(ReferenceBinding type) {
		Scope scope = this;
		do {
			if (scope instanceof ClassScope)
				if (((ClassScope) scope).referenceContext.binding == type)
					return true;
			scope = scope.parent;
		} while (scope != null);
		return false;
	}

	/** 
	 * Returns true if the scope or one of its parent is associated to a given caseStatement, denoting
	 * being part of a given switch case statement.
	 */
	public boolean isInsideCase(CaseStatement caseStatement) {
		Scope scope = this;
		do {
			switch (scope.kind) {
				case Scope.BLOCK_SCOPE :
					if (((BlockScope) scope).enclosingCase == caseStatement) {
						return true;
					}
			}
			scope = scope.parent;
		} while (scope != null);
		return false;
	}
	
	public boolean isInsideDeprecatedCode(){
		switch(this.kind){
			case Scope.BLOCK_SCOPE :
			case Scope.METHOD_SCOPE :
				MethodScope methodScope = methodScope();
				if (!methodScope.isInsideInitializer()){
					// check method modifiers to see if deprecated
					MethodBinding context = ((AbstractMethodDeclaration)methodScope.referenceContext).binding;
					if (context != null && context.isViewedAsDeprecated())
						return true;
				} else {
					SourceTypeBinding type = ((BlockScope)this).referenceType().binding;
					// inside field declaration ? check field modifier to see if deprecated
					if (methodScope.initializedField != null && methodScope.initializedField.isViewedAsDeprecated())
						return true;
					if (type != null) {
						type.initializeDeprecatedAnnotationTagBits(); // may not have been resolved until then
						if (type.isViewedAsDeprecated())
							return true;
					}
				}
				break;
			case Scope.CLASS_SCOPE :
				ReferenceBinding context = ((ClassScope)this).referenceType().binding;
				if (context != null) {
					context.initializeDeprecatedAnnotationTagBits(); // may not have been resolved until then
					if (context.isViewedAsDeprecated()) 
						return true;
				}
				break;
			case Scope.COMPILATION_UNIT_SCOPE :
				// consider import as being deprecated if first type is itself deprecated (123522)
				CompilationUnitDeclaration unit = referenceCompilationUnit();
				if (unit.types != null && unit.types.length > 0) {
					SourceTypeBinding type = unit.types[0].binding;
					if (type != null) {
						type.initializeDeprecatedAnnotationTagBits(); // may not have been resolved until then
						if (type.isViewedAsDeprecated())
							return true;
					}
				}
		}
		return false;
	}

	private TypeBinding leastContainingInvocation(TypeBinding mec, Object invocationData, List lubStack) {
		if (invocationData == null) return mec; // no alternate invocation
		if (invocationData instanceof TypeBinding) { // only one invocation, simply return it (array only allocated if more than one)
			return (TypeBinding) invocationData;
		}
		TypeBinding[] invocations = (TypeBinding[]) invocationData;

		// if mec is an array type, intersect invocation leaf component types, then promote back to array
		int dim = mec.dimensions();
		mec = mec.leafComponentType();
		
		int argLength = mec.typeVariables().length;
		if (argLength == 0) return mec; // should be caught by no invocation check

		// infer proper parameterized type from invocations
		TypeBinding[] bestArguments = new TypeBinding[argLength];
		for (int i = 0, length = invocations.length; i < length; i++) {
			TypeBinding invocation = invocations[i].leafComponentType();
			switch (invocation.kind()) {
				case Binding.GENERIC_TYPE :
					TypeVariableBinding[] invocationVariables = invocation.typeVariables();
					for (int j = 0; j < argLength; j++) {
						TypeBinding bestArgument = leastContainingTypeArgument(bestArguments[j], invocationVariables[j], (ReferenceBinding) mec, j, lubStack);
						if (bestArgument == null) return null;
						bestArguments[j] = bestArgument;
					}
					break;
				case Binding.PARAMETERIZED_TYPE :
					ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding)invocation;
					for (int j = 0; j < argLength; j++) {
						TypeBinding bestArgument = leastContainingTypeArgument(bestArguments[j], parameterizedType.arguments[j], (ReferenceBinding) mec, j, lubStack);
						if (bestArgument == null) return null;
						bestArguments[j] = bestArgument;
					}
					break;
				case Binding.RAW_TYPE :
					return dim == 0 ? invocation : environment().createArrayType(invocation, dim); // raw type is taking precedence
			}
		}
		TypeBinding least = environment().createParameterizedType((ReferenceBinding) mec.erasure(), bestArguments, mec.enclosingType());
		return dim == 0 ? least : environment().createArrayType(least, dim);
	}
	
	// JLS 15.12.2
	private TypeBinding leastContainingTypeArgument(TypeBinding u, TypeBinding v, ReferenceBinding genericType, int rank, List lubStack) {
		if (u == null) return v;
		if (u == v) return u;
		if (v.isWildcard()) {
			WildcardBinding wildV = (WildcardBinding) v;
			if (u.isWildcard()) {
				WildcardBinding wildU = (WildcardBinding) u;
				switch (wildU.boundKind) {
					// ? extends U
					case Wildcard.EXTENDS :
						switch(wildV.boundKind) {
							// ? extends U, ? extends V
							case Wildcard.EXTENDS :  
								TypeBinding lub = lowerUpperBound(new TypeBinding[]{wildU.bound,wildV.bound}, lubStack);
								if (lub == null) return null;
								// int is returned to denote cycle detected in lub computation - stop recursion by answering unbound wildcard
								if (lub == TypeBinding.INT) return environment().createWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND);
								return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);	
							// ? extends U, ? SUPER V
							case Wildcard.SUPER : 
								if (wildU.bound == wildV.bound) return wildU.bound;
								return environment().createWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND);
						}
						break;
						// ? super U
					case Wildcard.SUPER : 
						// ? super U, ? super V
						if (wildU.boundKind == Wildcard.SUPER) {
							TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound,wildV.bound});
							if (glb == null) return null;
							return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER);	// TODO (philippe) need to capture entire bounds
						}
				}				
			} else {
				switch (wildV.boundKind) {
					// U, ? extends V
					case Wildcard.EXTENDS :
						TypeBinding lub = lowerUpperBound(new TypeBinding[]{u,wildV.bound}, lubStack);
						if (lub == null) return null;
						// int is returned to denote cycle detected in lub computation - stop recursion by answering unbound wildcard
						if (lub == TypeBinding.INT) return environment().createWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND);
						return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);	
					// U, ? super V
					case Wildcard.SUPER :
						TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{u,wildV.bound});
						if (glb == null) return null;
						return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER);	// TODO (philippe) need to capture entire bounds
					case Wildcard.UNBOUND :
				}
			}
		} else if (u.isWildcard()) {
			WildcardBinding wildU = (WildcardBinding) u;
			switch (wildU.boundKind) {
				// U, ? extends V
				case Wildcard.EXTENDS :
					TypeBinding lub = lowerUpperBound(new TypeBinding[]{wildU.bound, v}, lubStack);
					if (lub == null) return null;
					// int is returned to denote cycle detected in lub computation - stop recursion by answering unbound wildcard
					if (lub == TypeBinding.INT) return environment().createWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND);
					return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);	
				// U, ? super V
				case Wildcard.SUPER :
					TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound, v});
					if (glb == null) return null;
					return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER); // TODO (philippe) need to capture entire bounds		
				case Wildcard.UNBOUND :
			}
		}
		TypeBinding lub = lowerUpperBound(new TypeBinding[]{u,v}, lubStack);
		if (lub == null) return null;
		// int is returned to denote cycle detected in lub computation - stop recursion by answering unbound wildcard
		if (lub == TypeBinding.INT) return environment().createWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND);
		return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);
	}

	// 15.12.2
	/**
	 * Returns VoidBinding if types have no intersection (e.g. 2 unrelated interfaces), or null if
	 * no common supertype (e.g. List<String> and List<Exception>), or the intersection type if possible
	 */
	public TypeBinding lowerUpperBound(TypeBinding[] types) {
		int typeLength = types.length;
		if (typeLength == 1) {
			TypeBinding type = types[0];
			return type == null ? TypeBinding.VOID : type;
		}		
		return lowerUpperBound(types, new ArrayList(1));
	}
	
	// 15.12.2
	private TypeBinding lowerUpperBound(TypeBinding[] types, List lubStack) {
		
		int typeLength = types.length;
		if (typeLength == 1) {
			TypeBinding type = types[0];
			return type == null ? TypeBinding.VOID : type;
		}
		// cycle detection
		int stackLength = lubStack.size();
		nextLubCheck: for (int i = 0; i < stackLength; i++) {
			TypeBinding[] lubTypes = (TypeBinding[])lubStack.get(i);
			int lubTypeLength = lubTypes.length;
			if (lubTypeLength < typeLength) continue nextLubCheck;
			nextTypeCheck:	for (int j = 0; j < typeLength; j++) {
				TypeBinding type = types[j];
				if (type == null) continue nextTypeCheck; // ignore
				for (int k = 0; k < lubTypeLength; k++) {
					TypeBinding lubType = lubTypes[k];
					if (lubType == null) continue; // ignore
					if (lubType == type || lubType.isEquivalentTo(type)) continue nextTypeCheck; // type found, jump to next one 
				}
				continue nextLubCheck; // type not found in current lubTypes
			}
			// all types are included in some lub, cycle detected - stop recursion by answering special value (int)
			return TypeBinding.INT;
		}

		lubStack.add(types);
		Map invocations = new HashMap(1);
		TypeBinding[] mecs = minimalErasedCandidates(types, invocations);
		if (mecs == null) return null;
		int length = mecs.length;
		if (length == 0) return TypeBinding.VOID;
		int count = 0;
		TypeBinding firstBound = null;
		int commonDim = -1;
		for (int i = 0; i < length; i++) {
			TypeBinding mec = mecs[i];
			if (mec == null) continue;
			mec = leastContainingInvocation(mec, invocations.get(mec), lubStack);
			if (mec == null) return null;
			int dim = mec.dimensions();
			if (commonDim == -1) {
				commonDim = dim;
			} else if (dim != commonDim) { // not all types have same dimension
				return null;
			}
			if (firstBound == null && !mec.leafComponentType().isInterface()) firstBound = mec.leafComponentType();
			mecs[count++] = mec; // recompact them to the front
		}
		switch (count) {
			case 0 : return TypeBinding.VOID;
			case 1 : return mecs[0];
			case 2 : 
				if ((commonDim == 0 ? mecs[1].id : mecs[1].leafComponentType().id) == T_JavaLangObject) return mecs[0];
				if ((commonDim == 0 ? mecs[0].id : mecs[0].leafComponentType().id) == T_JavaLangObject) return mecs[1];
		}
		TypeBinding[] otherBounds = new TypeBinding[count - 1];
		int rank = 0;
		for (int i = 0; i < count; i++) {
			TypeBinding mec = commonDim == 0 ? mecs[i] : mecs[i].leafComponentType();
			if (mec.isInterface()) {
				otherBounds[rank++] = mec;
			}
		}
		TypeBinding intersectionType = environment().createWildcard(null, 0, firstBound, otherBounds, Wildcard.EXTENDS);
		return commonDim == 0 ? intersectionType : environment().createArrayType(intersectionType, commonDim);
	}
	
	public final MethodScope methodScope() {
		Scope scope = this;
		do {
			if (scope instanceof MethodScope)
				return (MethodScope) scope;
			scope = scope.parent;
		} while (scope != null);
		return null;
	}

	/**
	 * Returns the most specific set of types compatible with all given types.
	 * (i.e. most specific common super types)
	 * If no types is given, will return an empty array. If not compatible 
	 * reference type is found, returns null. In other cases, will return an array 
	 * of minimal erased types, where some nulls may appear (and must simply be
	 * ignored).
	 */
	protected TypeBinding[] minimalErasedCandidates(TypeBinding[] types, Map allInvocations) {
		int length = types.length;
		int indexOfFirst = -1, actualLength = 0;
		for (int i = 0; i < length; i++) {
			TypeBinding type = types[i];
			if (type == null) continue;
			if (type.isBaseType()) return null;
			if (indexOfFirst < 0) indexOfFirst = i;
			actualLength ++;
		}
		switch (actualLength) {
			case 0: return Binding.NO_TYPES;
			case 1: return types;
		}
		TypeBinding firstType = types[indexOfFirst];
		if (firstType.isBaseType()) return null; 

		// record all supertypes of type
		// intersect with all supertypes of otherType
		ArrayList typesToVisit = new ArrayList(5);
		
		int dim = firstType.dimensions();
		TypeBinding leafType = firstType.leafComponentType();
		TypeBinding firstErasure = (leafType.isTypeVariable() || leafType.isWildcard()/*&& !leafType.isCapture()*/) ? firstType : firstType.erasure();
		if (firstErasure != firstType) {
			allInvocations.put(firstErasure, firstType);
		}						
		typesToVisit.add(firstType);
		int max = 1;
		ReferenceBinding currentType;
		for (int i = 0; i < max; i++) {
			TypeBinding typeToVisit = (TypeBinding) typesToVisit.get(i);
			dim = typeToVisit.dimensions();
			if (dim > 0) {
				leafType = typeToVisit.leafComponentType();
				switch(leafType.id) {
					case T_JavaLangObject:
						if (dim > 1) { // Object[][] supertype is Object[]
							TypeBinding elementType = ((ArrayBinding)typeToVisit).elementsType();
							if (!typesToVisit.contains(elementType)) {
								typesToVisit.add(elementType);
								max++;
							}
							continue;
						}
						// fallthrough
					case T_byte:
					case T_short:
					case T_char:
					case T_boolean:
					case T_int:
					case T_long:
					case T_float:
					case T_double:
						TypeBinding superType = getJavaIoSerializable();
						if (!typesToVisit.contains(superType)) {
							typesToVisit.add(superType);
							max++;
						}
						superType = getJavaLangCloneable();
						if (!typesToVisit.contains(superType)) {
							typesToVisit.add(superType);
							max++;
						}
						superType = getJavaLangObject();
						if (!typesToVisit.contains(superType)) {
							typesToVisit.add(superType);
							max++;
						}
						continue;
					
					default:
				}
				typeToVisit = leafType;
			}
			currentType = (ReferenceBinding) typeToVisit;
			if (currentType.isCapture()) {
				TypeBinding firstBound = ((CaptureBinding) currentType).firstBound;
				if (firstBound != null && firstBound.isArrayType()) {
					TypeBinding superType = dim == 0 ? firstBound : (TypeBinding)environment().createArrayType(firstBound, dim); // recreate array if needed
					if (!typesToVisit.contains(superType)) {
						typesToVisit.add(superType);
						max++;
						TypeBinding superTypeErasure = (firstBound.isTypeVariable() || firstBound.isWildcard() /*&& !itsInterface.isCapture()*/) ? superType : superType.erasure();
						if (superTypeErasure != superType) {
							allInvocations.put(superTypeErasure, superType);
						}						
					}
					continue;
				}
			}
			// inject super interfaces prior to superclass
			ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
			if (itsInterfaces != null) { // can be null during code assist operations that use LookupEnvironment.completeTypeBindings(parsedUnit, buildFieldsAndMethods)
				for (int j = 0, count = itsInterfaces.length; j < count; j++) {
					TypeBinding itsInterface = itsInterfaces[j];
					TypeBinding superType = dim == 0 ? itsInterface : (TypeBinding)environment().createArrayType(itsInterface, dim); // recreate array if needed
					if (!typesToVisit.contains(superType)) {
						typesToVisit.add(superType);
						max++;
						TypeBinding superTypeErasure = (itsInterface.isTypeVariable() || itsInterface.isWildcard() /*&& !itsInterface.isCapture()*/) ? superType : superType.erasure();
						if (superTypeErasure != superType) {
							allInvocations.put(superTypeErasure, superType);
						}						
					}
				}
			}
			TypeBinding itsSuperclass = currentType.superclass();
			if (itsSuperclass != null) {
				TypeBinding superType = dim == 0 ? itsSuperclass : (TypeBinding)environment().createArrayType(itsSuperclass, dim); // recreate array if needed
				if (!typesToVisit.contains(superType)) {
					typesToVisit.add(superType);
					max++;
					TypeBinding superTypeErasure = (itsSuperclass.isTypeVariable() || itsSuperclass.isWildcard() /*&& !itsSuperclass.isCapture()*/) ? superType : superType.erasure();
					if (superTypeErasure != superType) {
						allInvocations.put(superTypeErasure, superType);
					}
				}
			}
		}
		int superLength = typesToVisit.size();
		TypeBinding[] erasedSuperTypes = new TypeBinding[superLength];
		int rank = 0;
		for (Iterator iter = typesToVisit.iterator(); iter.hasNext();) {
			TypeBinding type = (TypeBinding)iter.next();
			leafType = type.leafComponentType();
			erasedSuperTypes[rank++] = (leafType.isTypeVariable() || leafType.isWildcard() /*&& !leafType.isCapture()*/) ? type : type.erasure();
		}
		// intersecting first type supertypes with other types' ones, nullifying non matching supertypes
		int remaining = superLength;
		nextOtherType: for (int i = indexOfFirst+1; i < length; i++) {
			TypeBinding otherType = types[i];
			if (otherType == null) continue nextOtherType;
			if (otherType.isArrayType()) {
				nextSuperType: for (int j = 0; j < superLength; j++) {
					TypeBinding erasedSuperType = erasedSuperTypes[j];
					if (erasedSuperType == null || erasedSuperType == otherType) continue nextSuperType;
					TypeBinding match;
					if ((match = otherType.findSuperTypeWithSameErasure(erasedSuperType)) == null) {
						erasedSuperTypes[j] = null;
						if (--remaining == 0) return null;
						continue nextSuperType;
					}
					// record invocation
					Object invocationData = allInvocations.get(erasedSuperType);
					if (invocationData == null) {
						allInvocations.put(erasedSuperType, match); // no array for singleton
					} else if (invocationData instanceof TypeBinding) {
						if (match != invocationData) {
							// using an array to record invocations in order (188103)
							TypeBinding[] someInvocations = { (TypeBinding) invocationData, match, };
							allInvocations.put(erasedSuperType, someInvocations);
						}
					} else { // using an array to record invocations in order (188103)
						TypeBinding[] someInvocations = (TypeBinding[]) invocationData;
						checkExisting: {
							int invocLength = someInvocations.length;
							for (int k = 0; k < invocLength; k++) {
								if (someInvocations[k] == match) break checkExisting;
							}
							System.arraycopy(someInvocations, 0, someInvocations = new TypeBinding[invocLength+1], 0, invocLength);
							allInvocations.put(erasedSuperType, someInvocations);
							someInvocations[invocLength] = match;
						}
					}
				}
				continue nextOtherType;
			}
			nextSuperType: for (int j = 0; j < superLength; j++) {
				TypeBinding erasedSuperType = erasedSuperTypes[j];
				if (erasedSuperType == null) continue nextSuperType;
				TypeBinding match;
				if (erasedSuperType == otherType || erasedSuperType.id == T_JavaLangObject && otherType.isInterface()) {
					match = erasedSuperType;
				} else {
					if (erasedSuperType.isArrayType()) {
						match = null;
					} else {
						match = otherType.findSuperTypeWithSameErasure(erasedSuperType);
					}
					if (match == null) { // incompatible super type
						erasedSuperTypes[j] = null;
						if (--remaining == 0) return null;
						continue nextSuperType;
					}
				}
				// record invocation
				Object invocationData = allInvocations.get(erasedSuperType);
				if (invocationData == null) {
					allInvocations.put(erasedSuperType, match); // no array for singleton
				} else if (invocationData instanceof TypeBinding) {
					if (match != invocationData) {
						// using an array to record invocations in order (188103)
						TypeBinding[] someInvocations = { (TypeBinding) invocationData, match, };
						allInvocations.put(erasedSuperType, someInvocations);
					}
				} else { // using an array to record invocations in order (188103)
					TypeBinding[] someInvocations = (TypeBinding[]) invocationData;
					checkExisting: {
						int invocLength = someInvocations.length;
						for (int k = 0; k < invocLength; k++) {
							if (someInvocations[k] == match) break checkExisting;
						}
						System.arraycopy(someInvocations, 0, someInvocations = new TypeBinding[invocLength+1], 0, invocLength);
						allInvocations.put(erasedSuperType, someInvocations);
						someInvocations[invocLength] = match;
					}
				}
			}				
		}
		// eliminate non minimal super types
		if (remaining > 1) {
			nextType: for (int i = 0; i < superLength; i++) {
				TypeBinding erasedSuperType = erasedSuperTypes[i];
				if (erasedSuperType == null) continue nextType;
				nextOtherType: for (int j = 0; j < superLength; j++) {
					if (i == j) continue nextOtherType;
					TypeBinding otherType = erasedSuperTypes[j];
					if (otherType == null) continue nextOtherType;
					if (erasedSuperType instanceof ReferenceBinding) {
						if (otherType.id == T_JavaLangObject && erasedSuperType.isInterface()) continue nextOtherType; // keep Object for an interface
						if (erasedSuperType.findSuperTypeWithSameErasure(otherType) != null) {
							erasedSuperTypes[j] = null; // discard non minimal supertype
							remaining--;
						}
					} else if (erasedSuperType.isArrayType()) {
					if (otherType.isArrayType() // keep Object[...] for an interface array (same dimensions)
							&& otherType.leafComponentType().id == T_JavaLangObject
							&& otherType.dimensions() == erasedSuperType.dimensions()
							&& erasedSuperType.leafComponentType().isInterface()) continue nextOtherType;
						if (erasedSuperType.findSuperTypeWithSameErasure(otherType) != null) {
							erasedSuperTypes[j] = null; // discard non minimal supertype
							remaining--;
						}
					}
				}
			}
		}
		return erasedSuperTypes;
	}
	
	// Internal use only
	/* All methods in visible are acceptable matches for the method in question...
	* The methods defined by the receiver type appear before those defined by its
	* superclass and so on. We want to find the one which matches best.
	*
	* Since the receiver type is a class, we know each method's declaring class is
	* either the receiver type or one of its superclasses. It is an error if the best match
	* is defined by a superclass, when a lesser match is defined by the receiver type
	* or a closer superclass.
	*/
	protected final MethodBinding mostSpecificClassMethodBinding(MethodBinding[] visible, int visibleSize, InvocationSite invocationSite) {
		MethodBinding previous = null;
		nextVisible : for (int i = 0; i < visibleSize; i++) {
			MethodBinding method = visible[i];
			if (previous != null && method.declaringClass != previous.declaringClass)
				break; // cannot answer a method farther up the hierarchy than the first method found

			if (!method.isStatic()) previous = method; // no ambiguity for static methods
			for (int j = 0; j < visibleSize; j++) {
				if (i == j) continue;
				if (!visible[j].areParametersCompatibleWith(method.parameters))
					continue nextVisible;
			}
			compilationUnitScope().recordTypeReferences(method.thrownExceptions);
			return method;
		}
		return new ProblemMethodBinding(visible[0], visible[0].selector, visible[0].parameters, ProblemReasons.Ambiguous);
	}
	
	// Internal use only
	/* All methods in visible are acceptable matches for the method in question...
	* Since the receiver type is an interface, we ignore the possibility that 2 inherited
	* but unrelated superinterfaces may define the same method in acceptable but
	* not identical ways... we just take the best match that we find since any class which
	* implements the receiver interface MUST implement all signatures for the method...
	* in which case the best match is correct.
	*
	* NOTE: This is different than javac... in the following example, the message send of
	* bar(X) in class Y is supposed to be ambiguous. But any class which implements the
	* interface I MUST implement both signatures for bar. If this class was the receiver of
	* the message send instead of the interface I, then no problem would be reported.
	*
	interface I1 {
		void bar(J j);
	}
	interface I2 {
	//	void bar(J j);
		void bar(Object o);
	}
	interface I extends I1, I2 {}
	interface J {}
	
	class X implements J {}
	
	class Y extends X {
		public void foo(I i, X x) { i.bar(x); }
	}
	*/
	protected final MethodBinding mostSpecificInterfaceMethodBinding(MethodBinding[] visible, int visibleSize, InvocationSite invocationSite) {
		nextVisible : for (int i = 0; i < visibleSize; i++) {
			MethodBinding method = visible[i];
			for (int j = 0; j < visibleSize; j++) {
				if (i == j) continue;
				if (!visible[j].areParametersCompatibleWith(method.parameters))
					continue nextVisible;
			}
			compilationUnitScope().recordTypeReferences(method.thrownExceptions);
			return method;
		}
		return new ProblemMethodBinding(visible[0], visible[0].selector, visible[0].parameters, ProblemReasons.Ambiguous);
	}

	// caveat: this is not a direct implementation of JLS
	protected final MethodBinding mostSpecificMethodBinding(MethodBinding[] visible, int visibleSize, TypeBinding[] argumentTypes, InvocationSite invocationSite, ReferenceBinding receiverType) {
		int[] compatibilityLevels = new int[visibleSize];
		for (int i = 0; i < visibleSize; i++)
			compatibilityLevels[i] = parameterCompatibilityLevel(visible[i], argumentTypes);

		boolean useTiebreakMethod = invocationSite.genericTypeArguments() == null;
		MethodBinding[] moreSpecific = new MethodBinding[visibleSize];
		int count = 0;
		for (int level = 0, max = VARARGS_COMPATIBLE; level <= max; level++) {
			nextVisible : for (int i = 0; i < visibleSize; i++) {
				if (compatibilityLevels[i] != level) continue nextVisible;
				max = level; // do not examine further categories, will either return mostSpecific or report ambiguous case
				MethodBinding current = visible[i];
				MethodBinding original = current.original();
				MethodBinding tiebreakMethod = useTiebreakMethod ? current.tiebreakMethod() : current;
				for (int j = 0; j < visibleSize; j++) {
					if (i == j || compatibilityLevels[j] != level) continue;
					MethodBinding next = visible[j];
					if (original == next.original()) {
						// parameterized superclasses & interfaces may be walked twice from different paths so skip next from now on
						compatibilityLevels[j] = -1; 
						continue;
					}

					MethodBinding methodToTest = next;
					if (next instanceof ParameterizedGenericMethodBinding) {
						ParameterizedGenericMethodBinding pNext = (ParameterizedGenericMethodBinding) next;
						if (pNext.isRaw && !pNext.isStatic()) {
							// hold onto the raw substituted method
						} else {
							methodToTest = pNext.originalMethod;
						}
					}
					MethodBinding acceptable = computeCompatibleMethod(methodToTest, tiebreakMethod.parameters, invocationSite);
					/* There are 4 choices to consider with current & next :
					 foo(B) & foo(A) where B extends A
					 1. the 2 methods are equal (both accept each others parameters) -> want to continue
					 2. current has more specific parameters than next (so acceptable is a valid method) -> want to continue
					 3. current has less specific parameters than next (so acceptable is null) -> go on to next
					 4. current and next are not compatible with each other (so acceptable is null) -> go on to next
					 */
					if (acceptable == null || !acceptable.isValidBinding())
						continue nextVisible;
					if (!isAcceptableMethod(tiebreakMethod, acceptable))
						continue nextVisible;
					// pick a concrete method over a bridge method when parameters are equal since the return type of the concrete method is more specific
					if (current.isBridge() && !next.isBridge())
						if (tiebreakMethod.areParametersEqual(acceptable))
							continue nextVisible; // skip current so acceptable wins over this bridge method
				}
				moreSpecific[i] = current;
				count++;
			}
		}
		if (count == 1) {
			for (int i = 0; i < visibleSize; i++) {
				if (moreSpecific[i] != null) {
					compilationUnitScope().recordTypeReferences(visible[i].thrownExceptions);
					return visible[i];
				}
			}
		} else if (count == 0) {
			return new ProblemMethodBinding(visible[0], visible[0].selector, visible[0].parameters, ProblemReasons.Ambiguous);
		}

		// found several methods that are mutually acceptable -> must be equal
		// so now with the first acceptable method, find the 'correct' inherited method for each other acceptable method AND
		// see if they are equal after substitution of type variables (do the type variables have to be equal to be considered an override???)
		if (receiverType != null)
			receiverType = receiverType instanceof CaptureBinding ? receiverType : (ReferenceBinding) receiverType.erasure();
		nextSpecific : for (int i = 0; i < visibleSize; i++) {
			MethodBinding current = moreSpecific[i];
			if (current != null) {
				ReferenceBinding[] mostSpecificExceptions = null;
				SimpleSet possibleMethods = null;
				MethodBinding original = current.original();
				for (int j = 0; j < visibleSize; j++) {
					MethodBinding next = moreSpecific[j];
					if (next == null || i == j) continue;
					MethodBinding original2 = next.original();
					if (original.declaringClass == original2.declaringClass)
						break nextSpecific; // duplicates thru substitution

					if (!original.isAbstract()) {
						if (original2.isAbstract())
							continue; // only compare current against other concrete methods
						TypeBinding superType = original.declaringClass.findSuperTypeWithSameErasure(original2.declaringClass.erasure());
						if (superType == null)
							continue nextSpecific; // current's declaringClass is not a subtype of next's declaringClass
						if (current.hasSubstitutedParameters() || original.typeVariables != Binding.NO_TYPE_VARIABLES) {
							if (original2.declaringClass != superType) {
								// must find inherited method with the same substituted variables
								MethodBinding[] superMethods = ((ReferenceBinding) superType).getMethods(original2.selector);
								for (int m = 0, l = superMethods.length; m < l; m++) {
									if (superMethods[m].original() == original2) {
										original2 = superMethods[m];
										break;
									}
								}
							}
							if (original.typeVariables != Binding.NO_TYPE_VARIABLES)
								original2 = original.computeSubstitutedMethod(original2, environment());
							if (original2 == null || !original.areParametersEqual(original2))
								continue nextSpecific; // current does not override next
						}
					} else if (receiverType != null) { // should not be null if original isAbstract, but be safe
						TypeBinding superType = receiverType.findSuperTypeWithSameErasure(original.declaringClass.erasure());
						if (original.declaringClass == superType || !(superType instanceof ReferenceBinding)) {
							// keep original
						} else {
							// must find inherited method with the same substituted variables
							MethodBinding[] superMethods = ((ReferenceBinding) superType).getMethods(original.selector);
							for (int m = 0, l = superMethods.length; m < l; m++) {
								if (superMethods[m].original() == original) {
									original = superMethods[m];
									break;
								}
							}
						}
						superType = receiverType.findSuperTypeWithSameErasure(original2.declaringClass.erasure());
						if (original2.declaringClass == superType || !(superType instanceof ReferenceBinding)) {
							// keep original2
						} else {
							// must find inherited method with the same substituted variables
							MethodBinding[] superMethods = ((ReferenceBinding) superType).getMethods(original2.selector);
							for (int m = 0, l = superMethods.length; m < l; m++) {
								if (superMethods[m].original() == original2) {
									original2 = superMethods[m];
									break;
								}
							}
						}
						if (original.typeVariables != Binding.NO_TYPE_VARIABLES)
							original2 = original.computeSubstitutedMethod(original2, environment());
						if (original2 == null || !original.areParameterErasuresEqual(original2))
							continue nextSpecific; // current does not override next
						if (!original.returnType.isCompatibleWith(original2.returnType) &&
								!original.returnType.erasure().isCompatibleWith(original2.returnType.erasure())) {
							// 15.12.2
							continue nextSpecific; // choose original2 instead
						}
						if (original.thrownExceptions != original2.thrownExceptions) {
							if (mostSpecificExceptions == null)
								mostSpecificExceptions = original.thrownExceptions;
							if (possibleMethods == null)
								possibleMethods = new SimpleSet(3);
							int mostSpecificLength = mostSpecificExceptions.length;
							int original2Length = original2.thrownExceptions.length;
							SimpleSet temp = new SimpleSet(mostSpecificLength);
							nextException : for (int t = 0; t < mostSpecificLength; t++) {
								ReferenceBinding exception = mostSpecificExceptions[t];
								for (int s = 0; s < original2Length; s++) {
									if (exception.isCompatibleWith(original2.thrownExceptions[s])) {
										possibleMethods.add(current);
										temp.add(exception);
										continue nextException;
									} else if (original2.thrownExceptions[s].isCompatibleWith(exception)) {
										possibleMethods.add(next);
										temp.add(original2.thrownExceptions[s]);
										continue nextException;
									}
								}
							}
							mostSpecificExceptions = temp.elementSize == 0 ? Binding.NO_EXCEPTIONS : new ReferenceBinding[temp.elementSize];
							temp.asArray(mostSpecificExceptions);
						}
					}
				}
				if (mostSpecificExceptions != null) {
					Object[] values = possibleMethods.values;
					int exceptionLength = mostSpecificExceptions.length;
					nextMethod : for (int p = 0, vLength = values.length; p < vLength; p++) {
						MethodBinding possible = (MethodBinding) values[p];
						if (possible == null) continue nextMethod;
						ReferenceBinding[] itsExceptions = possible.thrownExceptions;
						if (itsExceptions.length == exceptionLength) {
							nextException : for (int e = 0; e < exceptionLength; e++) {
								ReferenceBinding exception = itsExceptions[e];
								for (int f = 0; f < exceptionLength; f++)
									if (exception == mostSpecificExceptions[f]) continue nextException;
								continue nextMethod;
							}
							return possible;
						}
					}
// do not return a new methodBinding until we know that it does not cause problems
//					return new MethodBinding(
//						current.modifiers,
//						current.selector,
//						current.returnType,
//						current.parameters,
//						mostSpecificExceptions,
//						current.declaringClass
//					);
				}
				return current;
			}
		}

		// if all moreSpecific methods are equal then see if duplicates exist because of substitution
		return new ProblemMethodBinding(visible[0], visible[0].selector, visible[0].parameters, ProblemReasons.Ambiguous);
	}

	public final ClassScope outerMostClassScope() {
		ClassScope lastClassScope = null;
		Scope scope = this;
		do {
			if (scope instanceof ClassScope)
				lastClassScope = (ClassScope) scope;
			scope = scope.parent;
		} while (scope != null);
		return lastClassScope; // may answer null if no class around
	}

	public final MethodScope outerMostMethodScope() {
		MethodScope lastMethodScope = null;
		Scope scope = this;
		do {
			if (scope instanceof MethodScope)
				lastMethodScope = (MethodScope) scope;
			scope = scope.parent;
		} while (scope != null);
		return lastMethodScope; // may answer null if no method around
	}

	public int parameterCompatibilityLevel(MethodBinding method, TypeBinding[] arguments) {
		TypeBinding[] parameters = method.parameters;
		int paramLength = parameters.length;
		int argLength = arguments.length;

		if (compilerOptions().sourceLevel < ClassFileConstants.JDK1_5) {
			if (paramLength != argLength)
				return NOT_COMPATIBLE;
			for (int i = 0; i < argLength; i++) {
				TypeBinding param = parameters[i];
				TypeBinding arg = arguments[i];
				if (arg != param && !arg.isCompatibleWith(param))
					return NOT_COMPATIBLE;
			}
			return COMPATIBLE;
		}

		int level = COMPATIBLE; // no autoboxing or varargs support needed
		int lastIndex = argLength;
		LookupEnvironment env = environment();
		if (method.isVarargs()) {
			lastIndex = paramLength - 1;
			if (paramLength == argLength) { // accept X or X[] but not X[][]
				TypeBinding param = parameters[lastIndex]; // is an ArrayBinding by definition
				TypeBinding arg = arguments[lastIndex];
				if (param != arg) {
					level = parameterCompatibilityLevel(arg, param, env);
					if (level == NOT_COMPATIBLE) {
						// expect X[], is it called with X
						param = ((ArrayBinding) param).elementsType();
						if (parameterCompatibilityLevel(arg, param, env) == NOT_COMPATIBLE)
							return NOT_COMPATIBLE;
						level = VARARGS_COMPATIBLE; // varargs support needed
					}
				}
			} else {
				if (paramLength < argLength) { // all remaining argument types must be compatible with the elementsType of varArgType
					TypeBinding param = ((ArrayBinding) parameters[lastIndex]).elementsType();
					for (int i = lastIndex; i < argLength; i++) {
						TypeBinding arg = arguments[i];
						if (param != arg && parameterCompatibilityLevel(arg, param, env) == NOT_COMPATIBLE)
							return NOT_COMPATIBLE;
					}
				}  else if (lastIndex != argLength) { // can call foo(int i, X ... x) with foo(1) but NOT foo();
					return NOT_COMPATIBLE;
				}
				level = VARARGS_COMPATIBLE; // varargs support needed
			}
		} else if (paramLength != argLength) {
			return NOT_COMPATIBLE;
		}
		// now compare standard arguments from 0 to lastIndex
		for (int i = 0; i < lastIndex; i++) {
			TypeBinding param = parameters[i];
			TypeBinding arg = arguments[i];
			if (arg != param) {
				int newLevel = parameterCompatibilityLevel(arg, param, env);
				if (newLevel == NOT_COMPATIBLE)
					return NOT_COMPATIBLE;
				if (newLevel > level)
					level = newLevel;
			}
		}
		return level;
	}

	private int parameterCompatibilityLevel(TypeBinding arg, TypeBinding param, LookupEnvironment env) {
		// only called if env.options.sourceLevel >= ClassFileConstants.JDK1_5
		if (arg.isCompatibleWith(param))
			return COMPATIBLE;
		if (arg.isBaseType() != param.isBaseType()) {
			TypeBinding convertedType = env.computeBoxingType(arg);
			if (convertedType == param || convertedType.isCompatibleWith(param))
				return AUTOBOX_COMPATIBLE;
		}
		return NOT_COMPATIBLE;
	}

	public abstract ProblemReporter problemReporter();

	public final CompilationUnitDeclaration referenceCompilationUnit() {
		Scope scope, unitScope = this;
		while ((scope = unitScope.parent) != null)
			unitScope = scope;
		return ((CompilationUnitScope) unitScope).referenceContext;
	}
	
	/**
	 * Returns the nearest reference context, starting from current scope.
	 * If starting on a class, it will return current class. If starting on unitScope, returns unit.
	 */
	public ReferenceContext referenceContext() {
		Scope current = this;
		do {
			switch(current.kind) {
				case METHOD_SCOPE :
					return ((MethodScope) current).referenceContext;
				case CLASS_SCOPE :
					return ((ClassScope) current).referenceContext;
				case COMPILATION_UNIT_SCOPE :
					return ((CompilationUnitScope) current).referenceContext;
			}
		} while ((current = current.parent) != null);
		return null;
	}
	
	// start position in this scope - for ordering scopes vs. variables
	int startIndex() {
		return 0;
	}
}
