org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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 PlainPackageBinding 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; // ROOT_ONLY
    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;

    /**
     * Flag that should be set during annotation traversal or similar runs
     * to prevent caching of failures regarding imports of yet to be generated classes.
     */
    public boolean suppressImportErrors; // per module

    public String moduleVersion; // ROOT_ONLY

    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, null/*not perfect*/);
    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 PlainPackageBinding(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 PlainPackageBinding(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;
            if (candidate != null && candidate.problemId() == ProblemReasons.Ambiguous)
                return candidate; // saw enough
            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
                answerPackage = 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);
        }
        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 (one.fPackage == null || !clientModule.canAccess(one.fPackage))
            return two;
        if (two.fPackage == null || !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) {
                    if (answer.moduleBinding == null) {
                        char[] nameFromAnswer = answer.moduleName();
                        if (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);
                if (binding.isValidBinding())
                    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;
        CompilationUnitDeclaration previousUnitBeingCompleted = rootEnv.unitBeingCompleted;
        (rootEnv.unitBeingCompleted = parsedUnit).scope.checkAndSetImports();
        parsedUnit.scope.connectTypeHierarchy();
        parsedUnit.scope.checkParameterizedTypes();
        if (buildFieldsAndMethods)
            parsedUnit.scope.buildFieldsAndMethods();
        rootEnv.unitBeingCompleted = previousUnitBeingCompleted;
    }

    /*
    * 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:
        case Binding.INTERSECTION_TYPE18:
            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();
        }
        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)
                            .getUniqueModulesDeclaringPackage(new char[][] { 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 = this.module.createDeclaredToplevelPackage(constantPoolName[0]);
            }
            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[][] currentCompoundName = CharOperation.arrayConcat(parent.compoundName,
                                constantPoolName[i]);
                        char[][] declaringModules = ((IModuleAwareNameEnvironment) this.nameEnvironment)
                                .getModulesDeclaringPackage(currentCompoundName, ModuleBinding.ANY);
                        if (declaringModules != null) {
                            for (char[] mod : declaringModules) {
                                ModuleBinding declaringModule = this.root.getModule(mod);
                                if (declaringModule != null)
                                    packageBinding = SplitPackageBinding.combine(
                                            declaringModule.getVisiblePackage(currentCompoundName), packageBinding,
                                            this.module);
                            }
                        }
                    } else {
                        packageBinding = this.module.getVisiblePackage(parent, constantPoolName[i]);
                    }
                }
                if (packageBinding == null || packageBinding == TheNotFoundPackage) {
                    packageBinding = this.module
                            .createDeclaredPackage(CharOperation.subarray(constantPoolName, 0, i + 1), parent);
                }
                if (isMissing) {
                    packageBinding.tagBits |= TagBits.HasMissingType;
                }
                packageBinding = parent.addPackage(packageBinding, this.module);
            }
        }
        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) {
        return createPlainPackage(compoundName);
    }

    public PlainPackageBinding createPlainPackage(char[][] compoundName) {
        PackageBinding packageBinding = this.module.getDeclaredPackage(CharOperation.concatWith(compoundName, '.'));
        if (packageBinding != null && packageBinding.isValidBinding()) {
            // restart from the toplevel package to proceed with clash analysis below
            packageBinding = this.getTopLevelPackage(compoundName[0]);
        } else {
            packageBinding = getPackage0(compoundName[0]);
            if (packageBinding == null || packageBinding == TheNotFoundPackage) {
                packageBinding = this.module.getOrCreateDeclaredPackage(new char[][] { compoundName[0] });
                if (this.useModuleSystem) {
                    char[][] declaringModuleNames = null;
                    if (this.module.isUnnamed()) {
                        IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.nameEnvironment;
                        declaringModuleNames = moduleEnv.getUniqueModulesDeclaringPackage(
                                new char[][] { packageBinding.readableName() }, ModuleBinding.ANY);
                    }
                    packageBinding = this.module.combineWithPackagesFromOtherRelevantModules(packageBinding,
                            packageBinding.compoundName, declaringModuleNames);
                }
                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
            if (packageBinding.hasType0Any(compoundName[i]))
                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;
                    }
                }
                PackageBinding singleParent = parent.getIncarnation(this.module);
                if (singleParent != parent && singleParent != null) {
                    // parent.getPackage0() may have been too shy, so drill into the split:
                    packageBinding = singleParent.getPackage0(compoundName[i]);
                }
                if (packageBinding == null) {
                    packageBinding = this.module
                            .createDeclaredPackage(CharOperation.subarray(compoundName, 0, i + 1), parent);
                    packageBinding = parent.addPackage(packageBinding, this.module);
                }
            }
        }
        return packageBinding.getIncarnation(this.module);
    }

    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, Scope scope) {
        // 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;
                    if (scope instanceof BlockScope && ptb.expression.resolvedType == null) {
                        ptb.expression.setExpectedType(scope.getJavaLangObject());
                        parametersTypeBinding[i] = ptb.expression.resolveType((BlockScope) scope);
                    } else {
                        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) {
        ReferenceBinding result = getCachedType0(compoundName);
        if (result == null && this.useModuleSystem) {
            ModuleBinding[] modulesToSearch = this.module.isUnnamed() || this.module.isAuto
                    ? this.root.knownModules.valueTable
                    : this.module.getAllRequiredModules();
            for (ModuleBinding someModule : modulesToSearch) {
                if (someModule == null)
                    continue;
                result = someModule.environment.getCachedType0(compoundName);
                if (result != null && result.isValidBinding())
                    break;
            }
        }
        return result;
    }

    public ReferenceBinding getCachedType0(char[][] compoundName) {
        if (compoundName.length == 1) {
            return this.defaultPackage.getType0(compoundName[0]);
        }
        PackageBinding packageBinding = getPackage0(compoundName[0]);
        if (packageBinding == null || packageBinding == TheNotFoundPackage)
            return null;
        // we should be asking via the correct LE, so peel any SPB at the root:
        packageBinding = packageBinding.getIncarnation(this.module);
        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) {
        if (this.useModuleSystem) {
            return this.module.getTopLevelPackage(name);
        }
        PackageBinding packageBinding = getPackage0(name);
        if (packageBinding != null) {
            if (packageBinding == TheNotFoundPackage)
                return null;
            return packageBinding;
        }
        if (this.nameEnvironment.isPackage(null, name)) {
            this.knownPackages.put(name, packageBinding = this.module.createDeclaredToplevelPackage(name));
            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);
            }
        }
        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) {
        if (walker == ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER) {
            return binding;
        }
        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 (walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER
                && 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 (walker != ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER && 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.stepCompleted = 0;
        this.knownModules = new HashtableOfModule();
        this.UnNamedModule = new ModuleBinding.UnNamedModule(this);
        this.module = this.UnNamedModule;
        this.JavaBaseModule = null;

        this.defaultPackage = new PlainPackageBinding(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;
    }

    public Binding getInaccessibleBinding(char[][] compoundName, ModuleBinding clientModule) {
        if (this.root != this)
            return this.root.getInaccessibleBinding(compoundName, clientModule);
        if (this.nameEnvironment instanceof IModuleAwareNameEnvironment) {
            IModuleAwareNameEnvironment moduleEnv = (IModuleAwareNameEnvironment) this.nameEnvironment;
            int length = compoundName.length;
            for (int j = length; j > 0; j--) {
                char[][] candidateName = CharOperation.subarray(compoundName, 0, j);
                char[][] moduleNames = moduleEnv.getModulesDeclaringPackage(candidateName, ModuleBinding.ANY);
                if (moduleNames != null) {
                    // in some module a package named candidateName exists, verify observability & inaccessibility:
                    PackageBinding inaccessiblePackage = null;
                    for (char[] moduleName : moduleNames) {
                        if (moduleName == ModuleBinding.UNOBSERVABLE)
                            continue;
                        ModuleBinding mod = getModule(moduleName);
                        if (mod != null) {
                            PackageBinding pack = mod.getVisiblePackage(candidateName);
                            if (pack != null && pack.isValidBinding()) {
                                if (clientModule.canAccess(pack))
                                    return null;
                                inaccessiblePackage = pack;
                            }
                        }
                    }
                    if (inaccessiblePackage == null)
                        return null;
                    if (j < length) {
                        // does the package even contain a type of the next name segment?
                        TypeBinding type = inaccessiblePackage.getType(compoundName[j],
                                inaccessiblePackage.enclosingModule);
                        if (type instanceof ReferenceBinding && type.isValidBinding())
                            return new ProblemReferenceBinding(compoundName, (ReferenceBinding) type,
                                    ProblemReasons.NotAccessible);
                    }
                    return new ProblemPackageBinding(candidateName, ProblemReasons.NotAccessible, this);
                }
            }
        }
        return null;
    }
}