/*******************************************************************************
 * Copyright (c) 2000, 2018 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
 *     Stephan Herrmann - contributions for
 *     							bug 337868 - [compiler][model] incomplete support for package-info.java when using SearchableEnvironment
 *								bug 186342 - [compiler][null] Using annotations for null checking
 *								bug 365531 - [compiler][null] investigate alternative strategy for internally encoding nullness defaults
 *								bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
 *								bug 392862 - [1.8][compiler][null] Evaluate null annotations on array types
 *								bug 392384 - [1.8][compiler][null] Restore nullness info from type annotations in class files
 *								Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
 *								Bug 415291 - [1.8][null] differentiate type incompatibilities due to null annotations
 *								Bug 392238 - [1.8][compiler][null] Detect semantically invalid null type annotations
 *								Bug 415850 - [1.8] Ensure RunJDTCoreTests can cope with null annotations enabled
 *								Bug 415043 - [1.8][null] Follow-up re null type annotations after bug 392099
 *								Bug 416183 - [1.8][compiler][null] Overload resolution fails with null annotations
 *								Bug 416307 - [1.8][compiler][null] subclass with type parameter substitution confuses null checking
 *								Bug 417295 - [1.8[[null] Massage type annotated null analysis to gel well with deep encoded type bindings.
 *								Bug 416190 - [1.8][null] detect incompatible overrides due to null type annotations
 *								Bug 424624 - [1.8][null] if a static-object with annotation @NonNull is used, a warning is shown
 *								Bug 438458 - [1.8][null] clean up handling of null type annotations wrt type variables
 *								Bug 439516 - [1.8][null] NonNullByDefault wrongly applied to implicit type bound of binary type
 *								Bug 434602 - Possible error with inferred null annotations leading to contradictory null annotations
 *								Bug 435805 - [1.8][compiler][null] Java 8 compiler does not recognize declaration style null annotations
 *								Bug 453475 - [1.8][null] Contradictory null annotations (4.5 M3 edition)
 *								Bug 457079 - Regression: type inference
 *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
 *								Bug 455180 - IllegalStateException in AnnotatableTypeSystem.getRawType
 *								Bug 470467 - [null] Nullness of special Enum methods not detected from .class file
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFilePool;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.*;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.util.HashtableOfModule;
import org.eclipse.jdt.internal.compiler.util.HashtableOfPackage;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;

@SuppressWarnings({"rawtypes", "unchecked"})
public class LookupEnvironment implements ProblemReasons, TypeConstants {

	/**
	 * Map from typeBinding -> accessRestriction rule
	 */
	private Map accessRestrictions;
	ImportBinding[] defaultImports;				// ROOT_ONLY
	/**
	 * The root environment driving the current compilation.
	 * Other mutable fields in this class marked as ROOT_ONLY must always be accessed from the root environment.
	 * It is assumed that external clients only know the root environment, whereas calls internally in the compiler
	 * have to delegate to root where necessary.
	 * Immutable fields with "global" semantics are SHARED among environments via aliasing.
	 */
	public final LookupEnvironment root;
	public ModuleBinding UnNamedModule;
	public ModuleBinding JavaBaseModule;
	public ModuleBinding module;
	public PackageBinding defaultPackage;
	/** All visible toplevel packages, i.e. observable packages associated with modules read by the current module. */
	HashtableOfPackage knownPackages;
	private int lastCompletedUnitIndex = -1; 	// ROOT_ONLY
	private int lastUnitIndex = -1; 			// ROOT_ONLY

	TypeSystem typeSystem;					 	// SHARED
	
	public INameEnvironment nameEnvironment;	// SHARED
	public CompilerOptions globalOptions;		// SHARED

	public ProblemReporter problemReporter; 	// SHARED
	public ClassFilePool classFilePool; 		// SHARED
	// indicate in which step on the compilation we are.
	// step 1 : build the reference binding
	// step 2 : conect the hierarchy (connect bindings)
	// step 3 : build fields and method bindings.
	private int stepCompleted; 					// ROOT_ONLY
	public ITypeRequestor typeRequestor;		// SHARED

	private SimpleLookupTable uniqueParameterizedGenericMethodBindings;
	
	// key is a string with the method selector value is an array of method bindings
	private SimpleLookupTable uniquePolymorphicMethodBindings;
	private SimpleLookupTable uniqueGetClassMethodBinding; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300734

	boolean useModuleSystem;					// true when compliance >= 9 and nameEnvironment is module aware
	// key is a string with the module name value is a module binding
	public HashtableOfModule knownModules;		// SHARED

	public CompilationUnitDeclaration unitBeingCompleted = null; // only set while completing units -- ROOT_ONLY
	public Object missingClassFileLocation = null; // only set when resolving certain references, to help locating problems
	private CompilationUnitDeclaration[] units = new CompilationUnitDeclaration[4]; // ROOT_ONLY
	private MethodVerifier verifier;

	private ArrayList missingTypes;
	Set<SourceTypeBinding> typesBeingConnected;	// SHARED
	public boolean isProcessingAnnotations = false;
	public boolean mayTolerateMissingType = false;

	PackageBinding nullableAnnotationPackage;			// the package supposed to contain the Nullable annotation type
	PackageBinding nonnullAnnotationPackage;			// the package supposed to contain the NonNull annotation type
	PackageBinding nonnullByDefaultAnnotationPackage;	// the package supposed to contain the NonNullByDefault annotation type

	AnnotationBinding nonNullAnnotation;
	AnnotationBinding nullableAnnotation;

	Map<String,Integer> allNullAnnotations = null;

	final List<MethodBinding> deferredEnumMethods; // SHARED: during early initialization we cannot mark Enum-methods as nonnull.

	/** Global access to the outermost active inference context as the universe for inference variable interning. */
	InferenceContext18 currentInferenceContext;

	final static int BUILD_FIELDS_AND_METHODS = 4;
	final static int BUILD_TYPE_HIERARCHY = 1;
	final static int CHECK_AND_SET_IMPORTS = 2;
	final static int CONNECT_TYPE_HIERARCHY = 3;

	static final ProblemPackageBinding TheNotFoundPackage = new ProblemPackageBinding(CharOperation.NO_CHAR, NotFound);
	static final ProblemReferenceBinding TheNotFoundType = new ProblemReferenceBinding(CharOperation.NO_CHAR_CHAR, null, NotFound);
	static final ModuleBinding TheNotFoundModule = new ModuleBinding(CharOperation.NO_CHAR);

/** Construct the root LookupEnvironment, corresponding to the UnNamedModule. */
public LookupEnvironment(ITypeRequestor typeRequestor, CompilerOptions globalOptions, ProblemReporter problemReporter, INameEnvironment nameEnvironment) {
	this.root = this;
	this.UnNamedModule = new ModuleBinding.UnNamedModule(this);
	this.module = this.UnNamedModule;
	this.typeRequestor = typeRequestor;
	this.globalOptions = globalOptions;
	this.problemReporter = problemReporter;
	this.defaultPackage = new PackageBinding(this); // assume the default package always exists
	this.defaultImports = null;
	this.nameEnvironment = nameEnvironment;
	this.knownPackages = new HashtableOfPackage();
	this.uniqueParameterizedGenericMethodBindings = new SimpleLookupTable(3);
	this.uniquePolymorphicMethodBindings = new SimpleLookupTable(3);
	this.missingTypes = null;
	this.accessRestrictions = new HashMap(3);
	this.classFilePool = ClassFilePool.newInstance();
	this.typesBeingConnected = new HashSet<>();
	this.deferredEnumMethods = new ArrayList<>();
	this.typeSystem = this.globalOptions.sourceLevel >= ClassFileConstants.JDK1_8 && this.globalOptions.storeAnnotations ? new AnnotatableTypeSystem(this) : new TypeSystem(this);
	this.knownModules = new HashtableOfModule();
	this.useModuleSystem = nameEnvironment instanceof IModuleAwareNameEnvironment && globalOptions.complianceLevel >= ClassFileConstants.JDK9;
	this.resolutionListeners = new IQualifiedTypeResolutionListener[0];
}

/** Construct a specific LookupEnvironment, corresponding to the given module. */
LookupEnvironment(LookupEnvironment rootEnv, ModuleBinding module) {
	this.root = rootEnv;
	this.UnNamedModule = rootEnv.UnNamedModule;
	this.module = module;
	this.typeRequestor = rootEnv.typeRequestor;
	this.globalOptions = rootEnv.globalOptions;
	this.problemReporter = rootEnv.problemReporter;
	this.defaultPackage = new PackageBinding(this); // assume the default package always exists
	this.defaultImports = null;
	this.nameEnvironment = rootEnv.nameEnvironment;
	this.knownPackages = new HashtableOfPackage();
	this.uniqueParameterizedGenericMethodBindings = new SimpleLookupTable(3);
	this.uniquePolymorphicMethodBindings = new SimpleLookupTable(3);
	this.missingTypes = null;
	this.accessRestrictions = new HashMap(3);
	this.classFilePool = rootEnv.classFilePool;
	this.typesBeingConnected = rootEnv.typesBeingConnected;
	this.deferredEnumMethods = rootEnv.deferredEnumMethods;
	this.typeSystem = rootEnv.typeSystem;
	// knownModules is unused in specific LookupEnvironments
	this.useModuleSystem = rootEnv.useModuleSystem;
}

// NOTE: only for resolving!
public ModuleBinding getModule(char[] name) {
	if (this.root != this)
		return this.root.getModule(name);
	if (name == null || name == ModuleBinding.UNNAMED || CharOperation.equals(name, ModuleBinding.ALL_UNNAMED))
		return this.UnNamedModule;
	ModuleBinding moduleBinding = this.knownModules.get(name);
	if (moduleBinding == null) {
		if (this.useModuleSystem) {
			IModule mod = ((IModuleAwareNameEnvironment) this.nameEnvironment).getModule(name);
			if (mod != null) {
				this.typeRequestor.accept(mod, this);
				moduleBinding = this.root.knownModules.get(name);
			}
		} else 
			return this.UnNamedModule;
	}
	return moduleBinding;
}

/**
 * Ask the name environment for a type which corresponds to the compoundName.
 * Answer null if the name cannot be found.
 */

public ReferenceBinding askForType(char[][] compoundName, /*@NonNull*/ModuleBinding clientModule) {
	assert clientModule != null : "lookup needs a module"; //$NON-NLS-1$
	NameEnvironmentAnswer[] answers = null;
	if (this.useModuleSystem) {
		IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.nameEnvironment;
		answers = askForTypeFromModules(clientModule, clientModule.getAllRequiredModules(),
				mod -> moduleEnv.findType(compoundName, mod.nameForLookup()));
	} else {
		NameEnvironmentAnswer answer = this.nameEnvironment.findType(compoundName);
		if (answer != null) {
			answer.moduleBinding = this.module;
			answers = new NameEnvironmentAnswer[] { answer };
		}
	}
	if (answers == null)
		return null;

	ReferenceBinding candidate = null;
	for (NameEnvironmentAnswer answer : answers) {
		if (answer == null) continue;

		ModuleBinding answerModule = answer.moduleBinding != null ? answer.moduleBinding : this.UnNamedModule;
	
		if (answer.isBinaryType()) {
			// the type was found as a .class file
			PackageBinding pkg = answerModule.environment.computePackageFrom(compoundName, false /* valid pkg */);
			this.typeRequestor.accept(answer.getBinaryType(), pkg, answer.getAccessRestriction());
			ReferenceBinding binding = pkg.getType0(compoundName[compoundName.length - 1]);
			if (binding instanceof BinaryTypeBinding) {
				((BinaryTypeBinding) binding).module = answerModule;
				if (pkg.enclosingModule == null)
					pkg.enclosingModule = answerModule;
			}
		} else if (answer.isCompilationUnit()) {
			// the type was found as a .java file, try to build it then search the cache
			this.typeRequestor.accept(answer.getCompilationUnit(), answer.getAccessRestriction());
		} else if (answer.isSourceType()) {
			// the type was found as a source model
			PackageBinding pkg = answerModule.environment.computePackageFrom(compoundName, false /* valid pkg */);
			this.typeRequestor.accept(answer.getSourceTypes(), pkg, answer.getAccessRestriction());
			ReferenceBinding binding = pkg.getType0(compoundName[compoundName.length - 1]);
			if (binding instanceof SourceTypeBinding) {
				((SourceTypeBinding) binding).module = answerModule;
				if (pkg.enclosingModule == null)
					pkg.enclosingModule = answerModule;
			}
		}
		candidate = combine(candidate, answerModule.environment.getCachedType(compoundName), clientModule);
	}
	return candidate;
}

/* Ask the oracle for a type named name in the packageBinding.
* Answer null if the name cannot be found.
*/
ReferenceBinding askForType(PackageBinding packageBinding, char[] name, ModuleBinding clientModule) {
	assert clientModule != null : "lookup needs a module"; //$NON-NLS-1$
	if (packageBinding == null) {
		packageBinding = this.defaultPackage;
	}
	NameEnvironmentAnswer[] answers = null;
	if (this.useModuleSystem) {
		IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.nameEnvironment;
		final PackageBinding pack = packageBinding;
		// leverage module information from the (split?) package as to prefer NotAccessible over NotFound:
		answers = askForTypeFromModules(null, packageBinding.getDeclaringModules(),
				mod -> fromSplitPackageOrOracle(moduleEnv, mod, pack, name));
	} else {
		NameEnvironmentAnswer answer = this.nameEnvironment.findType(name, packageBinding.compoundName);
		if (answer != null) {
			answer.moduleBinding = this.module;
			answers = new NameEnvironmentAnswer[] { answer };
		}
	}
	if (answers == null)
		return null;

	ReferenceBinding candidate = null;
	for (NameEnvironmentAnswer answer : answers) {
		if (answer == null) continue;
		ModuleBinding answerModule = answer.moduleBinding != null ? answer.moduleBinding : this.UnNamedModule;
		PackageBinding answerPackage = packageBinding;
		
		if (answerModule != null) {
			if (!answerPackage.isDeclaredIn(answerModule))
				continue; // this answer is not reachable via the packageBinding
			if (answerPackage instanceof SplitPackageBinding)
				answerPackage = ((SplitPackageBinding) answerPackage).getIncarnation(answerModule);
		}
		if (answer.isResolvedBinding()) {
			candidate = combine(candidate, answer.getResolvedBinding(), clientModule);
			continue;
		} else if (answer.isBinaryType()) {
			// the type was found as a .class file
			this.typeRequestor.accept(answer.getBinaryType(), answerPackage, answer.getAccessRestriction());
			ReferenceBinding binding = answerPackage.getType0(name);
			if (binding instanceof BinaryTypeBinding) {
				((BinaryTypeBinding) binding).module = answerModule;
			}
		} else if (answer.isCompilationUnit()) {
			// the type was found as a .java file, try to build it then search the cache
			try {
				this.typeRequestor.accept(answer.getCompilationUnit(), answer.getAccessRestriction());
			} catch (AbortCompilation abort) {
				if (CharOperation.equals(name, TypeConstants.PACKAGE_INFO_NAME))
					return null; // silently, requestor may not be able to handle compilation units (HierarchyResolver)
				throw abort;
			}
		} else if (answer.isSourceType()) {
			// the type was found as a source model
			this.typeRequestor.accept(answer.getSourceTypes(), answerPackage, answer.getAccessRestriction());
			ReferenceBinding binding = answerPackage.getType0(name);
			if (binding instanceof SourceTypeBinding) {
				((SourceTypeBinding) binding).module = answerModule;
			}
			String externalAnnotationPath = answer.getExternalAnnotationPath();
			if (externalAnnotationPath != null && this.globalOptions.isAnnotationBasedNullAnalysisEnabled && binding instanceof SourceTypeBinding) {
				ExternalAnnotationSuperimposer.apply((SourceTypeBinding) binding, externalAnnotationPath);
			}
			candidate = combine(candidate, binding, clientModule);
			continue;
		}
		candidate = combine(candidate, answerPackage.getType0(name), clientModule);
		if (candidate != null && candidate.problemId() == ProblemReasons.Ambiguous)
			return candidate; // saw enough
	}
	return candidate;
}
/** Combine up-to two candidate types. If both types are present let accessibility from the given clientModule decide. */
private ReferenceBinding combine(ReferenceBinding one, ReferenceBinding two, ModuleBinding clientModule) {
	if (one == null) return two;
	if (two == null) return one;
	if (!clientModule.canAccess(one.fPackage)) return two;
	if (!clientModule.canAccess(two.fPackage)) return one;
	if (one == two) return one; //$IDENTITY-COMPARISON$
	return new ProblemReferenceBinding(one.compoundName, one, ProblemReasons.Ambiguous); // TODO(SHMOD): use a new problem ID
}
/** Collect answers from the oracle concerning the given clientModule (if present) and each of a set of other modules. */
private NameEnvironmentAnswer[] askForTypeFromModules(ModuleBinding clientModule, ModuleBinding[] otherModules,
		Function<ModuleBinding,NameEnvironmentAnswer> oracle)
{
	if (clientModule != null && clientModule.nameForLookup().length == 0) {
		NameEnvironmentAnswer answer = oracle.apply(clientModule);
		if (answer != null)
			answer.moduleBinding = this.root.getModuleFromAnswer(answer);
		return new NameEnvironmentAnswer[] { answer };
	} else {
		boolean found = false;
		NameEnvironmentAnswer[] answers = null;
		if (clientModule != null) {
			answers = new NameEnvironmentAnswer[otherModules.length+1];
			NameEnvironmentAnswer answer = oracle.apply(clientModule);
			if (answer != null) {
				answer.moduleBinding = clientModule;
				answers[answers.length-1] = answer;
				found = true;
			}
		} else {
			answers = new NameEnvironmentAnswer[otherModules.length];
		}
		for (int i = 0; i < otherModules.length; i++) {
			NameEnvironmentAnswer answer = oracle.apply(otherModules[i]);
			if (answer != null) {
				char[] nameFromAnswer = answer.moduleName();
				if (nameFromAnswer == null || CharOperation.equals(nameFromAnswer, otherModules[i].moduleName)) {
					answer.moduleBinding = otherModules[i];
				} else {
					answer.moduleBinding = getModule(nameFromAnswer);
				}
				answers[i] = answer;
				found = true;
			}
		}
		return found ? answers : null;
	}
}
/** First check for a known type in a split package and otherwise ask the oracle. */
private static NameEnvironmentAnswer fromSplitPackageOrOracle(IModuleAwareNameEnvironment moduleEnv, ModuleBinding module,
		PackageBinding packageBinding, char[] name)
{
	if (packageBinding instanceof SplitPackageBinding) {
		// when asking a split package getType0() we may have answered null in case of ambiguity (not knowing the module context).
		// now check if the module-incarnation of the package has the type:
		// (needed because the moduleEnv will not answer initial types).
		ReferenceBinding binding = ((SplitPackageBinding) packageBinding).getType0ForModule(module, name);
		if (binding != null && binding.isValidBinding()) {
			if (binding instanceof UnresolvedReferenceBinding)
				binding = ((UnresolvedReferenceBinding) binding).resolve(module.environment, false);
			return new NameEnvironmentAnswer(binding, module);
		}
	}
	return moduleEnv.findType(name, packageBinding.compoundName, module.nameForLookup());
}
private ModuleBinding getModuleFromAnswer(NameEnvironmentAnswer answer) {
	char[] moduleName = answer.moduleName();
	if (moduleName != null) {
		ModuleBinding moduleBinding;
		if (!this.useModuleSystem || moduleName == ModuleBinding.UNNAMED) {
			moduleBinding = this.UnNamedModule;
		} else { 
			moduleBinding = this.knownModules.get(moduleName);
			if (moduleBinding == null && this.nameEnvironment instanceof IModuleAwareNameEnvironment) {
				IModule iModule = ((IModuleAwareNameEnvironment) this.nameEnvironment).getModule(moduleName);
				try {
					this.typeRequestor.accept(iModule, this);
					moduleBinding = this.knownModules.get(moduleName);
				} catch (NullPointerException e) {
					System.err.println("Bug 529367: moduleName: " + new String(moduleName) + "iModule null" +  //$NON-NLS-1$ //$NON-NLS-2$
							(iModule == null ? "true" : "false")); //$NON-NLS-1$ //$NON-NLS-2$]
					throw e;
				}
			}
		}
		return moduleBinding;
	}
	return null;
}

public boolean canTypeBeAccessed(SourceTypeBinding binding, Scope scope) {
	ModuleBinding client = scope.module();
	return client.canAccess(binding.fPackage);
}

/* Create the initial type bindings for the compilation unit.
*
* See completeTypeBindings() for a description of the remaining steps
*
* NOTE: This method can be called multiple times as additional source files are needed
*/
public void buildTypeBindings(CompilationUnitDeclaration unit, AccessRestriction accessRestriction) {
	CompilationUnitScope scope;
	ModuleBinding unitModule = null;
	if (unit.moduleDeclaration != null) {
		char[] moduleName = unit.moduleDeclaration.moduleName;
		scope = new CompilationUnitScope(unit, this.globalOptions);
		unitModule = unit.moduleDeclaration.setBinding(new SourceModuleBinding(moduleName, scope, this.root));
	} else {
		unitModule = unit.module(this);
		scope = new CompilationUnitScope(unit, unitModule != null ? unitModule.environment : this);
	}
	scope.buildTypeBindings(accessRestriction);
	LookupEnvironment rootEnv = this.root;
	int unitsLength = rootEnv.units.length;
	if (++rootEnv.lastUnitIndex >= unitsLength)
		System.arraycopy(rootEnv.units, 0, rootEnv.units = new CompilationUnitDeclaration[2 * unitsLength], 0, unitsLength);
	rootEnv.units[rootEnv.lastUnitIndex] = unit;
}

/* Cache the binary type since we know it is needed during this compile.
*
* Answer the created BinaryTypeBinding or null if the type is already in the cache.
*/
public BinaryTypeBinding cacheBinaryType(IBinaryType binaryType, AccessRestriction accessRestriction) {
	return cacheBinaryType(binaryType, true, accessRestriction);
}

/* Cache the binary type since we know it is needed during this compile.
*
* Answer the created BinaryTypeBinding or null if the type is already in the cache.
*/
public BinaryTypeBinding cacheBinaryType(IBinaryType binaryType, boolean needFieldsAndMethods, AccessRestriction accessRestriction) {
	char[][] compoundName = CharOperation.splitOn('/', binaryType.getName());
	ReferenceBinding existingType = getCachedType(compoundName);

	if (existingType == null || existingType instanceof UnresolvedReferenceBinding)
		// only add the binary type if its not already in the cache
		return createBinaryTypeFrom(binaryType, computePackageFrom(compoundName, false /* valid pkg */), needFieldsAndMethods, accessRestriction);
	return null; // the type already exists & can be retrieved from the cache
}

public void completeTypeBindings() {
	if (this != this.root) {
		this.root.completeTypeBindings();
		return;
	}
	this.stepCompleted = BUILD_TYPE_HIERARCHY;

	for (int i = this.lastCompletedUnitIndex + 1; i <= this.lastUnitIndex; i++) {
	    (this.unitBeingCompleted = this.units[i]).scope.checkAndSetImports();
	}
	this.stepCompleted = CHECK_AND_SET_IMPORTS;

	for (int i = this.lastCompletedUnitIndex + 1; i <= this.lastUnitIndex; i++) {
	    (this.unitBeingCompleted = this.units[i]).scope.connectTypeHierarchy();
	}
	this.stepCompleted = CONNECT_TYPE_HIERARCHY;

	for (int i = this.lastCompletedUnitIndex + 1; i <= this.lastUnitIndex; i++) {
		CompilationUnitScope unitScope = (this.unitBeingCompleted = this.units[i]).scope;
		unitScope.checkParameterizedTypes();
		unitScope.buildFieldsAndMethods();
		this.units[i] = null; // release unnecessary reference to the parsed unit
	}
	this.stepCompleted = BUILD_FIELDS_AND_METHODS;
	this.lastCompletedUnitIndex = this.lastUnitIndex;
	this.unitBeingCompleted = null;
}

/*
* 1. Connect the type hierarchy for the type bindings created for parsedUnits.
* 2. Create the field bindings
* 3. Create the method bindings
*/

/* We know each known compilationUnit is free of errors at this point...
*
* Each step will create additional bindings unless a problem is detected, in which
* case either the faulty import/superinterface/field/method will be skipped or a
* suitable replacement will be substituted (such as Object for a missing superclass)
*/
public void completeTypeBindings(CompilationUnitDeclaration parsedUnit) {
	if (this != this.root) {
		this.root.completeTypeBindings(parsedUnit);
		return;
	}
	if (this.stepCompleted == BUILD_FIELDS_AND_METHODS) {
		// This can only happen because the original set of units are completely built and
		// are now being processed, so we want to treat all the additional units as a group
		// until they too are completely processed.
		completeTypeBindings();
	} else {
		if (parsedUnit.scope == null) return; // parsing errors were too severe

		if (this.stepCompleted >= CHECK_AND_SET_IMPORTS)
			(this.unitBeingCompleted = parsedUnit).scope.checkAndSetImports();

		if (this.stepCompleted >= CONNECT_TYPE_HIERARCHY)
			(this.unitBeingCompleted = parsedUnit).scope.connectTypeHierarchy();

		this.unitBeingCompleted = null;
	}
}

/*
* Used by other compiler tools which do not start by calling completeTypeBindings().
*
* 1. Connect the type hierarchy for the type bindings created for parsedUnits.
* 2. Create the field bindings
* 3. Create the method bindings
*/

/*
* Each step will create additional bindings unless a problem is detected, in which
* case either the faulty import/superinterface/field/method will be skipped or a
* suitable replacement will be substituted (such as Object for a missing superclass)
*/
public void completeTypeBindings(CompilationUnitDeclaration parsedUnit, boolean buildFieldsAndMethods) {
	if (parsedUnit.scope == null) return; // parsing errors were too severe
	LookupEnvironment rootEnv = this.root;
	(rootEnv.unitBeingCompleted = parsedUnit).scope.checkAndSetImports();
	parsedUnit.scope.connectTypeHierarchy();
	parsedUnit.scope.checkParameterizedTypes();
	if (buildFieldsAndMethods)
		parsedUnit.scope.buildFieldsAndMethods();
	rootEnv.unitBeingCompleted = null;
}

/*
* Used by other compiler tools which do not start by calling completeTypeBindings()
* and have more than 1 unit to complete.
*
* 1. Connect the type hierarchy for the type bindings created for parsedUnits.
* 2. Create the field bindings
* 3. Create the method bindings
*/
public void completeTypeBindings(CompilationUnitDeclaration[] parsedUnits, boolean[] buildFieldsAndMethods, int unitCount) {
	LookupEnvironment rootEnv = this.root;
	for (int i = 0; i < unitCount; i++) {
		CompilationUnitDeclaration parsedUnit = parsedUnits[i];
		if (parsedUnit.scope != null)
			(rootEnv.unitBeingCompleted = parsedUnit).scope.checkAndSetImports();
	}

	for (int i = 0; i < unitCount; i++) {
		CompilationUnitDeclaration parsedUnit = parsedUnits[i];
		if (parsedUnit.scope != null)
			(rootEnv.unitBeingCompleted = parsedUnit).scope.connectTypeHierarchy();
	}

	for (int i = 0; i < unitCount; i++) {
		CompilationUnitDeclaration parsedUnit = parsedUnits[i];
		if (parsedUnit.scope != null) {
			(rootEnv.unitBeingCompleted = parsedUnit).scope.checkParameterizedTypes();
			if (buildFieldsAndMethods[i])
				parsedUnit.scope.buildFieldsAndMethods();
		}
	}

	rootEnv.unitBeingCompleted = null;
}
public TypeBinding computeBoxingType(TypeBinding type) {
	TypeBinding boxedType;
	switch (type.id) {
		case TypeIds.T_JavaLangBoolean :
			return TypeBinding.BOOLEAN;
		case TypeIds.T_JavaLangByte :
			return TypeBinding.BYTE;
		case TypeIds.T_JavaLangCharacter :
			return TypeBinding.CHAR;
		case TypeIds.T_JavaLangShort :
			return TypeBinding.SHORT;
		case TypeIds.T_JavaLangDouble :
			return TypeBinding.DOUBLE;
		case TypeIds.T_JavaLangFloat :
			return TypeBinding.FLOAT;
		case TypeIds.T_JavaLangInteger :
			return TypeBinding.INT;
		case TypeIds.T_JavaLangLong :
			return TypeBinding.LONG;

		case TypeIds.T_int :
			boxedType = getType(JAVA_LANG_INTEGER, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_INTEGER, null, NotFound);
		case TypeIds.T_byte :
			boxedType = getType(JAVA_LANG_BYTE, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_BYTE, null, NotFound);
		case TypeIds.T_short :
			boxedType = getType(JAVA_LANG_SHORT, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_SHORT, null, NotFound);
		case TypeIds.T_char :
			boxedType = getType(JAVA_LANG_CHARACTER, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_CHARACTER, null, NotFound);
		case TypeIds.T_long :
			boxedType = getType(JAVA_LANG_LONG, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_LONG, null, NotFound);
		case TypeIds.T_float :
			boxedType = getType(JAVA_LANG_FLOAT, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_FLOAT, null, NotFound);
		case TypeIds.T_double :
			boxedType = getType(JAVA_LANG_DOUBLE, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_DOUBLE, null, NotFound);
		case TypeIds.T_boolean :
			boxedType = getType(JAVA_LANG_BOOLEAN, javaBaseModule());
			if (boxedType != null) return boxedType;
			return new ProblemReferenceBinding(JAVA_LANG_BOOLEAN, null, NotFound);
//		case TypeIds.T_int :
//			return getResolvedType(JAVA_LANG_INTEGER, null);
//		case TypeIds.T_byte :
//			return getResolvedType(JAVA_LANG_BYTE, null);
//		case TypeIds.T_short :
//			return getResolvedType(JAVA_LANG_SHORT, null);
//		case TypeIds.T_char :
//			return getResolvedType(JAVA_LANG_CHARACTER, null);
//		case TypeIds.T_long :
//			return getResolvedType(JAVA_LANG_LONG, null);
//		case TypeIds.T_float :
//			return getResolvedType(JAVA_LANG_FLOAT, null);
//		case TypeIds.T_double :
//			return getResolvedType(JAVA_LANG_DOUBLE, null);
//		case TypeIds.T_boolean :
//			return getResolvedType(JAVA_LANG_BOOLEAN, null);
	}
	// allow indirect unboxing conversion for wildcards and type parameters
	switch (type.kind()) {
		case Binding.WILDCARD_TYPE :
		case Binding.INTERSECTION_TYPE :
		case Binding.TYPE_PARAMETER :
			switch (type.erasure().id) {
				case TypeIds.T_JavaLangBoolean :
					return TypeBinding.BOOLEAN;
				case TypeIds.T_JavaLangByte :
					return TypeBinding.BYTE;
				case TypeIds.T_JavaLangCharacter :
					return TypeBinding.CHAR;
				case TypeIds.T_JavaLangShort :
					return TypeBinding.SHORT;
				case TypeIds.T_JavaLangDouble :
					return TypeBinding.DOUBLE;
				case TypeIds.T_JavaLangFloat :
					return TypeBinding.FLOAT;
				case TypeIds.T_JavaLangInteger :
					return TypeBinding.INT;
				case TypeIds.T_JavaLangLong :
					return TypeBinding.LONG;
			}
			break;
		case Binding.POLY_TYPE:
			return ((PolyTypeBinding) type).computeBoxingType();
		case Binding.INTERSECTION_TYPE18:
			return computeBoxingType(type.getIntersectingTypes()[0]);
	}
	return type;
}

public ModuleBinding javaBaseModule() {
	if (this.JavaBaseModule != null)
		return this.JavaBaseModule;
	if (this.root != this)
		return this.JavaBaseModule = this.root.javaBaseModule();
	ModuleBinding resolvedModel = null;
	if (this.useModuleSystem)
		resolvedModel = getModule(TypeConstants.JAVA_BASE);
	return this.JavaBaseModule = (resolvedModel != null ? resolvedModel : this.UnNamedModule); // fall back to pre-Jigsaw view
}

private PackageBinding computePackageFrom(char[][] constantPoolName, boolean isMissing) {
	if (constantPoolName.length == 1)
		return this.defaultPackage;

	PackageBinding packageBinding = getPackage0(constantPoolName[0]);
	if (packageBinding == null || packageBinding == TheNotFoundPackage) {
		if (this.useModuleSystem) {
			if (this.module.isUnnamed()) {
				char[][] declaringModules = ((IModuleAwareNameEnvironment) this.nameEnvironment).getModulesDeclaringPackage(null, constantPoolName[0], ModuleBinding.ANY);
				if (declaringModules != null) {
					for (char[] mod : declaringModules) {
						ModuleBinding declaringModule = this.root.getModule(mod);
						if (declaringModule != null)
							packageBinding = SplitPackageBinding.combine(declaringModule.getTopLevelPackage(constantPoolName[0]), packageBinding, this.module);
					}
				}
			} else {
				packageBinding = this.module.getTopLevelPackage(constantPoolName[0]);
			}
		}
		if (packageBinding == null || packageBinding == TheNotFoundPackage) {
			packageBinding = new PackageBinding(constantPoolName[0], this, this.module);
		}
		if (isMissing) packageBinding.tagBits |= TagBits.HasMissingType;
		this.knownPackages.put(constantPoolName[0], packageBinding); // TODO: split?
	}

	for (int i = 1, length = constantPoolName.length - 1; i < length; i++) {
		PackageBinding parent = packageBinding;
		if ((packageBinding = parent.getPackage0(constantPoolName[i])) == null || packageBinding == TheNotFoundPackage) {
			if (this.useModuleSystem) {
				if (this.module.isUnnamed()) {
					char[][] declaringModules = ((IModuleAwareNameEnvironment) this.nameEnvironment).getModulesDeclaringPackage(parent.compoundName, constantPoolName[i], ModuleBinding.ANY);
					if (declaringModules != null) {
						for (char[] mod : declaringModules) {
							ModuleBinding declaringModule = this.root.getModule(mod);
							if (declaringModule != null)
								packageBinding = SplitPackageBinding.combine(declaringModule.getPackage(parent.compoundName, constantPoolName[i]), packageBinding, this.module);
						}
					}
				} else {
					packageBinding = this.module.getVisiblePackage(parent, constantPoolName[i]);
				}
			}
			if (packageBinding == null || packageBinding == TheNotFoundPackage) {
				packageBinding = new PackageBinding(CharOperation.subarray(constantPoolName, 0, i + 1), parent, this, this.module);
			}
			if (isMissing) {
				packageBinding.tagBits |= TagBits.HasMissingType;
			}
			packageBinding = parent.addPackage(packageBinding, this.module, true);
		}
	}
	if (packageBinding instanceof SplitPackageBinding) {
		PackageBinding candidate = null;
		// select from incarnations the unique package containing CUs, if any:
		for (PackageBinding incarnation : ((SplitPackageBinding) packageBinding).incarnations) {
			if (incarnation.hasCompilationUnit(false)) {
				if (candidate != null) {
					candidate = null;
					break; // likely to report "accessible from more than one module" downstream 
				}
				candidate = incarnation;
			}
		}
		if (candidate != null)
			return candidate;
	}
	return packageBinding;
}

/**
 * Convert a given source type into a parameterized form if generic.
 * generic X<E> --> param X<E>
 */
public ReferenceBinding convertToParameterizedType(ReferenceBinding originalType) {
	if (originalType != null) {
		boolean isGeneric = originalType.isGenericType();
		if (!isGeneric && !originalType.hasEnclosingInstanceContext())
			return originalType;
		ReferenceBinding originalEnclosingType = originalType.enclosingType();
		ReferenceBinding convertedEnclosingType = originalEnclosingType;
		boolean needToConvert = isGeneric;
		if (originalEnclosingType != null && originalType.hasEnclosingInstanceContext()) {
			convertedEnclosingType = convertToParameterizedType(originalEnclosingType);
			needToConvert |= TypeBinding.notEquals(originalEnclosingType, convertedEnclosingType);
		}
		if (needToConvert) {
			return createParameterizedType(originalType, isGeneric ? originalType.typeVariables() : null, convertedEnclosingType);
		}
	}
	return originalType;
}
/**
 * Returns the given binding's raw type binding.
 * @param type the TypeBinding to raw convert
 * @param forceRawEnclosingType forces recursive raw conversion of enclosing types (used in Javadoc references only)
 * @return TypeBinding the raw converted TypeBinding
 */
public TypeBinding convertToRawType(TypeBinding type, boolean forceRawEnclosingType) {
	int dimension;
	TypeBinding originalType;
	switch(type.kind()) {
		case Binding.BASE_TYPE :
		case Binding.TYPE_PARAMETER:
		case Binding.WILDCARD_TYPE:
		case Binding.INTERSECTION_TYPE:
		case Binding.RAW_TYPE:
			return type;
		case Binding.ARRAY_TYPE:
			dimension = type.dimensions();
			originalType = type.leafComponentType();
			break;
		default:
			if (type.id == TypeIds.T_JavaLangObject)
				return type; // Object is not generic
			dimension = 0;
			originalType = type;
	}
	boolean needToConvert;
	switch (originalType.kind()) {
		case Binding.BASE_TYPE :
			return type;
		case Binding.GENERIC_TYPE :
			needToConvert = true;
			break;
		case Binding.PARAMETERIZED_TYPE :
			ParameterizedTypeBinding paramType = (ParameterizedTypeBinding) originalType;
			needToConvert = paramType.genericType().isGenericType(); // only recursive call to enclosing type can find parameterizedType with arguments
			break;
		default :
			needToConvert = false;
			break;
	}
	forceRawEnclosingType &= !originalType.isStatic();
	ReferenceBinding originalEnclosing = originalType.enclosingType();
	TypeBinding convertedType;
	if (originalEnclosing == null) {
		convertedType = needToConvert ? createRawType((ReferenceBinding)originalType.erasure(), null) : originalType;
	} else {
		ReferenceBinding convertedEnclosing;
		if (!((ReferenceBinding)originalType).hasEnclosingInstanceContext()) {
			convertedEnclosing = (ReferenceBinding) originalEnclosing.original();
		} else {
			if (originalEnclosing.kind() == Binding.RAW_TYPE) {			
				convertedEnclosing = originalEnclosing;
				needToConvert = true;
			} else if (forceRawEnclosingType && !needToConvert/*stop recursion when conversion occurs*/) {
				convertedEnclosing = (ReferenceBinding) convertToRawType(originalEnclosing, forceRawEnclosingType);
				needToConvert = TypeBinding.notEquals(originalEnclosing, convertedEnclosing); // only convert generic or parameterized types
			} else if (needToConvert) {
				convertedEnclosing = (ReferenceBinding) convertToRawType(originalEnclosing, false);
			} else {
				convertedEnclosing = convertToParameterizedType(originalEnclosing);
			}
		}
		if (needToConvert) {
			convertedType = createRawType((ReferenceBinding) originalType.erasure(), convertedEnclosing);
		} else if (TypeBinding.notEquals(originalEnclosing, convertedEnclosing)) {
			convertedType = createParameterizedType((ReferenceBinding) originalType.erasure(), null, convertedEnclosing);
		} else {
			convertedType = originalType;
		}
	}
	if (TypeBinding.notEquals(originalType, convertedType)) {
		return dimension > 0 ? (TypeBinding)createArrayType(convertedType, dimension) : convertedType;
	}
	return type;
}

/**
 * Convert an array of types in raw forms.
 * Only allocate an array if anything is different.
 */
public ReferenceBinding[] convertToRawTypes(ReferenceBinding[] originalTypes, boolean forceErasure, boolean forceRawEnclosingType) {
	if (originalTypes == null) return null;
    ReferenceBinding[] convertedTypes = originalTypes;
    for (int i = 0, length = originalTypes.length; i < length; i++) {
        ReferenceBinding originalType = originalTypes[i];
        ReferenceBinding convertedType = (ReferenceBinding) convertToRawType(forceErasure ? originalType.erasure() : originalType, forceRawEnclosingType);
        if (TypeBinding.notEquals(convertedType, originalType)) {        
            if (convertedTypes == originalTypes) {
                System.arraycopy(originalTypes, 0, convertedTypes = new ReferenceBinding[length], 0, i);
            }
            convertedTypes[i] = convertedType;
        } else if (convertedTypes != originalTypes) {
            convertedTypes[i] = originalType;
        }
    }
    return convertedTypes;
}

// variation for unresolved types in binaries (consider generic type as raw)
public TypeBinding convertUnresolvedBinaryToRawType(TypeBinding type) {
	int dimension;
	TypeBinding originalType;
	switch(type.kind()) {
		case Binding.BASE_TYPE :
		case Binding.TYPE_PARAMETER:
		case Binding.WILDCARD_TYPE:
		case Binding.INTERSECTION_TYPE:
		case Binding.RAW_TYPE:
			return type;
		case Binding.ARRAY_TYPE:
			dimension = type.dimensions();
			originalType = type.leafComponentType();
			break;
		default:
			if (type.id == TypeIds.T_JavaLangObject)
				return type; // Object is not generic
			dimension = 0;
			originalType = type;
	}
	boolean needToConvert;
	switch (originalType.kind()) {
		case Binding.BASE_TYPE :
			return type;
		case Binding.GENERIC_TYPE :
			needToConvert = true;
			break;
		case Binding.PARAMETERIZED_TYPE :
			ParameterizedTypeBinding paramType = (ParameterizedTypeBinding) originalType;
			needToConvert = paramType.genericType().isGenericType(); // only recursive call to enclosing type can find parameterizedType with arguments
			break;
		default :
			needToConvert = false;
			break;
	}
	ReferenceBinding originalEnclosing = originalType.enclosingType();
	TypeBinding convertedType;
	if (originalEnclosing == null) {
		convertedType = needToConvert ? createRawType((ReferenceBinding)originalType.erasure(), null) : originalType;
	} else {
		if (!needToConvert && originalType.isStatic())
			return originalType;

		ReferenceBinding convertedEnclosing = (ReferenceBinding) convertUnresolvedBinaryToRawType(originalEnclosing);
		if (TypeBinding.notEquals(convertedEnclosing, originalEnclosing)) {
			needToConvert = true;
		}
		if (needToConvert) {
			convertedType = createRawType((ReferenceBinding) originalType.erasure(), convertedEnclosing);
		} else {
			convertedType = originalType;
		}
	}
	if (TypeBinding.notEquals(originalType, convertedType)) {
		return dimension > 0 ? (TypeBinding)createArrayType(convertedType, dimension) : convertedType;
	}
	return type;
}
/* Used to guarantee annotation identity: we do that only for marker annotations and others with all default values.
   We don't have the machinery for the general case as of now.
*/
public AnnotationBinding createAnnotation(ReferenceBinding annotationType, ElementValuePair[] pairs) {
	if (pairs.length != 0) {
		AnnotationBinding.setMethodBindings(annotationType, pairs);
		return new AnnotationBinding(annotationType, pairs);
	}
	return this.typeSystem.getAnnotationType(annotationType, true);
}

/* Used to guarantee annotation identity: we do that only for marker annotations and others with all default values.
   We don't have the machinery for the general case as of now.
*/
public AnnotationBinding createUnresolvedAnnotation(ReferenceBinding annotationType, ElementValuePair[] pairs) {
	if (pairs.length != 0) {
		return new UnresolvedAnnotationBinding(annotationType, pairs, this);
	}
	return this.typeSystem.getAnnotationType(annotationType, false);
}

/*
 *  Used to guarantee array type identity.
 */
public ArrayBinding createArrayType(TypeBinding leafComponentType, int dimensionCount) {
	return this.typeSystem.getArrayType(leafComponentType, dimensionCount);
}

public ArrayBinding createArrayType(TypeBinding leafComponentType, int dimensionCount, AnnotationBinding [] annotations) {
	return this.typeSystem.getArrayType(leafComponentType, dimensionCount, annotations);
}

public TypeBinding createIntersectionType18(ReferenceBinding[] intersectingTypes) {
	if (!intersectingTypes[0].isClass()) {
		Arrays.sort(intersectingTypes, new Comparator<TypeBinding>() {
			@Override
			public int compare(TypeBinding o1, TypeBinding o2) {
				//
				return o1.isClass() ? -1 : (o2.isClass() ? 1 : CharOperation.compareTo(o1.readableName(), o2.readableName()));
			}
		});
	}
	return this.typeSystem.getIntersectionType18(intersectingTypes);
}	

public BinaryTypeBinding createBinaryTypeFrom(IBinaryType binaryType, PackageBinding packageBinding, AccessRestriction accessRestriction) {
	return createBinaryTypeFrom(binaryType, packageBinding, true, accessRestriction);
}

public BinaryTypeBinding createBinaryTypeFrom(IBinaryType binaryType, PackageBinding packageBinding, boolean needFieldsAndMethods, AccessRestriction accessRestriction) {
	if (this != packageBinding.environment)
		return packageBinding.environment.createBinaryTypeFrom(binaryType, packageBinding, needFieldsAndMethods, accessRestriction);
	BinaryTypeBinding binaryBinding = new BinaryTypeBinding(packageBinding, binaryType, this);

	// resolve any array bindings which reference the unresolvedType
	ReferenceBinding cachedType = packageBinding.getType0(binaryBinding.compoundName[binaryBinding.compoundName.length - 1]);
	if (cachedType != null && !cachedType.isUnresolvedType()) {
		if (cachedType.isBinaryBinding()) // sanity check... at this point the cache should ONLY contain unresolved types
			return (BinaryTypeBinding) cachedType;
		// it is possible with a large number of source files (exceeding AbstractImageBuilder.MAX_AT_ONCE) that a member type can be in the cache as an UnresolvedType,
		// but because its enclosingType is resolved while its created (call to BinaryTypeBinding constructor), its replaced with a source type
		return null;
	}
	packageBinding.addType(binaryBinding);
	setAccessRestriction(binaryBinding, accessRestriction);
	binaryBinding.cachePartsFrom(binaryType, needFieldsAndMethods);
	return binaryBinding;
}

/*
 * Used to create types denoting missing types.
 * If package is given, then reuse the package; if not then infer a package from compound name.
 * If the package is existing, then install the missing type in type cache
*/
public MissingTypeBinding createMissingType(PackageBinding packageBinding, char[][] compoundName) {
	// create a proxy for the missing BinaryType
	if (packageBinding == null) {
		packageBinding = computePackageFrom(compoundName, true /* missing */);
		if (packageBinding == TheNotFoundPackage) packageBinding = this.defaultPackage;
	}
	MissingTypeBinding missingType = new MissingTypeBinding(packageBinding, compoundName, this);
	if (missingType.id != TypeIds.T_JavaLangObject) {
		// make Object be its superclass - it could in turn be missing as well
		ReferenceBinding objectType = getType(TypeConstants.JAVA_LANG_OBJECT, javaBaseModule());
		if (objectType == null) {
			objectType = createMissingType(null, TypeConstants.JAVA_LANG_OBJECT);	// create a proxy for the missing Object type
		}
		missingType.setMissingSuperclass(objectType);
	}
	packageBinding.addType(missingType);
	if (this.missingTypes == null)
		this.missingTypes = new ArrayList(3);
	this.missingTypes.add(missingType);
	return missingType;
}

/*
 * 1. Connect the type hierarchy for the type bindings created for parsedUnits.
 * 2. Create the field bindings
 * 3. Create the method bindings
 */
public PackageBinding createPackage(char[][] compoundName) {
	PackageBinding packageBinding = getPackage0(compoundName[0]);
	if (packageBinding == null || packageBinding == TheNotFoundPackage) {
		packageBinding = new PackageBinding(compoundName[0], this, this.module);
		this.knownPackages.put(compoundName[0], packageBinding);
		if (this.module != null) {
			packageBinding = this.module.addPackage(packageBinding, true);
			this.knownPackages.put(compoundName[0], packageBinding); // update in case of split package
		}
	}

	for (int i = 1, length = compoundName.length; i < length; i++) {
		// check to see if it collides with a known type...
		// this case can only happen if the package does not exist as a directory in the file system
		// otherwise when the source type was defined, the correct error would have been reported
		// unless its an unresolved type which is referenced from an inconsistent class file
		// NOTE: empty packages are not packages according to changes in JLS v2, 7.4.3
		// so not all types cause collision errors when they're created even though the package did exist
		ReferenceBinding type = packageBinding.getType0(compoundName[i]);
		if (type != null && type != TheNotFoundType && !(type instanceof UnresolvedReferenceBinding))
			return null;

		PackageBinding parent = packageBinding;
		if ((packageBinding = parent.getPackage0(compoundName[i])) == null || packageBinding == TheNotFoundPackage) {
			// if the package is unknown, check to see if a type exists which would collide with the new package
			// catches the case of a package statement of: package java.lang.Object;
			// since the package can be added after a set of source files have already been compiled,
			// we need to check whenever a package is created
			if(this.nameEnvironment instanceof INameEnvironmentExtension) {
				//When the nameEnvironment is an instance of INameEnvironmentWithProgress, it can get avoided to search for secondaryTypes (see flag).
				// This is a performance optimization, because it is very expensive to search for secondary types and it isn't necessary to check when creating a package,
				// because package name can not collide with a secondary type name.
				if (((INameEnvironmentExtension)this.nameEnvironment).findType(compoundName[i], parent.compoundName, false, this.module.nameForLookup()) != null) {
					return null;
				}
			} else {
				if (this.nameEnvironment.findType(compoundName[i], parent.compoundName) != null) {
					return null;
				}
			}
			if (parent instanceof SplitPackageBinding) {
				// parent.getPackage0() may have been too shy, so drill into the split:
				PackageBinding singleParent = ((SplitPackageBinding) parent).getIncarnation(this.module);
				if (singleParent != null)
					packageBinding = singleParent.getPackage0(compoundName[i]);
			}
			if (packageBinding == null) {
				packageBinding = new PackageBinding(CharOperation.subarray(compoundName, 0, i + 1), parent, this, this.module);
				packageBinding = parent.addPackage(packageBinding, this.module, true);
			}
		}
	}
	if (packageBinding instanceof SplitPackageBinding)
		packageBinding = ((SplitPackageBinding) packageBinding).getIncarnation(this.module);
	return packageBinding;
}

public ParameterizedGenericMethodBinding createParameterizedGenericMethod(MethodBinding genericMethod, RawTypeBinding rawType) {
	// cached info is array of already created parameterized types for this type
	ParameterizedGenericMethodBinding[] cachedInfo = (ParameterizedGenericMethodBinding[])this.uniqueParameterizedGenericMethodBindings.get(genericMethod);
	boolean needToGrow = false;
	int index = 0;
	if (cachedInfo != null){
		nextCachedMethod :
			// iterate existing parameterized for reusing one with same type arguments if any
			for (int max = cachedInfo.length; index < max; index++){
				ParameterizedGenericMethodBinding cachedMethod = cachedInfo[index];
				if (cachedMethod == null) break nextCachedMethod;
				if (!cachedMethod.isRaw) continue nextCachedMethod;
				if (cachedMethod.declaringClass != (rawType == null ? genericMethod.declaringClass : rawType)) continue nextCachedMethod; //$IDENTITY-COMPARISON$
				return cachedMethod;
		}
		needToGrow = true;
	} else {
		cachedInfo = new ParameterizedGenericMethodBinding[5];
		this.uniqueParameterizedGenericMethodBindings.put(genericMethod, cachedInfo);
	}
	// grow cache ?
	int length = cachedInfo.length;
	if (needToGrow && index == length){
		System.arraycopy(cachedInfo, 0, cachedInfo = new ParameterizedGenericMethodBinding[length*2], 0, length);
		this.uniqueParameterizedGenericMethodBindings.put(genericMethod, cachedInfo);
	}
	// add new binding
	ParameterizedGenericMethodBinding parameterizedGenericMethod = new ParameterizedGenericMethodBinding(genericMethod, rawType, this);
	cachedInfo[index] = parameterizedGenericMethod;
	return parameterizedGenericMethod;
}

public ParameterizedGenericMethodBinding createParameterizedGenericMethod(MethodBinding genericMethod, TypeBinding[] typeArguments) {
	return createParameterizedGenericMethod(genericMethod, typeArguments, null);
}
public ParameterizedGenericMethodBinding createParameterizedGenericMethod(MethodBinding genericMethod, TypeBinding[] typeArguments, TypeBinding targetType) {
	return createParameterizedGenericMethod(genericMethod, typeArguments, false, false, targetType);
}
public ParameterizedGenericMethodBinding createParameterizedGenericMethod(MethodBinding genericMethod, TypeBinding[] typeArguments,
																			boolean inferredWithUncheckedConversion, boolean hasReturnProblem, TypeBinding targetType)
{
	// cached info is array of already created parameterized types for this type
	ParameterizedGenericMethodBinding[] cachedInfo = (ParameterizedGenericMethodBinding[])this.uniqueParameterizedGenericMethodBindings.get(genericMethod);
	int argLength = typeArguments == null ? 0: typeArguments.length;
	boolean needToGrow = false;
	int index = 0;
	if (cachedInfo != null){
		nextCachedMethod :
			// iterate existing parameterized for reusing one with same type arguments if any
			for (int max = cachedInfo.length; index < max; index++){
				ParameterizedGenericMethodBinding cachedMethod = cachedInfo[index];
				if (cachedMethod == null) break nextCachedMethod;
				if (cachedMethod.isRaw) continue nextCachedMethod;
				if (cachedMethod.targetType != targetType) continue nextCachedMethod; //$IDENTITY-COMPARISON$
				if (cachedMethod.inferredWithUncheckedConversion != inferredWithUncheckedConversion) continue nextCachedMethod;
				TypeBinding[] cachedArguments = cachedMethod.typeArguments;
				int cachedArgLength = cachedArguments == null ? 0 : cachedArguments.length;
				if (argLength != cachedArgLength) continue nextCachedMethod;
				for (int j = 0; j < cachedArgLength; j++){
					if (typeArguments[j] != cachedArguments[j]) continue nextCachedMethod; //$IDENTITY-COMPARISON$
				}
				if (inferredWithUncheckedConversion) { // JSL 18.5.2: "If unchecked conversion was necessary..."
					// don't tolerate remaining parameterized types / type variables, should have been eliminated by erasure:
					if (cachedMethod.returnType.isParameterizedType() || cachedMethod.returnType.isTypeVariable()) continue;
					for (TypeBinding exc : cachedMethod.thrownExceptions)
						if (exc.isParameterizedType() || exc.isTypeVariable()) continue nextCachedMethod;
				}
				// all arguments match, reuse current
				return cachedMethod;
		}
		needToGrow = true;
	} else {
		cachedInfo = new ParameterizedGenericMethodBinding[5];
		this.uniqueParameterizedGenericMethodBindings.put(genericMethod, cachedInfo);
	}
	// grow cache ?
	int length = cachedInfo.length;
	if (needToGrow && index == length){
		System.arraycopy(cachedInfo, 0, cachedInfo = new ParameterizedGenericMethodBinding[length*2], 0, length);
		this.uniqueParameterizedGenericMethodBindings.put(genericMethod, cachedInfo);
	}
	// add new binding
	ParameterizedGenericMethodBinding parameterizedGenericMethod =
			new ParameterizedGenericMethodBinding(genericMethod, typeArguments, this, inferredWithUncheckedConversion, hasReturnProblem, targetType);
	cachedInfo[index] = parameterizedGenericMethod;
	return parameterizedGenericMethod;
}
public PolymorphicMethodBinding createPolymorphicMethod(MethodBinding originalPolymorphicMethod, TypeBinding[] parameters) {
	// cached info is array of already created polymorphic methods for this type
	String key = new String(originalPolymorphicMethod.selector);
	PolymorphicMethodBinding[] cachedInfo = (PolymorphicMethodBinding[]) this.uniquePolymorphicMethodBindings.get(key);
	int parametersLength = parameters == null ? 0: parameters.length;
	TypeBinding[] parametersTypeBinding = new TypeBinding[parametersLength]; 
	for (int i = 0; i < parametersLength; i++) {
		TypeBinding parameterTypeBinding = parameters[i];
		if (parameterTypeBinding.id == TypeIds.T_null) {
			parametersTypeBinding[i] = getType(JAVA_LANG_VOID, javaBaseModule());
		} else {
			if (parameterTypeBinding.isPolyType()) {
				PolyTypeBinding ptb = (PolyTypeBinding) parameterTypeBinding;
				parametersTypeBinding[i] = ptb.expression.resolvedType;
			} else {
				parametersTypeBinding[i] = parameterTypeBinding.erasure();
			}
		}
	}
	boolean needToGrow = false;
	int index = 0;
	if (cachedInfo != null) {
		nextCachedMethod :
			// iterate existing polymorphic method for reusing one with same type arguments if any
			for (int max = cachedInfo.length; index < max; index++) {
				PolymorphicMethodBinding cachedMethod = cachedInfo[index];
				if (cachedMethod == null) {
					break nextCachedMethod;
				}
				if (cachedMethod.matches(parametersTypeBinding, originalPolymorphicMethod.returnType)) {
					return cachedMethod;
				}
		}
		needToGrow = true;
	} else {
		cachedInfo = new PolymorphicMethodBinding[5];
		this.uniquePolymorphicMethodBindings.put(key, cachedInfo);
	}
	// grow cache ?
	int length = cachedInfo.length;
	if (needToGrow && index == length) {
		System.arraycopy(cachedInfo, 0, cachedInfo = new PolymorphicMethodBinding[length*2], 0, length);
		this.uniquePolymorphicMethodBindings.put(key, cachedInfo);
	}
	// add new binding
	PolymorphicMethodBinding polymorphicMethod = new PolymorphicMethodBinding(
			originalPolymorphicMethod,
			parametersTypeBinding);
	cachedInfo[index] = polymorphicMethod;
	return polymorphicMethod;
}

public boolean usesAnnotatedTypeSystem() {
	return this.typeSystem.isAnnotatedTypeSystem();
}

public MethodBinding updatePolymorphicMethodReturnType(PolymorphicMethodBinding binding, TypeBinding typeBinding) {
	// update the return type to be the given return type, but reuse existing binding if one can match
	String key = new String(binding.selector);
	PolymorphicMethodBinding[] cachedInfo = (PolymorphicMethodBinding[]) this.uniquePolymorphicMethodBindings.get(key);
	boolean needToGrow = false;
	int index = 0;
	TypeBinding[] parameters = binding.parameters;
	if (cachedInfo != null) {
		nextCachedMethod :
			// iterate existing polymorphic method for reusing one with same type arguments if any
			for (int max = cachedInfo.length; index < max; index++) {
				PolymorphicMethodBinding cachedMethod = cachedInfo[index];
				if (cachedMethod == null) {
					break nextCachedMethod;
				}
				if (cachedMethod.matches(parameters, typeBinding)) {
					return cachedMethod;
				}
		}
		needToGrow = true;
	} else {
		cachedInfo = new PolymorphicMethodBinding[5];
		this.uniquePolymorphicMethodBindings.put(key, cachedInfo);
	}
	// grow cache ?
	int length = cachedInfo.length;
	if (needToGrow && index == length) {
		System.arraycopy(cachedInfo, 0, cachedInfo = new PolymorphicMethodBinding[length*2], 0, length);
		this.uniquePolymorphicMethodBindings.put(key, cachedInfo);
	}
	// add new binding
	PolymorphicMethodBinding polymorphicMethod = new PolymorphicMethodBinding(
			binding.original(),
			typeBinding,
			parameters);
	cachedInfo[index] = polymorphicMethod;
	return polymorphicMethod;
}
public ParameterizedMethodBinding createGetClassMethod(TypeBinding receiverType, MethodBinding originalMethod, Scope scope) {
	// see if we have already cached this method for the given receiver type.
	ParameterizedMethodBinding retVal = null;
	if (this.uniqueGetClassMethodBinding == null) {
		this.uniqueGetClassMethodBinding = new SimpleLookupTable(3);
	} else {
		retVal = (ParameterizedMethodBinding)this.uniqueGetClassMethodBinding.get(receiverType);
	}
	if (retVal == null) {
		retVal = ParameterizedMethodBinding.instantiateGetClass(receiverType, originalMethod, scope);
		this.uniqueGetClassMethodBinding.put(receiverType, retVal);
	}
	return retVal;
}
public ReferenceBinding createMemberType(ReferenceBinding memberType, ReferenceBinding enclosingType) {
	return this.typeSystem.getMemberType(memberType, enclosingType);
}
public ParameterizedTypeBinding createParameterizedType(ReferenceBinding genericType, TypeBinding[] typeArguments, ReferenceBinding enclosingType) {
	AnnotationBinding[] annotations = genericType.typeAnnotations;
	if (annotations != Binding.NO_ANNOTATIONS)
		return this.typeSystem.getParameterizedType((ReferenceBinding) genericType.unannotated(), typeArguments, enclosingType, annotations);
	return this.typeSystem.getParameterizedType(genericType, typeArguments, enclosingType);
}

public ParameterizedTypeBinding createParameterizedType(ReferenceBinding genericType, TypeBinding[] typeArguments, ReferenceBinding enclosingType, AnnotationBinding [] annotations) {
	return this.typeSystem.getParameterizedType(genericType, typeArguments, enclosingType, annotations);
}
public ReferenceBinding maybeCreateParameterizedType(ReferenceBinding nonGenericType, ReferenceBinding enclosingType) {
	boolean canSeeEnclosingTypeParameters = enclosingType != null 
			&& (enclosingType.isParameterizedType() | enclosingType.isRawType())
			&& !nonGenericType.isStatic();
	if (canSeeEnclosingTypeParameters)
		return createParameterizedType(nonGenericType, null, enclosingType);
	return nonGenericType;
}

public TypeBinding createAnnotatedType(TypeBinding type, AnnotationBinding[][] annotations) {
	return this.typeSystem.getAnnotatedType(type, annotations);
}

// Variant to handle incoming type possibly carrying annotations.
public TypeBinding createAnnotatedType(TypeBinding type, AnnotationBinding[] newbies) {
	final int newLength = newbies == null ? 0 :  newbies.length;
	if (type == null || newLength == 0)
		return type;
	AnnotationBinding [] oldies = type.getTypeAnnotations();
	final int oldLength = oldies == null ? 0 : oldies.length;
	if (oldLength > 0) {
		System.arraycopy(newbies, 0, newbies = new AnnotationBinding[newLength + oldLength], 0, newLength);
		System.arraycopy(oldies, 0, newbies, newLength, oldLength);
	}
	if (this.globalOptions.isAnnotationBasedNullAnalysisEnabled) {
		// filter duplicate null annotations
		// (do we want to filter other annotations as well? only if not repeatable?)
		long tagBitsSeen = 0;
		AnnotationBinding[] filtered = new AnnotationBinding[newbies.length];
		int count = 0;
		for (int i = 0; i < newbies.length; i++) {
			if (newbies[i] == null) {
				filtered[count++] = null;
				// reset tagBitsSeen for next array dimension
				tagBitsSeen = 0;
				continue;
			}
			long tagBits = 0;
			if (newbies[i].type.hasNullBit(TypeIds.BitNonNullAnnotation)) {
				tagBits = TagBits.AnnotationNonNull;
			} else if (newbies[i].type.hasNullBit(TypeIds.BitNullableAnnotation)) {
				tagBits = TagBits.AnnotationNullable;
			}
			if ((tagBitsSeen & tagBits) == 0) {
				tagBitsSeen |= tagBits;
				filtered[count++] = newbies[i];
			}
		}
		if (count < newbies.length)
			System.arraycopy(filtered, 0, newbies = new AnnotationBinding[count], 0, count);
	}
	return this.typeSystem.getAnnotatedType(type, new AnnotationBinding [][] { newbies });
}

public RawTypeBinding createRawType(ReferenceBinding genericType, ReferenceBinding enclosingType) {
	AnnotationBinding[] annotations = genericType.typeAnnotations;
	if (annotations != Binding.NO_ANNOTATIONS)
		return this.typeSystem.getRawType((ReferenceBinding) genericType.unannotated(), enclosingType, annotations);
	return this.typeSystem.getRawType(genericType, enclosingType);
}

public RawTypeBinding createRawType(ReferenceBinding genericType, ReferenceBinding enclosingType, AnnotationBinding [] annotations) {
	return this.typeSystem.getRawType(genericType, enclosingType, annotations);
}

public WildcardBinding createWildcard(ReferenceBinding genericType, int rank, TypeBinding bound, TypeBinding[] otherBounds, int boundKind) {
	if (genericType != null) {
		AnnotationBinding[] annotations = genericType.typeAnnotations;
		if (annotations != Binding.NO_ANNOTATIONS)
			return this.typeSystem.getWildcard((ReferenceBinding) genericType.unannotated(), rank, bound, otherBounds, boundKind, annotations);
	}
	return this.typeSystem.getWildcard(genericType, rank, bound, otherBounds, boundKind);
}

public CaptureBinding createCapturedWildcard(WildcardBinding wildcard, ReferenceBinding contextType, int start, int end, ASTNode cud, int id) {
	return this.typeSystem.getCapturedWildcard(wildcard, contextType, start, end, cud, id);
}

public WildcardBinding createWildcard(ReferenceBinding genericType, int rank, TypeBinding bound, TypeBinding[] otherBounds, int boundKind, AnnotationBinding [] annotations) {
	return this.typeSystem.getWildcard(genericType, rank, bound, otherBounds, boundKind, annotations);
}

/**
 * Returns the access restriction associated to a given type, or null if none
 */
public AccessRestriction getAccessRestriction(TypeBinding type) {
	return (AccessRestriction) this.accessRestrictions.get(type);
}

/**
 *  Answer the type for the compoundName if it exists in the cache.
 * Answer theNotFoundType if it could not be resolved the first time
 * it was looked up, otherwise answer null.
 *
 * NOTE: Do not use for nested types... the answer is NOT the same for a.b.C or a.b.C.D.E
 * assuming C is a type in both cases. In the a.b.C.D.E case, null is the answer.
 */
public ReferenceBinding getCachedType(char[][] compoundName) {
	if (compoundName.length == 1) {
		return this.defaultPackage.getType0(compoundName[0]);
	}
	PackageBinding packageBinding = getPackage0(compoundName[0]);
	if (packageBinding == null || packageBinding == TheNotFoundPackage)
		return null;

	for (int i = 1, packageLength = compoundName.length - 1; i < packageLength; i++)
		if ((packageBinding = packageBinding.getPackage0Any(compoundName[i])) == null || packageBinding == TheNotFoundPackage)
			return null;
	return packageBinding.getType0(compoundName[compoundName.length - 1]);
}

public AnnotationBinding getNullableAnnotation() {
	if (this.nullableAnnotation != null)
		return this.nullableAnnotation;
	if (this.root != this) {
		return this.nullableAnnotation = this.root.getNullableAnnotation();
	}
	ReferenceBinding nullable = getResolvedType(this.globalOptions.nullableAnnotationName, null);
	return this.nullableAnnotation = this.typeSystem.getAnnotationType(nullable, true);
}

public char[][] getNullableAnnotationName() {
	return this.globalOptions.nullableAnnotationName;
}

public AnnotationBinding getNonNullAnnotation() {
	if (this.nonNullAnnotation != null) 
		return this.nonNullAnnotation;
	if (this.root != this) {
		return this.nonNullAnnotation = this.root.getNonNullAnnotation();
	}
	ReferenceBinding nonNull = getResolvedType(this.globalOptions.nonNullAnnotationName, null);
	return this.nonNullAnnotation = this.typeSystem.getAnnotationType(nonNull, true);
}

public AnnotationBinding[] nullAnnotationsFromTagBits(long nullTagBits) {
	if (nullTagBits == TagBits.AnnotationNonNull)
		return new AnnotationBinding[] { getNonNullAnnotation() };
	else if (nullTagBits == TagBits.AnnotationNullable)
		return new AnnotationBinding[] { getNullableAnnotation() };
	return null;
}

public char[][] getNonNullAnnotationName() {
	return this.globalOptions.nonNullAnnotationName;
}

public char[][] getNonNullByDefaultAnnotationName() {
	return this.globalOptions.nonNullByDefaultAnnotationName;
}

int getNullAnnotationBit(char[][] qualifiedTypeName) {
	if (this.allNullAnnotations == null) {
		this.allNullAnnotations = new HashMap<>();
		this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullAnnotationName), TypeIds.BitNonNullAnnotation);
		this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nullableAnnotationName), TypeIds.BitNullableAnnotation);
		this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullByDefaultAnnotationName), TypeIds.BitNonNullByDefaultAnnotation);
		for (String name : this.globalOptions.nullableAnnotationSecondaryNames)
			this.allNullAnnotations.put(name, TypeIds.BitNullableAnnotation);
		for (String name : this.globalOptions.nonNullAnnotationSecondaryNames)
			this.allNullAnnotations.put(name, TypeIds.BitNonNullAnnotation);
		for (String name : this.globalOptions.nonNullByDefaultAnnotationSecondaryNames)
			this.allNullAnnotations.put(name, TypeIds.BitNonNullByDefaultAnnotation);
	}
	String qualifiedTypeString = CharOperation.toString(qualifiedTypeName);
	Integer typeBit = this.allNullAnnotations.get(qualifiedTypeString);
	return typeBit == null ? 0 : typeBit;
}
public boolean isNullnessAnnotationPackage(PackageBinding pkg) {
	return this.nonnullAnnotationPackage == pkg || this.nullableAnnotationPackage == pkg || this.nonnullByDefaultAnnotationPackage == pkg;
}

public boolean usesNullTypeAnnotations() {
	if(this.root != this) {
		return this.root.usesNullTypeAnnotations();
	}
	if (this.globalOptions.useNullTypeAnnotations != null)
		return this.globalOptions.useNullTypeAnnotations;

	initializeUsesNullTypeAnnotation();
	for (MethodBinding enumMethod : this.deferredEnumMethods) {
		int purpose = 0;
		if (CharOperation.equals(enumMethod.selector, TypeConstants.VALUEOF)) {
			purpose = SyntheticMethodBinding.EnumValueOf;
		} else if (CharOperation.equals(enumMethod.selector, TypeConstants.VALUES)) {
			purpose = SyntheticMethodBinding.EnumValues;
		}
		if (purpose != 0)
			SyntheticMethodBinding.markNonNull(enumMethod, purpose, this);
	}
	this.deferredEnumMethods.clear();
	return this.globalOptions.useNullTypeAnnotations;
}

private void initializeUsesNullTypeAnnotation() {
	this.globalOptions.useNullTypeAnnotations = Boolean.FALSE;
	if (!this.globalOptions.isAnnotationBasedNullAnalysisEnabled || this.globalOptions.originalSourceLevel < ClassFileConstants.JDK1_8)
		return;
	ReferenceBinding nullable;
	ReferenceBinding nonNull;
	boolean origMayTolerateMissingType = this.mayTolerateMissingType;
	this.mayTolerateMissingType = true;
	try {
		nullable = this.nullableAnnotation != null ? this.nullableAnnotation.getAnnotationType()
				: getType(this.getNullableAnnotationName(), this.UnNamedModule); // FIXME(SHMOD) module for null
																					// annotations??
		nonNull = this.nonNullAnnotation != null ? this.nonNullAnnotation.getAnnotationType()
				: getType(this.getNonNullAnnotationName(), this.UnNamedModule);
	} finally {
		this.mayTolerateMissingType = origMayTolerateMissingType;
	}
	if (nullable == null && nonNull == null)
		return;
	if (nullable == null || nonNull == null)
		return; // TODO should report an error about inconsistent setup
	long nullableMetaBits = nullable.getAnnotationTagBits() & TagBits.AnnotationForTypeUse;
	long nonNullMetaBits = nonNull.getAnnotationTagBits() & TagBits.AnnotationForTypeUse;
	if (nullableMetaBits != nonNullMetaBits)
		return; // TODO should report an error about inconsistent setup
	if (nullableMetaBits == 0)
		return;
	this.globalOptions.useNullTypeAnnotations = Boolean.TRUE;
}

/* Answer the top level package named name if it exists in the cache.
* Answer theNotFoundPackage if it could not be resolved the first time
* it was looked up, otherwise answer null.
*
* NOTE: Senders must convert theNotFoundPackage into a real problem
* package if its to returned.
*/
PackageBinding getPackage0(char[] name) {
	return this.knownPackages.get(name);
}

/* Answer the type corresponding to the compoundName.
* Ask the name environment for the type if its not in the cache.
* Fail with a classpath error if the type cannot be found.
*/
public ReferenceBinding getResolvedType(char[][] compoundName, Scope scope) {
	return getResolvedType(compoundName, scope == null ? this.UnNamedModule : scope.module(), scope);
}
public ReferenceBinding getResolvedType(char[][] compoundName, ModuleBinding moduleBinding, Scope scope) {
	if (this.module != moduleBinding)
		return moduleBinding.environment.getResolvedType(compoundName, moduleBinding, scope);
	ReferenceBinding type = getType(compoundName, moduleBinding);
	if (type != null) return type;

	// create a proxy for the missing BinaryType
	// report the missing class file first
	this.problemReporter.isClassPathCorrect(
		compoundName,
		scope == null ? this.root.unitBeingCompleted : scope.referenceCompilationUnit(),
		this.missingClassFileLocation);
	return createMissingType(null, compoundName);
}
public ReferenceBinding getResolvedJavaBaseType(char[][] compoundName, Scope scope) {
	return getResolvedType(compoundName, javaBaseModule(), scope);
}

/* Answer the top level package named name.
* Ask the oracle for the package if its not in the cache.
* Answer null if the package cannot be found.
*/
PackageBinding getTopLevelPackage(char[] name) {
	PackageBinding packageBinding = getPackage0(name);
	if (packageBinding != null) {
		if (packageBinding == TheNotFoundPackage)
			return null;
		return packageBinding;
	}
	if (this.useModuleSystem) {
		if (this.module != null)
			packageBinding = this.module.getTopLevelPackage(name);
	} else {
		if (this.nameEnvironment.isPackage(null, name)) {
			this.knownPackages.put(name, packageBinding = new PackageBinding(name, this, this.module));
		}
	}
	if (packageBinding != null) {
		if (packageBinding == TheNotFoundPackage)
			return null;
		return packageBinding;
	}

	this.knownPackages.put(name, TheNotFoundPackage); // saves asking the oracle next time
	return null;
}

public ReferenceBinding getType(char[][] compoundName) {
	return getType(compoundName, this.UnNamedModule);
}
/* Answer the type corresponding to the compoundName.
* Ask the name environment for the type if its not in the cache.
* Answer null if the type cannot be found.
*/
public ReferenceBinding getType(char[][] compoundName, ModuleBinding mod) {
	ReferenceBinding referenceBinding;

	if (compoundName.length == 1) {
		if ((referenceBinding = this.defaultPackage.getType0(compoundName[0])) == null) {
			PackageBinding packageBinding = getPackage0(compoundName[0]);
			if (packageBinding != null && packageBinding != TheNotFoundPackage)
				return null; // collides with a known package... should not call this method in such a case
			referenceBinding = askForType(this.defaultPackage, compoundName[0], mod);
		}
	} else {
		PackageBinding packageBinding = getPackage0(compoundName[0]);
		if (packageBinding == TheNotFoundPackage)
			return null;

		if (packageBinding != null) {
			for (int i = 1, packageLength = compoundName.length - 1; i < packageLength; i++) {
				if ((packageBinding = packageBinding.getPackage0(compoundName[i])) == null)
					break;
				if (packageBinding == TheNotFoundPackage)
					return null;
			}
		}

		if (packageBinding == null)
			referenceBinding = askForType(compoundName, mod);
		else if ((referenceBinding = packageBinding.getType0(compoundName[compoundName.length - 1])) == null)
			referenceBinding = askForType(packageBinding, compoundName[compoundName.length - 1], mod);
	}

	if (referenceBinding == null || referenceBinding == TheNotFoundType)
		return null;
	referenceBinding = (ReferenceBinding) BinaryTypeBinding.resolveType(referenceBinding, this, false /* no raw conversion for now */);

	// compoundName refers to a nested type incorrectly (for example, package1.A$B)
//	if (referenceBinding.isNestedType())
//		return new ProblemReferenceBinding(compoundName, referenceBinding, InternalNameProvided);
	return referenceBinding;
}

private TypeBinding[] getTypeArgumentsFromSignature(SignatureWrapper wrapper, TypeVariableBinding[] staticVariables, ReferenceBinding enclosingType, ReferenceBinding genericType,
		char[][][] missingTypeNames, ITypeAnnotationWalker walker)
{
	java.util.ArrayList args = new java.util.ArrayList(2);
	int rank = 0;
	do {
		args.add(getTypeFromVariantTypeSignature(wrapper, staticVariables, enclosingType, genericType, rank, missingTypeNames,
					walker.toTypeArgument(rank++)));
	} while (wrapper.signature[wrapper.start] != '>');
	wrapper.start++; // skip '>'
	TypeBinding[] typeArguments = new TypeBinding[args.size()];
	args.toArray(typeArguments);
	return typeArguments;
}

/* Answer the type corresponding to the compound name.
* Does not ask the oracle for the type if its not found in the cache... instead an
* unresolved type is returned which must be resolved before used.
*
* NOTE: Does NOT answer base types nor array types!
*/
private ReferenceBinding getTypeFromCompoundName(char[][] compoundName, boolean isParameterized, boolean wasMissingType) {
	ReferenceBinding binding = getCachedType(compoundName);
	if (binding == null) {
		PackageBinding packageBinding = computePackageFrom(compoundName, false /* valid pkg */);
		if(this.useModuleSystem) {
			// the package might not have been seen in getCachedType, so retry
			binding = packageBinding.getType0(compoundName[compoundName.length - 1]);
		}
		if(binding == null) {
			binding = new UnresolvedReferenceBinding(compoundName, packageBinding);
			if (wasMissingType) {
				binding.tagBits |= TagBits.HasMissingType; // record it was bound to a missing type
			}
			packageBinding.addType(binding);
		}
	} else if (binding == TheNotFoundType) {
		// report the missing class file first
		if (!wasMissingType) {
			/* Since missing types have been already been complained against while producing binaries, there is no class path 
			 * misconfiguration now that did not also exist in some equivalent form while producing the class files which encode 
			 * these missing types. So no need to bark again. Note that wasMissingType == true signals a type referenced in a .class 
			 * file which could not be found when the binary was produced. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=364450 */
			this.problemReporter.isClassPathCorrect(compoundName, this.root.unitBeingCompleted, this.missingClassFileLocation);
		}
		// create a proxy for the missing BinaryType
		binding = createMissingType(null, compoundName);
	} else if (!isParameterized) {
	    // check raw type, only for resolved types
        binding = (ReferenceBinding) convertUnresolvedBinaryToRawType(binding);
	}
	return binding;
}

/* Answer the type corresponding to the name from the binary file.
* Does not ask the oracle for the type if its not found in the cache... instead an
* unresolved type is returned which must be resolved before used.
*
* NOTE: Does NOT answer base types nor array types!
*/
ReferenceBinding getTypeFromConstantPoolName(char[] signature, int start, int end, boolean isParameterized, char[][][] missingTypeNames, ITypeAnnotationWalker walker) {
	if (end == -1)
		end = signature.length;
	char[][] compoundName = CharOperation.splitOn('/', signature, start, end);
	boolean wasMissingType = false;
	if (missingTypeNames != null) {
		for (int i = 0, max = missingTypeNames.length; i < max; i++) {
			if (CharOperation.equals(compoundName, missingTypeNames[i])) {
				wasMissingType = true;
				break;
			}
		}
	}
	ReferenceBinding binding = getTypeFromCompoundName(compoundName, isParameterized, wasMissingType);
	if (walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER) {
		binding = (ReferenceBinding) annotateType(binding, walker, missingTypeNames);
	}
	return binding;
}

ReferenceBinding getTypeFromConstantPoolName(char[] signature, int start, int end, boolean isParameterized, char[][][] missingTypeNames) {
	return getTypeFromConstantPoolName(signature, start, end, isParameterized, missingTypeNames, ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER);
}

/* Answer the type corresponding to the signature from the binary file.
* Does not ask the oracle for the type if its not found in the cache... instead an
* unresolved type is returned which must be resolved before used.
*
* NOTE: Does answer base types & array types.
*/
TypeBinding getTypeFromSignature(char[] signature, int start, int end, boolean isParameterized, TypeBinding enclosingType, 
		char[][][] missingTypeNames, ITypeAnnotationWalker walker)
{
	int dimension = 0;
	while (signature[start] == '[') {
		start++;
		dimension++;
	}
	// annotations on dimensions?
	AnnotationBinding [][] annotationsOnDimensions = null;
	if (dimension > 0 && walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER) {
		for (int i = 0; i < dimension; i++) {
			AnnotationBinding [] annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(0, true), this, missingTypeNames);
			if (annotations != Binding.NO_ANNOTATIONS) { 
				if (annotationsOnDimensions == null)
					annotationsOnDimensions = new AnnotationBinding[dimension][];
					annotationsOnDimensions[i] = annotations;
			}
			walker = walker.toNextArrayDimension();
		}
	}
	
	if (end == -1)
		end = signature.length - 1;

	// Just switch on signature[start] - the L case is the else
	TypeBinding binding = null;
	if (start == end) {
		switch (signature[start]) {
			case 'I' :
				binding = TypeBinding.INT;
				break;
			case 'Z' :
				binding = TypeBinding.BOOLEAN;
				break;
			case 'V' :
				binding = TypeBinding.VOID;
				break;
			case 'C' :
				binding = TypeBinding.CHAR;
				break;
			case 'D' :
				binding = TypeBinding.DOUBLE;
				break;
			case 'B' :
				binding = TypeBinding.BYTE;
				break;
			case 'F' :
				binding = TypeBinding.FLOAT;
				break;
			case 'J' :
				binding = TypeBinding.LONG;
				break;
			case 'S' :
				binding = TypeBinding.SHORT;
				break;
			default :
				this.problemReporter.corruptedSignature(enclosingType, signature, start);
				// will never reach here, since error will cause abort
		}
	} else {
		binding = getTypeFromConstantPoolName(signature, start + 1, end, isParameterized, missingTypeNames); // skip leading 'L' or 'T'
	}
	
	if (isParameterized) {
		if (dimension != 0)
			throw new IllegalStateException();
		return binding;
	}
	
	if (walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER) {
		binding = annotateType(binding, walker, missingTypeNames);
	}
	
	if (dimension != 0)
		binding =  this.typeSystem.getArrayType(binding, dimension, AnnotatableTypeSystem.flattenedAnnotations(annotationsOnDimensions));
	
	return binding;
}

private TypeBinding annotateType(TypeBinding binding, ITypeAnnotationWalker walker, char[][][] missingTypeNames) {
	int depth = binding.depth() + 1;
	if (depth > 1) {
		// need to count non-static nesting levels, resolved binding required for precision
		if (binding.isUnresolvedType())
			binding = ((UnresolvedReferenceBinding) binding).resolve(this, true);
		depth = countNonStaticNestingLevels(binding) + 1;
	}
	AnnotationBinding [][] annotations = null;
	for (int i = 0; i < depth; i++) {
		AnnotationBinding[] annots = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(binding.id, i == depth - 1), this, missingTypeNames);
		if (annots != null && annots.length > 0) {
			if (annotations == null)
				annotations = new AnnotationBinding[depth][];
			annotations[i] = annots;
		}
		walker = walker.toNextNestedType();
	}
	if (annotations != null)
		binding = createAnnotatedType(binding, annotations);
	return binding;
}

// compute depth below lowest static enclosingType
private int countNonStaticNestingLevels(TypeBinding binding) {
	if (binding.isUnresolvedType()) {
		throw new IllegalStateException();
	}
	int depth = -1;
	TypeBinding currentBinding = binding;
	while (currentBinding != null) {
		depth++;
		if (currentBinding.isStatic())
			break;
		currentBinding = currentBinding.enclosingType();
	}
	return depth;
}

boolean qualifiedNameMatchesSignature(char[][] name, char[] signature) {
	int s = 1; // skip 'L'
	for (int i = 0; i < name.length; i++) {
		char[] n = name[i];
		for (int j = 0; j < n.length; j++)
			if (n[j] != signature[s++])
				return false;
		if (signature[s] == ';' && i == name.length-1)
			return true;
		if (signature[s++] != '/')
			return false;
	}
	return false;
}

public TypeBinding getTypeFromTypeSignature(SignatureWrapper wrapper, TypeVariableBinding[] staticVariables, ReferenceBinding enclosingType, 
		char[][][] missingTypeNames, ITypeAnnotationWalker walker) 
{
	// TypeVariableSignature = 'T' Identifier ';'
	// ArrayTypeSignature = '[' TypeSignature
	// ClassTypeSignature = 'L' Identifier TypeArgs(optional) ';'
	//   or ClassTypeSignature '.' 'L' Identifier TypeArgs(optional) ';'
	// TypeArgs = '<' VariantTypeSignature VariantTypeSignatures '>'
	int dimension = 0;
	while (wrapper.signature[wrapper.start] == '[') {
		wrapper.start++;
		dimension++;
	}
	// annotations on dimensions?
	AnnotationBinding [][] annotationsOnDimensions = null;
	if (dimension > 0 && walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER) {
		for (int i = 0; i < dimension; i++) {
			AnnotationBinding [] annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(0, true), this, missingTypeNames);
			if (annotations != Binding.NO_ANNOTATIONS) { 
				if (annotationsOnDimensions == null)
					annotationsOnDimensions = new AnnotationBinding[dimension][];
					annotationsOnDimensions[i] = annotations;
			}
			walker = walker.toNextArrayDimension();
		}
	}
	if (wrapper.signature[wrapper.start] == 'T') {
	    int varStart = wrapper.start + 1;
	    int varEnd = wrapper.computeEnd();
		for (int i = staticVariables.length; --i >= 0;)
			if (CharOperation.equals(staticVariables[i].sourceName, wrapper.signature, varStart, varEnd))
				return getTypeFromTypeVariable(staticVariables[i], dimension, annotationsOnDimensions, walker, missingTypeNames);
	    ReferenceBinding initialType = enclosingType;
		do {
			TypeVariableBinding[] enclosingTypeVariables;
			if (enclosingType instanceof BinaryTypeBinding) { // compiler normal case, no eager resolution of binary variables
				enclosingTypeVariables = ((BinaryTypeBinding)enclosingType).typeVariables; // do not trigger resolution of variables
			} else { // codepath only use by codeassist for decoding signatures
				enclosingTypeVariables = enclosingType.typeVariables();
			}
			for (int i = enclosingTypeVariables.length; --i >= 0;)
				if (CharOperation.equals(enclosingTypeVariables[i].sourceName, wrapper.signature, varStart, varEnd))
					return getTypeFromTypeVariable(enclosingTypeVariables[i], dimension, annotationsOnDimensions, walker, missingTypeNames);
		} while ((enclosingType = enclosingType.enclosingType()) != null);
		this.problemReporter.undefinedTypeVariableSignature(CharOperation.subarray(wrapper.signature, varStart, varEnd), initialType);
		return null; // cannot reach this, since previous problem will abort compilation
	}
	boolean isParameterized;
	TypeBinding type = getTypeFromSignature(wrapper.signature, wrapper.start, wrapper.computeEnd(), isParameterized = (wrapper.end == wrapper.bracket), enclosingType, missingTypeNames, walker);

	if (!isParameterized)
		return dimension == 0 ? type : createArrayType(type, dimension, AnnotatableTypeSystem.flattenedAnnotations(annotationsOnDimensions));

	// type must be a ReferenceBinding at this point, cannot be a BaseTypeBinding or ArrayTypeBinding
	ReferenceBinding actualType = (ReferenceBinding) type;
	if (actualType instanceof UnresolvedReferenceBinding)
		if (actualType.depth() > 0)
			actualType = (ReferenceBinding) BinaryTypeBinding.resolveType(actualType, this, false /* no raw conversion */); // must resolve member types before asking for enclosingType
	ReferenceBinding actualEnclosing = actualType.enclosingType();

	ITypeAnnotationWalker savedWalker = walker;
	if(actualType.depth() > 0) {
		int nonStaticNestingLevels = countNonStaticNestingLevels(actualType);
		for (int i = 0; i < nonStaticNestingLevels; i++) {
			walker = walker.toNextNestedType();
		}
	}

	TypeBinding[] typeArguments = getTypeArgumentsFromSignature(wrapper, staticVariables, enclosingType, actualType, missingTypeNames, walker);
	ReferenceBinding currentType = createParameterizedType(actualType, typeArguments, actualEnclosing);
	ReferenceBinding plainCurrent = actualType;

	while (wrapper.signature[wrapper.start] == '.') {
		wrapper.start++; // skip '.'
		int memberStart = wrapper.start;
		char[] memberName = wrapper.nextWord();
		plainCurrent = (ReferenceBinding) BinaryTypeBinding.resolveType(plainCurrent, this, false);
		ReferenceBinding memberType = plainCurrent.getMemberType(memberName);
		// need to protect against the member type being null when the signature is invalid
		if (memberType == null)
			this.problemReporter.corruptedSignature(currentType, wrapper.signature, memberStart); // aborts
		if(memberType.isStatic()) {
			// may happen for class files generated by eclipse before bug 460491 was fixed. 
			walker = savedWalker;
		} else {
			walker = walker.toNextNestedType();
		}
		if (wrapper.signature[wrapper.start] == '<') {
			wrapper.start++; // skip '<'
			typeArguments = getTypeArgumentsFromSignature(wrapper, staticVariables, enclosingType, memberType, missingTypeNames, walker);
		} else {
			typeArguments = null;
		}
		if (typeArguments != null || 											// has type arguments, or ... 
				(!memberType.isStatic() && currentType.isParameterizedType())) 	// ... can see type arguments of enclosing
		{
			if (memberType.isStatic())
				currentType = plainCurrent; // ignore bogus parameterization of enclosing
			currentType = createParameterizedType(memberType, typeArguments, currentType);
		} else {
			currentType = memberType;
		}
		plainCurrent = memberType;
	}
	wrapper.start++; // skip ';'
	TypeBinding annotatedType = annotateType(currentType, savedWalker, missingTypeNames);
	return dimension == 0 ? annotatedType : createArrayType(annotatedType, dimension, AnnotatableTypeSystem.flattenedAnnotations(annotationsOnDimensions));
}

private TypeBinding getTypeFromTypeVariable(TypeVariableBinding typeVariableBinding, int dimension, AnnotationBinding [][] annotationsOnDimensions, ITypeAnnotationWalker walker, char [][][] missingTypeNames) {
	AnnotationBinding [] annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(-1, false), this, missingTypeNames);
	if (annotations != null && annotations != Binding.NO_ANNOTATIONS)
		typeVariableBinding = (TypeVariableBinding) createAnnotatedType(typeVariableBinding, new AnnotationBinding [][] { annotations });

	if (dimension == 0) {
		return typeVariableBinding;
	}
	return this.typeSystem.getArrayType(typeVariableBinding, dimension, AnnotatableTypeSystem.flattenedAnnotations(annotationsOnDimensions));
}

TypeBinding getTypeFromVariantTypeSignature(
		SignatureWrapper wrapper,
		TypeVariableBinding[] staticVariables,
		ReferenceBinding enclosingType,
		ReferenceBinding genericType,
		int rank,
		char[][][] missingTypeNames,
		ITypeAnnotationWalker walker) {
	// VariantTypeSignature = '-' TypeSignature
	//   or '+' TypeSignature
	//   or TypeSignature
	//   or '*'
	switch (wrapper.signature[wrapper.start]) {
		case '-' :
			// ? super aType
			wrapper.start++;
			TypeBinding bound = getTypeFromTypeSignature(wrapper, staticVariables, enclosingType, missingTypeNames, walker.toWildcardBound());
			AnnotationBinding [] annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(-1, false), this, missingTypeNames);
			return this.typeSystem.getWildcard(genericType, rank, bound, null /*no extra bound*/, Wildcard.SUPER, annotations);
		case '+' :
			// ? extends aType
			wrapper.start++;
			bound = getTypeFromTypeSignature(wrapper, staticVariables, enclosingType, missingTypeNames, walker.toWildcardBound());
			annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(-1, false), this, missingTypeNames);
			return this.typeSystem.getWildcard(genericType, rank, bound, null /*no extra bound*/, Wildcard.EXTENDS, annotations);
		case '*' :
			// ?
			wrapper.start++;
			annotations = BinaryTypeBinding.createAnnotations(walker.getAnnotationsAtCursor(-1, false), this, missingTypeNames);
			return this.typeSystem.getWildcard(genericType, rank, null, null /*no extra bound*/, Wildcard.UNBOUND, annotations);
		default :
			return getTypeFromTypeSignature(wrapper, staticVariables, enclosingType, missingTypeNames, walker);
	}
}

boolean isMissingType(char[] typeName) {
	for (int i = this.missingTypes == null ? 0 : this.missingTypes.size(); --i >= 0;) {
		MissingTypeBinding missingType = (MissingTypeBinding) this.missingTypes.get(i);
		if (CharOperation.equals(missingType.sourceName, typeName))
			return true;
	}
	return false;
}

// The method verifier is lazily initialized to guarantee the receiver, the compiler & the oracle are ready.
public MethodVerifier methodVerifier() {
	 // TODO(SHMOD): I'm not sure if the verifier would need to be created with a specific LE?
	if (this.verifier == null)
		this.verifier = newMethodVerifier();
	return this.verifier;
}

public MethodVerifier newMethodVerifier() {
	/* Always use MethodVerifier15. Even in a 1.4 project, we must internalize type variables and
	   observe any parameterization of super class and/or super interfaces in order to be able to
	   detect overriding in the presence of generics.
	   See https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850
	 */
	return new MethodVerifier15(this);
}

public void releaseClassFiles(org.eclipse.jdt.internal.compiler.ClassFile[] classFiles) {
	for (int i = 0, fileCount = classFiles.length; i < fileCount; i++)
		this.classFilePool.release(classFiles[i]);
}

public void reset() {
	if (this.root != this) {
		this.root.reset();
		return;
	}
	this.knownModules = new HashtableOfModule();
	this.UnNamedModule = new ModuleBinding.UnNamedModule(this);
	this.module = this.UnNamedModule;
	this.JavaBaseModule = null;

	this.defaultPackage = new PackageBinding(this); // assume the default package always exists
	this.defaultImports = null;
	this.knownPackages = new HashtableOfPackage();
	this.accessRestrictions = new HashMap(3);

	this.verifier = null;
	
	// NOTE: remember to fix #updateCaches(...) when adding unique binding caches
	this.uniqueParameterizedGenericMethodBindings = new SimpleLookupTable(3);
	this.uniquePolymorphicMethodBindings = new SimpleLookupTable(3);
	this.uniqueGetClassMethodBinding = null;
	this.missingTypes = null;
	this.typesBeingConnected = new HashSet();

	for (int i = this.units.length; --i >= 0;)
		this.units[i] = null;
	this.lastUnitIndex = -1;
	this.lastCompletedUnitIndex = -1;
	this.unitBeingCompleted = null; // in case AbortException occurred

	this.classFilePool.reset();
	this.typeSystem.reset();
	// name environment has a longer life cycle, and must be reset in
	// the code which created it.
}

/**
 * Associate a given type with some access restriction
 * (did not store the restriction directly into binding, since sparse information)
 */
public void setAccessRestriction(ReferenceBinding type, AccessRestriction accessRestriction) {
	if (accessRestriction == null) return;
	type.modifiers |= ExtraCompilerModifiers.AccRestrictedAccess;
	this.accessRestrictions.put(type, accessRestriction);
}

void updateCaches(UnresolvedReferenceBinding unresolvedType, ReferenceBinding resolvedType) {
	this.typeSystem.updateCaches(unresolvedType, resolvedType);
}

public IQualifiedTypeResolutionListener[] resolutionListeners;	// ROOT_ONLY

public void addResolutionListener(IQualifiedTypeResolutionListener resolutionListener) {
	synchronized (this.root) {
		int length = this.root.resolutionListeners.length;
		for (int i = 0; i < length; i++){
			if (this.root.resolutionListeners[i].equals(resolutionListener))
				return;
		}
		System.arraycopy(this.root.resolutionListeners, 0,
				this.root.resolutionListeners = new IQualifiedTypeResolutionListener[length + 1], 0, length);
		this.root.resolutionListeners[length] = resolutionListener;
	}
}


public TypeBinding getUnannotatedType(TypeBinding typeBinding) {
	return this.typeSystem.getUnannotatedType(typeBinding);
}

// Given a type, return all its variously annotated versions.
public TypeBinding[] getAnnotatedTypes(TypeBinding type) {
	return this.typeSystem.getAnnotatedTypes(type);
}

public AnnotationBinding[] filterNullTypeAnnotations(AnnotationBinding[] typeAnnotations) {
	if (typeAnnotations.length == 0)
		return typeAnnotations;
	AnnotationBinding[] filtered = new AnnotationBinding[typeAnnotations.length];
	int count = 0;
	for (int i = 0; i < typeAnnotations.length; i++) {
		AnnotationBinding typeAnnotation = typeAnnotations[i];
		if (typeAnnotation == null) {
			count++; // sentinel in annotation sequence for array dimensions
		} else {
			if (!typeAnnotation.type.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation))
				filtered[count++] = typeAnnotation;
		}
	}
	if (count == 0)
		return Binding.NO_ANNOTATIONS;
	if (count == typeAnnotations.length)
		return typeAnnotations;
	System.arraycopy(filtered, 0, filtered = new AnnotationBinding[count], 0, count);
	return filtered;
}

public boolean containsNullTypeAnnotation(IBinaryAnnotation[] typeAnnotations) {
	if (typeAnnotations.length == 0)
		return false;
	for (int i = 0; i < typeAnnotations.length; i++) {
		IBinaryAnnotation typeAnnotation = typeAnnotations[i];
		char[] typeName = typeAnnotation.getTypeName();
		// typeName must be "Lfoo/X;"
		if (typeName == null || typeName.length < 3 || typeName[0] != 'L') continue;
		char[][] name = CharOperation.splitOn('/', typeName, 1, typeName.length-1);
		if (getNullAnnotationBit(name) != 0)
			return true;
	}
	return false;
}
public boolean containsNullTypeAnnotation(AnnotationBinding[] typeAnnotations) {
	if (typeAnnotations.length == 0)
		return false;
	for (int i = 0; i < typeAnnotations.length; i++) {
		AnnotationBinding typeAnnotation = typeAnnotations[i];
		if (typeAnnotation.type.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation))
			return true;
	}
	return false;	
}
}
