com.redhat.ceylon.eclipse.core.model.JDTModelLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.ceylon.eclipse.core.model.JDTModelLoader.java

Source

/*
 * Copyright Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the authors tag. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License version 2.
 * 
 * This particular file is subject to the "Classpath" exception as provided in the 
 * LICENSE file that accompanied this code.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */

package com.redhat.ceylon.eclipse.core.model;

import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.isInCeylonClassesOutputFolder;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.WeakHashMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.ISourceType;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.core.BasicCompilationUnit;
import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jdt.internal.core.ClassFile;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.JavaElementRequestor;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.NameLookup;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;

import com.redhat.ceylon.cmr.api.ArtifactResult;
import com.redhat.ceylon.cmr.api.JDKUtils;
import com.redhat.ceylon.compiler.java.codegen.Naming;
import com.redhat.ceylon.compiler.java.loader.TypeFactory;
import com.redhat.ceylon.compiler.java.util.Timer;
import com.redhat.ceylon.compiler.java.util.Util;
import com.redhat.ceylon.compiler.loader.AbstractModelLoader;
import com.redhat.ceylon.compiler.loader.ModelResolutionException;
import com.redhat.ceylon.compiler.loader.SourceDeclarationVisitor;
import com.redhat.ceylon.compiler.loader.TypeParser;
import com.redhat.ceylon.compiler.loader.mirror.ClassMirror;
import com.redhat.ceylon.compiler.loader.mirror.MethodMirror;
import com.redhat.ceylon.compiler.loader.model.LazyClass;
import com.redhat.ceylon.compiler.loader.model.LazyInterface;
import com.redhat.ceylon.compiler.loader.model.LazyMethod;
import com.redhat.ceylon.compiler.loader.model.LazyModule;
import com.redhat.ceylon.compiler.loader.model.LazyPackage;
import com.redhat.ceylon.compiler.loader.model.LazyValue;
import com.redhat.ceylon.compiler.typechecker.context.Context;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.Modules;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Unit;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ModuleDescriptor;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PackageDescriptor;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.classpath.CeylonClasspathUtil;
import com.redhat.ceylon.eclipse.core.classpath.CeylonProjectModulesContainer;
import com.redhat.ceylon.eclipse.core.model.mirror.JDTClass;
import com.redhat.ceylon.eclipse.core.model.mirror.JDTMethod;
import com.redhat.ceylon.eclipse.core.model.mirror.SourceClass;
import com.redhat.ceylon.eclipse.core.model.mirror.SourceDeclarationHolder;

/**
 * A model loader which uses the JDT model.
 *
 * @author David Festal <david.festal@serli.com>
 */
public class JDTModelLoader extends AbstractModelLoader {

    private IJavaProject javaProject;
    private CompilerOptions compilerOptions;

    private ProblemReporter problemReporter;
    private LookupEnvironment lookupEnvironment;
    private MissingTypeBinding missingTypeBinding;
    private final Object lookupEnvironmentMutex = new Object();
    private boolean mustResetLookupEnvironment = false;
    private Set<Module> modulesInClassPath = new HashSet<Module>();

    public JDTModelLoader(final JDTModuleManager moduleManager, final Modules modules) {
        this.moduleManager = moduleManager;
        this.modules = modules;
        javaProject = moduleManager.getJavaProject();
        if (javaProject != null) {
            compilerOptions = new CompilerOptions(javaProject.getOptions(true));
            compilerOptions.ignoreMethodBodies = true;
            compilerOptions.storeAnnotations = true;
            problemReporter = new ProblemReporter(DefaultErrorHandlingPolicies.proceedWithAllProblems(),
                    compilerOptions, new DefaultProblemFactory());
        }
        this.timer = new Timer(false);
        internalCreate();
        if (javaProject != null) {
            modelLoaders.put(javaProject.getProject(), new WeakReference<JDTModelLoader>(this));
        }
    }

    public JDTModuleManager getModuleManager() {
        return (JDTModuleManager) moduleManager;
    }

    private void internalCreate() {
        this.typeFactory = new GlobalTypeFactory();
        this.typeParser = new TypeParser(this);
        this.timer = new Timer(false);
        createLookupEnvironment();
    }

    public void createLookupEnvironment() {
        if (javaProject == null) {
            return;
        }
        try {
            ModelLoaderTypeRequestor requestor = new ModelLoaderTypeRequestor();
            lookupEnvironment = new LookupEnvironment(requestor, compilerOptions, problemReporter,
                    createSearchableEnvironment());
            requestor.initialize(lookupEnvironment);
            lookupEnvironment.mayTolerateMissingType = true;
            missingTypeBinding = new MissingTypeBinding(lookupEnvironment.defaultPackage,
                    new char[][] { "unknown".toCharArray() }, lookupEnvironment);
        } catch (JavaModelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public LookupEnvironment createLookupEnvironmentForGeneratedCode() {
        if (javaProject == null) {
            return null;
        }
        try {
            ModelLoaderTypeRequestor requestor = new ModelLoaderTypeRequestor();
            LookupEnvironment lookupEnvironmentForGeneratedCode = new LookupEnvironment(requestor, compilerOptions,
                    problemReporter,
                    ((JavaProject) javaProject).newSearchableNameEnvironment((WorkingCopyOwner) null));
            requestor.initialize(lookupEnvironmentForGeneratedCode);
            lookupEnvironmentForGeneratedCode.mayTolerateMissingType = true;
            return lookupEnvironmentForGeneratedCode;
        } catch (JavaModelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    // TODO : remove when the bug in the AbstractModelLoader is corrected
    @Override
    public synchronized LazyPackage findOrCreatePackage(Module module, String pkgName) {
        LazyPackage pkg = super.findOrCreatePackage(module, pkgName);

        if (pkg.getModule() != null && pkg.getModule().isJava()) {
            pkg.setShared(true);
        }
        Module currentModule = pkg.getModule();
        if (currentModule.equals(modules.getDefaultModule()) && !currentModule.equals(module)) {
            currentModule.getPackages().remove(pkg);
            pkg.setModule(null);
            if (module != null) {
                module.getPackages().add(pkg);
                pkg.setModule(module);
            }
        }
        return pkg;
    }

    @Override
    public void loadStandardModules() {
        // Now create JDK and Oracle modules (cf. https://github.com/ceylon/ceylon-ide-eclipse/issues/733 )
        loadJDKModules();
        /*
         * We start by loading java.lang because we will need it no matter what.
         */
        Module jdkModule = findOrCreateModule(JAVA_BASE_MODULE_NAME, JDKUtils.jdk.version);
        Module languageModule = getLanguageModule();
        if (getModuleManager().isLoadDependenciesFromModelLoaderFirst() && !isBootstrap) {
            findOrCreatePackage(languageModule, CEYLON_LANGUAGE);
        }

        loadPackage(jdkModule, "java.lang", false);
        loadPackage(languageModule, "com.redhat.ceylon.compiler.java.metadata", false);
        loadPackage(languageModule, "com.redhat.ceylon.compiler.java.language", false);

    }

    private String getToplevelQualifiedName(final String pkgName, String name) {
        if (!Util.isInitialLowerCase(name)) {
            name = Util.quoteIfJavaKeyword(name);
        }

        String className = pkgName.isEmpty() ? name : Util.quoteJavaKeywords(pkgName) + "." + name;
        return className;
    }

    @Override
    public synchronized boolean loadPackage(Module module, String packageName, boolean loadDeclarations) {
        packageName = Util.quoteJavaKeywords(packageName);
        if (loadDeclarations && !loadedPackages.add(cacheKeyByModule(module, packageName))) {
            return true;
        }

        if (module instanceof JDTModule) {
            JDTModule jdtModule = (JDTModule) module;
            List<IPackageFragmentRoot> roots = jdtModule.getPackageFragmentRoots();
            IPackageFragment packageFragment = null;
            for (IPackageFragmentRoot root : roots) {
                // skip packages that are not present
                if (!root.exists() || !javaProject.isOnClasspath(root))
                    continue;
                try {
                    IClasspathEntry entry = root.getRawClasspathEntry();

                    //TODO: is the following really necessary?
                    //Note that getContentKind() returns an undefined
                    //value for a classpath container or variable
                    if (entry.getEntryKind() != IClasspathEntry.CPE_CONTAINER
                            && entry.getEntryKind() != IClasspathEntry.CPE_VARIABLE
                            && entry.getContentKind() == IPackageFragmentRoot.K_SOURCE
                            && !CeylonBuilder.isCeylonSourceEntry(entry)) {
                        continue;
                    }

                    packageFragment = root.getPackageFragment(packageName);
                    if (!packageFragment.exists()) {
                        continue;
                    }
                } catch (JavaModelException e) {
                    if (!e.isDoesNotExist()) {
                        e.printStackTrace();
                    }
                    continue;
                }
                if (!loadDeclarations) {
                    // we found the package
                    return true;
                }

                // we have a few virtual types in java.lang that we need to load but they are not listed from class files
                if (module.getNameAsString().equals(JAVA_BASE_MODULE_NAME) && packageName.equals("java.lang")) {
                    loadJavaBaseArrays();
                }

                IClassFile[] classFiles = new IClassFile[] {};
                org.eclipse.jdt.core.ICompilationUnit[] compilationUnits = new org.eclipse.jdt.core.ICompilationUnit[] {};
                try {
                    classFiles = packageFragment.getClassFiles();
                } catch (JavaModelException e) {
                    e.printStackTrace();
                }
                try {
                    compilationUnits = packageFragment.getCompilationUnits();
                } catch (JavaModelException e) {
                    e.printStackTrace();
                }

                List<IType> typesToLoad = new LinkedList<>();
                for (IClassFile classFile : classFiles) {
                    IType type = classFile.getType();
                    typesToLoad.add(type);
                }

                for (org.eclipse.jdt.core.ICompilationUnit compilationUnit : compilationUnits) {
                    // skip removed CUs
                    if (!compilationUnit.exists())
                        continue;
                    try {
                        for (IType type : compilationUnit.getTypes()) {
                            typesToLoad.add(type);
                        }
                    } catch (JavaModelException e) {
                        e.printStackTrace();
                    }
                }

                for (IType type : typesToLoad) {
                    String typeFullyQualifiedName = type.getFullyQualifiedName();
                    String[] nameParts = typeFullyQualifiedName.split("\\.");
                    String typeQualifiedName = nameParts[nameParts.length - 1];
                    // only top-levels are added in source declarations
                    if (typeQualifiedName.indexOf('$') > 0) {
                        continue;
                    }

                    if (type.exists()
                            && !sourceDeclarations.containsKey(getToplevelQualifiedName(
                                    type.getPackageFragment().getElementName(), typeFullyQualifiedName))
                            && !isTypeHidden(module, typeFullyQualifiedName)) {
                        convertToDeclaration(module, typeFullyQualifiedName, DeclarationType.VALUE);
                    }
                }
            }
        }
        return false;
    }

    synchronized public void refreshNameEnvironment() {
        try {
            lookupEnvironment.nameEnvironment = createSearchableEnvironment();
        } catch (JavaModelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public final class ModelLoaderTypeRequestor implements ITypeRequestor {
        private Parser basicParser;
        private LookupEnvironment lookupEnvironment;

        public void initialize(LookupEnvironment lookupEnvironment) {
            this.lookupEnvironment = lookupEnvironment;
        }

        @Override
        public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding,
                AccessRestriction accessRestriction) {
            // case of SearchableEnvironment of an IJavaProject is used
            ISourceType sourceType = sourceTypes[0];
            while (sourceType.getEnclosingType() != null)
                sourceType = sourceType.getEnclosingType();
            if (sourceType instanceof SourceTypeElementInfo) {
                // get source
                SourceTypeElementInfo elementInfo = (SourceTypeElementInfo) sourceType;
                IType type = elementInfo.getHandle();
                ICompilationUnit sourceUnit = (ICompilationUnit) type.getCompilationUnit();
                accept(sourceUnit, accessRestriction);
            } else {
                CompilationResult result = new CompilationResult(sourceType.getFileName(), 1, 1, 0);
                CompilationUnitDeclaration unit = SourceTypeConverter.buildCompilationUnit(sourceTypes,
                        SourceTypeConverter.FIELD_AND_METHOD // need field and methods
                                | SourceTypeConverter.MEMBER_TYPE, // need member types
                        // no need for field initialization
                        lookupEnvironment.problemReporter, result);
                lookupEnvironment.buildTypeBindings(unit, accessRestriction);
                lookupEnvironment.completeTypeBindings(unit, true);
            }
        }

        @Override
        public void accept(IBinaryType binaryType, PackageBinding packageBinding,
                AccessRestriction accessRestriction) {
            BinaryTypeBinding btb = lookupEnvironment.createBinaryTypeFrom(binaryType, packageBinding,
                    accessRestriction);

            if (btb.isNestedType() && !btb.isStatic()) {
                for (MethodBinding method : btb.methods()) {
                    if (method.isConstructor() && method.parameters.length > 0) {
                        char[] signature = method.signature();
                        for (IBinaryMethod methodInfo : binaryType.getMethods()) {
                            if (methodInfo.isConstructor()) {
                                char[] methodInfoSignature = methodInfo.getMethodDescriptor();
                                if (new String(signature).equals(new String(methodInfoSignature))) {
                                    IBinaryAnnotation[] binaryAnnotation = methodInfo.getParameterAnnotations(0);
                                    if (binaryAnnotation == null) {
                                        if (methodInfo.getAnnotatedParametersCount() == method.parameters.length
                                                + 1) {
                                            AnnotationBinding[][] newParameterAnnotations = new AnnotationBinding[method.parameters.length][];
                                            for (int i = 0; i < method.parameters.length; i++) {
                                                IBinaryAnnotation[] goodAnnotations = null;
                                                try {
                                                    goodAnnotations = methodInfo.getParameterAnnotations(i + 1);
                                                } catch (IndexOutOfBoundsException e) {
                                                    break;
                                                }
                                                if (goodAnnotations != null) {
                                                    AnnotationBinding[] parameterAnnotations = BinaryTypeBinding
                                                            .createAnnotations(goodAnnotations, lookupEnvironment,
                                                                    new char[][][] {});
                                                    newParameterAnnotations[i] = parameterAnnotations;
                                                }
                                            }
                                            method.setParameterAnnotations(newParameterAnnotations);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        @Override
        public void accept(ICompilationUnit sourceUnit, AccessRestriction accessRestriction) {
            // Switch the current policy and compilation result for this unit to the requested one.
            CompilationResult unitResult = new CompilationResult(sourceUnit, 1, 1,
                    compilerOptions.maxProblemsPerUnit);
            try {
                CompilationUnitDeclaration parsedUnit = basicParser().dietParse(sourceUnit, unitResult);
                lookupEnvironment.buildTypeBindings(parsedUnit, accessRestriction);
                lookupEnvironment.completeTypeBindings(parsedUnit, true);
            } catch (AbortCompilationUnit e) {
                // at this point, currentCompilationUnitResult may not be sourceUnit, but some other
                // one requested further along to resolve sourceUnit.
                if (unitResult.compilationUnit == sourceUnit) { // only report once
                    //requestor.acceptResult(unitResult.tagAsAccepted());
                } else {
                    throw e; // want to abort enclosing request to compile
                }
            }
            // Display unit error in debug mode
            if (BasicSearchEngine.VERBOSE) {
                if (unitResult.problemCount > 0) {
                    System.out.println(unitResult);
                }
            }
        }

        private Parser basicParser() {
            if (this.basicParser == null) {
                ProblemReporter problemReporter = new ProblemReporter(
                        DefaultErrorHandlingPolicies.proceedWithAllProblems(), compilerOptions,
                        new DefaultProblemFactory());
                this.basicParser = new Parser(problemReporter, false);
                this.basicParser.reportOnlyOneSyntaxError = true;
            }
            return this.basicParser;
        }
    }

    public static class ModelLoaderNameEnvironment extends SearchableEnvironment {
        public ModelLoaderNameEnvironment(IJavaProject javaProject) throws JavaModelException {
            super((JavaProject) javaProject, (WorkingCopyOwner) null);
        }

        public IJavaProject getJavaProject() {
            return project;
        }

        public IType findTypeInNameLookup(char[][] compoundTypeName) {
            if (compoundTypeName == null)
                return null;

            int length = compoundTypeName.length;
            if (length <= 1) {
                if (length == 0)
                    return null;
                return findTypeInNameLookup(new String(compoundTypeName[0]), IPackageFragment.DEFAULT_PACKAGE_NAME);
            }

            int lengthM1 = length - 1;
            char[][] packageName = new char[lengthM1][];
            System.arraycopy(compoundTypeName, 0, packageName, 0, lengthM1);

            return findTypeInNameLookup(new String(compoundTypeName[lengthM1]),
                    CharOperation.toString(packageName));
        }

        public IType findTypeInNameLookup(String typeName, String packageName) {
            JavaElementRequestor packageRequestor = new JavaElementRequestor();
            nameLookup.seekPackageFragments(packageName, false, packageRequestor);
            LinkedList<IPackageFragment> packagesToSearchIn = new LinkedList<>();

            for (IPackageFragment pf : packageRequestor.getPackageFragments()) {
                IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) pf
                        .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
                try {
                    if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
                        packagesToSearchIn.addFirst(pf);
                        continue;
                    }
                    if (isInCeylonClassesOutputFolder(packageRoot.getPath())) {
                        continue;
                    }
                    packagesToSearchIn.addLast(pf);
                } catch (JavaModelException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            IType type = null;
            for (IPackageFragment pf : packagesToSearchIn) {

                // We use considerSecondTypes = false because we will do it explicitly afterwards, in order to use waitForIndexes=true
                // TODO : when migrating to Luna only (removing Kepler support), we will be able to simply call :
                //        nameLookup.findType(typeName, pf, false, NameLookup.ACCEPT_ALL, 
                //                            true /* waitForIndices */,
                //                            true /* considerSecondaryTypes */)
                // But unfortunately, Kepler doesn't provide the ability to set the 'waitForIndexes' parameter to true.
                type = nameLookup.findType(typeName, pf, false, NameLookup.ACCEPT_ALL);
                if (type == null) {
                    JavaModelManager manager = JavaModelManager.getJavaModelManager();
                    try {
                        // This is a Copy / Paste from :
                        // org.eclipse.jdt.internal.core.NameLookup.findSecondaryType(...), in order to be able to call it with waitForIndexes = true:
                        // type = nameLookup.findSecondaryType(pf.getElementName(), typeName, pf.getJavaProject(), true, null);
                        IJavaProject javaProject = pf.getJavaProject();
                        @SuppressWarnings("rawtypes")
                        Map secondaryTypePaths = manager.secondaryTypes(javaProject, true, null);
                        if (secondaryTypePaths.size() > 0) {
                            @SuppressWarnings("rawtypes")
                            Map types = (Map) secondaryTypePaths.get(packageName == null ? "" : packageName); //$NON-NLS-1$
                            if (types != null && types.size() > 0) {
                                boolean startsWithDollar = false;
                                if (typeName.startsWith("$")) {
                                    startsWithDollar = true;
                                    typeName = typeName.substring(1);
                                }
                                String[] parts = typeName.split("(\\.|\\$)");
                                if (startsWithDollar) {
                                    parts[0] = "$" + parts[0];
                                }
                                int index = 0;
                                String topLevelClassName = parts[index++];
                                IType currentClass = (IType) types.get(topLevelClassName);
                                IType result = currentClass;
                                while (index < parts.length) {
                                    result = null;
                                    String nestedClassName = parts[index++];
                                    if (currentClass != null && currentClass.exists()) {
                                        currentClass = currentClass.getType(nestedClassName);
                                        result = currentClass;
                                    } else {
                                        break;
                                    }
                                }
                                type = result;
                            }
                        }
                    } catch (JavaModelException jme) {
                        // give up
                    }

                }
                if (type != null) {
                    break;
                }
            }
            return type;
        }

        @Override
        protected NameEnvironmentAnswer find(String typeName, String packageName) {
            if (packageName == null)
                packageName = IPackageFragment.DEFAULT_PACKAGE_NAME;
            if (this.owner != null) {
                String source = this.owner.findSource(typeName, packageName);
                if (source != null) {
                    ICompilationUnit cu = new BasicCompilationUnit(source.toCharArray(),
                            CharOperation.splitOn('.', packageName.toCharArray()),
                            typeName + org.eclipse.jdt.internal.core.util.Util.defaultJavaExtension());
                    return new NameEnvironmentAnswer(cu, null);
                }
            }

            IType type = findTypeInNameLookup(typeName, packageName);

            if (type != null) {
                // construct name env answer
                if (type instanceof BinaryType) { // BinaryType
                    try {
                        return new NameEnvironmentAnswer((IBinaryType) ((BinaryType) type).getElementInfo(), null);
                    } catch (JavaModelException npe) {
                        // fall back to using owner
                    }
                } else { //SourceType
                    try {
                        // retrieve the requested type
                        SourceTypeElementInfo sourceType = (SourceTypeElementInfo) ((SourceType) type)
                                .getElementInfo();
                        ISourceType topLevelType = sourceType;
                        while (topLevelType.getEnclosingType() != null) {
                            topLevelType = topLevelType.getEnclosingType();
                        }
                        // find all siblings (other types declared in same unit, since may be used for name resolution)
                        IType[] types = sourceType.getHandle().getCompilationUnit().getTypes();
                        ISourceType[] sourceTypes = new ISourceType[types.length];

                        // in the resulting collection, ensure the requested type is the first one
                        sourceTypes[0] = sourceType;
                        int length = types.length;
                        for (int i = 0, index = 1; i < length; i++) {
                            ISourceType otherType = (ISourceType) ((JavaElement) types[i]).getElementInfo();
                            if (!otherType.equals(topLevelType) && index < length) // check that the index is in bounds (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=62861)
                                sourceTypes[index++] = otherType;
                        }
                        return new NameEnvironmentAnswer(sourceTypes, null);
                    } catch (JavaModelException jme) {
                        if (jme.isDoesNotExist()
                                && String.valueOf(TypeConstants.PACKAGE_INFO_NAME).equals(typeName)) {
                            // in case of package-info.java the type doesn't exist in the model,
                            // but the CU may still help in order to fetch package level annotations.
                            return new NameEnvironmentAnswer((ICompilationUnit) type.getParent(), null);
                        }
                        // no usable answer
                    }
                }
            }
            return null;
        }
    }

    private INameEnvironment createSearchableEnvironment() throws JavaModelException {
        return new ModelLoaderNameEnvironment(javaProject);
    }

    synchronized private LookupEnvironment getLookupEnvironment() {
        if (mustResetLookupEnvironment) {
            synchronized (lookupEnvironment) {
                createLookupEnvironment();
            }
            mustResetLookupEnvironment = false;
        }
        return lookupEnvironment;
    }

    @Override
    public boolean searchAgain(Module module, String name) {
        if (module instanceof JDTModule) {
            JDTModule jdtModule = (JDTModule) module;
            if (jdtModule.isCeylonBinaryArchive() || jdtModule.isJavaBinaryArchive()) {
                String classRelativePath = name.replace('.', '/');
                return jdtModule.containsClass(classRelativePath + ".class")
                        || jdtModule.containsClass(classRelativePath + "_.class");
            } else if (jdtModule.isProjectModule()) {
                int nameLength = name.length();
                int packageEnd = name.lastIndexOf('.');
                int classNameStart = packageEnd + 1;
                String packageName = packageEnd > 0 ? name.substring(0, packageEnd) : "";
                String className = classNameStart < nameLength ? name.substring(classNameStart) : "";
                boolean moduleContainsJava = false;
                for (IPackageFragmentRoot root : jdtModule.getPackageFragmentRoots()) {
                    try {
                        IPackageFragment pf = root.getPackageFragment(packageName);
                        if (pf.exists() && javaProject.isOnClasspath(pf)) {
                            if (((IPackageFragment) pf).containsJavaResources()) {
                                moduleContainsJava = true;
                                break;
                            }
                        }
                    } catch (JavaModelException e) {
                        e.printStackTrace();
                        moduleContainsJava = true; // Just in case ...
                    }
                }
                if (moduleContainsJava) {
                    ModelLoaderNameEnvironment nameEnvironment = getNameEnvironment();
                    if (nameEnvironment.findTypeInNameLookup(className, packageName) != null
                            || nameEnvironment.findTypeInNameLookup(className + "_", packageName) != null) {
                        return true;
                    }
                }
                return false;
            }
        }
        return false;
    }

    @Override
    public boolean searchAgain(LazyPackage lazyPackage, String name) {
        return searchAgain(lazyPackage.getModule(),
                lazyPackage.getQualifiedName(lazyPackage.getQualifiedNameString(), name));
    }

    @Override
    public synchronized ClassMirror lookupNewClassMirror(Module module, String name) {
        if (sourceDeclarations.containsKey(name)) {
            return new SourceClass(sourceDeclarations.get(name));
        }

        ClassMirror mirror = buildClassMirror(name);
        if (mirror == null && lastPartHasLowerInitial(name)) {
            // We have to try the unmunged name first, so that we find the symbol
            // from the source in preference to the symbol from any 
            // pre-existing .class file
            mirror = buildClassMirror(name + "_");
        }
        return mirror;
    }

    public synchronized MissingTypeBinding getMissingTypeBinding() {
        return missingTypeBinding;
    }

    public static interface ActionOnResolvedType {
        void doWithBinding(ReferenceBinding referenceBinding);
    }

    private static WeakHashMap<IProject, WeakReference<JDTModelLoader>> modelLoaders = new WeakHashMap<>();

    public static JDTModelLoader getModelLoader(IProject project) {
        WeakReference<JDTModelLoader> modelLoaderRef = modelLoaders.get(project);
        if (modelLoaderRef != null) {
            return modelLoaderRef.get();
        }
        return null;
    }

    public static JDTModelLoader getModelLoader(IJavaProject javaProject) {
        return getModelLoader(javaProject.getProject());
    }

    public static JDTModelLoader getModelLoader(IType type) {
        return type == null ? null : getModelLoader(type.getJavaProject());
    }

    public static interface ActionOnMethodBinding {
        void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClassBinding,
                MethodBinding methodBinding);
    }

    public static interface ActionOnClassBinding {
        void doWithBinding(IType classModel, ReferenceBinding classBinding);
    }

    public static boolean doWithReferenceBinding(final IType typeModel, final ReferenceBinding binding,
            final ActionOnClassBinding action) {
        if (typeModel == null) {
            throw new ModelResolutionException("Resolving action requested on a missing declaration");
        }

        if (binding == null) {
            return false;
        }

        PackageBinding packageBinding = binding.getPackage();
        if (packageBinding == null) {
            return false;
        }
        LookupEnvironment lookupEnvironment = packageBinding.environment;
        if (lookupEnvironment == null) {
            return false;
        }
        JDTModelLoader modelLoader = getModelLoader(typeModel);
        if (modelLoader == null) {
            throw new ModelResolutionException(
                    "The Model Loader corresponding the type '" + typeModel.getFullyQualifiedName() + "'");
        }

        synchronized (modelLoader.lookupEnvironmentMutex) {
            if (modelLoader.lookupEnvironment != lookupEnvironment) {
                return false;
            }
            action.doWithBinding(typeModel, binding);
            return true;
        }
    }

    public static boolean doWithMethodBinding(final IType declaringClassModel, final MethodBinding binding,
            final ActionOnMethodBinding action) {
        if (declaringClassModel == null) {
            throw new ModelResolutionException("Resolving action requested on a missing declaration");
        }

        if (binding == null) {
            return false;
        }
        ReferenceBinding declaringClassBinding = binding.declaringClass;
        if (declaringClassBinding == null) {
            return false;
        }
        PackageBinding packageBinding = declaringClassBinding.getPackage();
        if (packageBinding == null) {
            return false;
        }
        LookupEnvironment lookupEnvironment = packageBinding.environment;
        if (lookupEnvironment == null) {
            return false;
        }

        JDTModelLoader modelLoader = getModelLoader(declaringClassModel);
        if (modelLoader == null) {
            throw new ModelResolutionException("The Model Loader corresponding the type '"
                    + declaringClassModel.getFullyQualifiedName() + "' doesn't exist");
        }

        synchronized (modelLoader.lookupEnvironmentMutex) {
            if (modelLoader.lookupEnvironment != lookupEnvironment) {
                return false;
            }
            action.doWithBinding(declaringClassModel, declaringClassBinding, binding);
            return true;
        }
    }

    public static interface ActionOnResolvedGeneratedType {
        void doWithBinding(IType classModel, ReferenceBinding classBinding, IBinaryType binaryType);
    }

    public static void doOnResolvedGeneratedType(IType typeModel, ActionOnResolvedGeneratedType action) {
        if (typeModel == null || !typeModel.exists()) {
            throw new ModelResolutionException("Resolving action requested on a missing declaration");
        }

        JDTModelLoader modelLoader = getModelLoader(typeModel);
        if (modelLoader == null) {
            throw new ModelResolutionException("The Model Loader is not available to resolve type '"
                    + typeModel.getFullyQualifiedName() + "'");
        }
        char[][] compoundName = CharOperation.splitOn('.', typeModel.getFullyQualifiedName().toCharArray());
        LookupEnvironment lookupEnvironment = modelLoader.createLookupEnvironmentForGeneratedCode();
        ReferenceBinding binding = null;
        IBinaryType binaryType = null;
        try {
            ITypeRoot typeRoot = typeModel.getTypeRoot();

            if (typeRoot instanceof IClassFile) {
                ClassFile classFile = (ClassFile) typeRoot;

                IFile classFileRsrc = (IFile) classFile.getCorrespondingResource();
                if (classFileRsrc != null && !classFileRsrc.exists()) {
                    //the .class file has been deleted
                    return;
                }

                BinaryTypeBinding binaryTypeBinding = null;
                try {
                    binaryType = classFile.getBinaryTypeInfo(classFileRsrc, true);
                    binaryTypeBinding = lookupEnvironment.cacheBinaryType(binaryType, null);
                } catch (JavaModelException e) {
                    if (!e.isDoesNotExist()) {
                        throw e;
                    }
                }

                if (binaryTypeBinding == null) {
                    ReferenceBinding existingType = lookupEnvironment.getCachedType(compoundName);
                    if (existingType == null || !(existingType instanceof BinaryTypeBinding)) {
                        return;
                    }
                    binaryTypeBinding = (BinaryTypeBinding) existingType;
                }
                binding = binaryTypeBinding;
            }
        } catch (JavaModelException e) {
            throw new ModelResolutionException(e);
        }
        if (binaryType != null && binding != null) {
            action.doWithBinding(typeModel, binding, binaryType);
        }
    }

    public static void doWithResolvedType(IType typeModel, ActionOnResolvedType action) {
        if (typeModel == null || !typeModel.exists()) {
            throw new ModelResolutionException("Resolving action requested on a missing declaration");
        }

        JDTModelLoader modelLoader = getModelLoader(typeModel);
        if (modelLoader == null) {
            throw new ModelResolutionException("The Model Loader is not available to resolve type '"
                    + typeModel.getFullyQualifiedName() + "'");
        }
        char[][] compoundName = CharOperation.splitOn('.', typeModel.getFullyQualifiedName().toCharArray());
        LookupEnvironment lookupEnvironment = modelLoader.getLookupEnvironment();
        synchronized (modelLoader.lookupEnvironmentMutex) {
            ReferenceBinding binding;
            try {
                binding = toBinding(typeModel, lookupEnvironment, compoundName);
            } catch (JavaModelException e) {
                throw new ModelResolutionException(e);
            }
            if (binding == null) {
                throw new ModelResolutionException(
                        "Binding not found for type : '" + typeModel.getFullyQualifiedName() + "'");
            }
            action.doWithBinding(binding);
        }
    }

    public static IType toType(ReferenceBinding binding) {
        ModelLoaderNameEnvironment nameEnvironment = (ModelLoaderNameEnvironment) binding
                .getPackage().environment.nameEnvironment;
        char[][] compoundName = ((ReferenceBinding) binding).compoundName;
        IType typeModel = nameEnvironment.findTypeInNameLookup(compoundName);

        if (typeModel == null && !(binding instanceof MissingTypeBinding)) {
            throw new ModelResolutionException("JDT reference binding without a JDT IType element !");
        }
        return typeModel;
    }

    private JDTClass buildClassMirror(String name) {
        if (javaProject == null) {
            return null;
        }

        try {
            LookupEnvironment theLookupEnvironment = getLookupEnvironment();
            char[][] uncertainCompoundName = CharOperation.splitOn('.', name.toCharArray());
            int numberOfParts = uncertainCompoundName.length;
            char[][] compoundName = null;
            IType type = null;

            for (int i = numberOfParts - 1; i > 0; i--) {
                char[][] triedPackageName = new char[0][];
                for (int j = 0; j < i; j++) {
                    triedPackageName = CharOperation.arrayConcat(triedPackageName, uncertainCompoundName[j]);
                }
                char[] triedClassName = new char[0];
                for (int k = i; k < numberOfParts; k++) {
                    triedClassName = CharOperation.concat(triedClassName, uncertainCompoundName[k], '$');
                }

                ModelLoaderNameEnvironment nameEnvironment = getNameEnvironment();
                type = nameEnvironment.findTypeInNameLookup(CharOperation.charToString(triedClassName),
                        CharOperation.toString(triedPackageName));
                if (type != null) {
                    compoundName = CharOperation.arrayConcat(triedPackageName, triedClassName);
                    break;
                }
            }

            if (type == null) {
                return null;
            }

            ReferenceBinding binding = toBinding(type, theLookupEnvironment, compoundName);
            if (binding != null) {
                return new JDTClass(binding, type);
            }

        } catch (JavaModelException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static ReferenceBinding toBinding(IType type, LookupEnvironment theLookupEnvironment,
            char[][] compoundName) throws JavaModelException {
        ITypeRoot typeRoot = type.getTypeRoot();

        if (typeRoot instanceof IClassFile) {
            ClassFile classFile = (ClassFile) typeRoot;

            IFile classFileRsrc = (IFile) classFile.getCorrespondingResource();
            if (classFileRsrc != null && !classFileRsrc.exists()) {
                //the .class file has been deleted
                return null;
            }

            BinaryTypeBinding binaryTypeBinding = null;
            try {
                IBinaryType binaryType = classFile.getBinaryTypeInfo(classFileRsrc, true);
                binaryTypeBinding = theLookupEnvironment.cacheBinaryType(binaryType, null);
            } catch (JavaModelException e) {
                if (!e.isDoesNotExist()) {
                    throw e;
                }
            }

            if (binaryTypeBinding == null) {
                ReferenceBinding existingType = theLookupEnvironment.getCachedType(compoundName);
                if (existingType == null || !(existingType instanceof BinaryTypeBinding)) {
                    return null;
                }
                binaryTypeBinding = (BinaryTypeBinding) existingType;
            }
            return binaryTypeBinding;
        } else {
            ReferenceBinding referenceBinding = theLookupEnvironment.getType(compoundName);
            if (referenceBinding != null && !(referenceBinding instanceof BinaryTypeBinding)) {

                if (referenceBinding instanceof ProblemReferenceBinding) {
                    ProblemReferenceBinding problemReferenceBinding = (ProblemReferenceBinding) referenceBinding;
                    if (problemReferenceBinding.problemId() == ProblemReasons.InternalNameProvided) {
                        referenceBinding = problemReferenceBinding.closestReferenceMatch();
                    } else {
                        System.out.println(
                                ProblemReferenceBinding.problemReasonString(problemReferenceBinding.problemId()));
                        return null;
                    }
                }
                return referenceBinding;
            }
            return null;
        }
    }

    private ModelLoaderNameEnvironment getNameEnvironment() {
        ModelLoaderNameEnvironment searchableEnvironment = (ModelLoaderNameEnvironment) getLookupEnvironment().nameEnvironment;
        return searchableEnvironment;
    }

    @Override
    public synchronized Declaration convertToDeclaration(Module module, String typeName,
            DeclarationType declarationType) {
        if (sourceDeclarations.containsKey(typeName)) {
            return sourceDeclarations.get(typeName).getModelDeclaration();
        }
        try {
            return super.convertToDeclaration(module, typeName, declarationType);
        } catch (RuntimeException e) {
            // FIXME: pretty sure this is plain wrong as it ignores problems and especially ModelResolutionException and just plain hides them
            return null;
        }
    }

    @Override
    public void addModuleToClassPath(Module module, ArtifactResult artifact) {
        if (artifact != null && module instanceof LazyModule)
            ((LazyModule) module).loadPackageList(artifact);

        if (module instanceof JDTModule) {
            JDTModule jdtModule = (JDTModule) module;
            if (!jdtModule.equals(getLanguageModule())
                    && (jdtModule.isCeylonBinaryArchive() || jdtModule.isJavaBinaryArchive())) {
                CeylonProjectModulesContainer container = CeylonClasspathUtil
                        .getCeylonProjectModulesClasspathContainer(javaProject);

                if (container != null) {
                    IPath modulePath = new Path(artifact.artifact().getPath());
                    IClasspathEntry newEntry = container.addNewClasspathEntryIfNecessary(modulePath);
                    if (newEntry != null) {
                        try {
                            JavaCore.setClasspathContainer(container.getPath(), new IJavaProject[] { javaProject },
                                    new IClasspathContainer[] { new CeylonProjectModulesContainer(container) },
                                    null);
                        } catch (JavaModelException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        refreshNameEnvironment();
                    }
                }
            }
        }
        modulesInClassPath.add(module);
    }

    @Override
    protected boolean isOverridingMethod(MethodMirror methodSymbol) {
        return ((JDTMethod) methodSymbol).isOverridingMethod();
    }

    @Override
    protected boolean isOverloadingMethod(MethodMirror methodSymbol) {
        return ((JDTMethod) methodSymbol).isOverloadingMethod();
    }

    @Override
    protected Unit getCompiledUnit(LazyPackage pkg, ClassMirror classMirror) {
        Unit unit = null;
        if (classMirror != null && classMirror instanceof JDTClass) {
            JDTClass jdtClass = (JDTClass) classMirror;
            String unitName = jdtClass.getFileName();

            if (!jdtClass.isBinary()) {
                // This search is for source Java classes since several classes might have the same file name 
                //  and live inside the same Java source file => into the same Unit
                for (Unit unitToTest : pkg.getUnits()) {
                    if (unitToTest.getFilename().equals(unitName)) {
                        return unitToTest;
                    }
                }
            }

            unit = newCompiledUnit(pkg, jdtClass);
        }

        if (unit == null) {
            unit = unitsByPackage.get(pkg);
            if (unit == null) {
                unit = new PackageTypeFactory(pkg);
                unit.setPackage(pkg);
                unitsByPackage.put(pkg, unit);
            }
        }
        return unit;
    }

    public void setModuleAndPackageUnits() {
        Context context = getModuleManager().getContext();
        for (Module module : context.getModules().getListOfModules()) {
            if (module instanceof JDTModule) {
                JDTModule jdtModule = (JDTModule) module;
                if (jdtModule.isCeylonBinaryArchive()) {
                    for (Package p : jdtModule.getPackages()) {
                        if (p.getUnit() == null) {
                            ClassMirror packageClassMirror = lookupClassMirror(jdtModule,
                                    p.getQualifiedNameString() + "." + Naming.PACKAGE_DESCRIPTOR_CLASS_NAME);
                            if (packageClassMirror == null) {
                                packageClassMirror = lookupClassMirror(jdtModule, p.getQualifiedNameString() + "."
                                        + Naming.PACKAGE_DESCRIPTOR_CLASS_NAME.substring(1));
                            }
                            // some modules do not declare their main package, because they don't have any declaration to share
                            // there, for example, so this can be null
                            if (packageClassMirror != null)
                                p.setUnit(newCompiledUnit((LazyPackage) p, packageClassMirror));
                        }
                        if (p.getNameAsString().equals(jdtModule.getNameAsString())) {
                            if (jdtModule.getUnit() == null) {
                                ClassMirror moduleClassMirror = lookupClassMirror(jdtModule,
                                        p.getQualifiedNameString() + "." + Naming.MODULE_DESCRIPTOR_CLASS_NAME);
                                if (moduleClassMirror == null) {
                                    moduleClassMirror = lookupClassMirror(jdtModule, p.getQualifiedNameString()
                                            + "." + Naming.OLD_MODULE_DESCRIPTOR_CLASS_NAME);
                                }
                                if (moduleClassMirror != null) {
                                    jdtModule.setUnit(newCompiledUnit((LazyPackage) p, moduleClassMirror));
                                }
                            }
                        }
                    }
                }
            }
        }

    }

    private Unit newCompiledUnit(LazyPackage pkg, ClassMirror classMirror) {
        Unit unit;
        JDTClass jdtClass = (JDTClass) classMirror;
        IType type = jdtClass.getType();
        if (type == null) {
            return null;
        }

        ITypeRoot typeRoot = type.getTypeRoot();
        StringBuilder sb = new StringBuilder();
        List<String> parts = pkg.getName();
        for (int i = 0; i < parts.size(); i++) {
            String part = parts.get(i);
            if (!part.isEmpty()) {
                sb.append(part);
                sb.append('/');
            }
        }
        sb.append(jdtClass.getFileName());
        String relativePath = sb.toString();
        String fileName = jdtClass.getFileName();
        String fullPath = jdtClass.getFullPath();

        if (!jdtClass.isBinary()) {
            unit = new JavaCompilationUnit((org.eclipse.jdt.core.ICompilationUnit) typeRoot, fileName, relativePath,
                    fullPath, pkg);
        } else {
            if (jdtClass.isCeylon()) {
                if (pkg.getModule() instanceof JDTModule) {
                    JDTModule module = (JDTModule) pkg.getModule();
                    IProject originalProject = module.getOriginalProject();
                    if (originalProject != null) {
                        unit = new CrossProjectBinaryUnit((IClassFile) typeRoot, fileName, relativePath, fullPath,
                                pkg);
                    } else {
                        unit = new CeylonBinaryUnit((IClassFile) typeRoot, fileName, relativePath, fullPath, pkg);
                    }
                } else {
                    unit = new CeylonBinaryUnit((IClassFile) typeRoot, fileName, relativePath, fullPath, pkg);
                }
            } else {
                unit = new JavaClassFile((IClassFile) typeRoot, fileName, relativePath, fullPath, pkg);
            }
        }

        return unit;
    }

    @Override
    protected void logError(String message) {
        //System.err.println("ERROR: "+message);
    }

    @Override
    protected void logWarning(String message) {
        //System.err.println("WARNING: "+message);
    }

    @Override
    protected void logVerbose(String message) {
        //System.err.println("NOTE: "+message);
    }

    @Override
    public synchronized void removeDeclarations(List<Declaration> declarations) {
        List<Declaration> allDeclarations = new ArrayList<Declaration>(declarations.size());
        Set<Package> changedPackages = new HashSet<Package>();

        allDeclarations.addAll(declarations);

        for (Declaration declaration : declarations) {
            Unit unit = declaration.getUnit();
            if (unit != null) {
                changedPackages.add(unit.getPackage());
            }
            retrieveInnerDeclarations(declaration, allDeclarations);
        }

        for (Declaration decl : allDeclarations) {
            String fqn = getToplevelQualifiedName(decl.getContainer().getQualifiedNameString(), decl.getName());
            sourceDeclarations.remove(fqn);
        }

        super.removeDeclarations(allDeclarations);
        for (Package changedPackage : changedPackages) {
            loadedPackages.remove(cacheKeyByModule(changedPackage.getModule(), changedPackage.getNameAsString()));
        }
        mustResetLookupEnvironment = true;
    }

    private void retrieveInnerDeclarations(Declaration declaration, List<Declaration> allDeclarations) {
        List<Declaration> members;
        try {
            members = declaration.getMembers();
        } catch (Exception e) {
            members = Collections.emptyList();
        }
        allDeclarations.addAll(members);
        for (Declaration member : members) {
            retrieveInnerDeclarations(member, allDeclarations);
        }
    }

    private final Map<String, SourceDeclarationHolder> sourceDeclarations = new TreeMap<String, SourceDeclarationHolder>();

    public synchronized Set<String> getSourceDeclarations() {
        Set<String> declarations = new HashSet<String>();
        declarations.addAll(sourceDeclarations.keySet());
        return declarations;
    }

    public synchronized SourceDeclarationHolder getSourceDeclaration(String declarationName) {
        return sourceDeclarations.get(declarationName);
    }

    public class PackageTypeFactory extends TypeFactory {
        public PackageTypeFactory(Package pkg) {
            super(moduleManager.getContext());
            assert (pkg != null);
            setPackage(pkg);
        }
    }

    public class GlobalTypeFactory extends TypeFactory {
        public GlobalTypeFactory() {
            super(moduleManager.getContext());
        }

        @Override
        public Package getPackage() {
            synchronized (JDTModelLoader.this) {
                if (super.getPackage() == null) {
                    super.setPackage(modules.getLanguageModule().getDirectPackage(Module.LANGUAGE_MODULE_NAME));
                }
                return super.getPackage();
            }
        }
    }

    public static interface SourceFileObjectManager {
        void setupSourceFileObjects(List<?> treeHolders);
    }

    public synchronized void setupSourceFileObjects(List<?> treeHolders) {
        addSourcePhasedUnits(treeHolders, true);
    }

    public synchronized void addSourcePhasedUnits(List<?> treeHolders, final boolean isSourceToCompile) {
        for (Object treeHolder : treeHolders) {
            if (treeHolder instanceof PhasedUnit) {
                final PhasedUnit unit = (PhasedUnit) treeHolder;
                final String pkgName = unit.getPackage().getQualifiedNameString();
                unit.getCompilationUnit().visit(new SourceDeclarationVisitor() {
                    @Override
                    public void loadFromSource(Tree.Declaration decl) {
                        if (decl.getIdentifier() != null) {
                            String fqn = getToplevelQualifiedName(pkgName, decl.getIdentifier().getText());
                            if (!sourceDeclarations.containsKey(fqn)) {
                                sourceDeclarations.put(fqn,
                                        new SourceDeclarationHolder(unit, decl, isSourceToCompile));
                            }
                        }
                    }

                    @Override
                    public void loadFromSource(ModuleDescriptor that) {
                    }

                    @Override
                    public void loadFromSource(PackageDescriptor that) {
                    }
                });
            }
        }
    }

    public void addSourceArchivePhasedUnits(List<PhasedUnit> sourceArchivePhasedUnits) {
        addSourcePhasedUnits(sourceArchivePhasedUnits, false);
    }

    public synchronized void clearCachesOnPackage(String packageName) {
        List<String> keysToRemove = new ArrayList<String>(classMirrorCache.size());
        for (Entry<String, ClassMirror> element : classMirrorCache.entrySet()) {
            if (element.getValue() == null) {
                String className = element.getKey();
                if (className != null) {
                    String classPackageName = className.replaceAll("\\.[^\\.]+$", "");
                    if (classPackageName.equals(packageName)) {
                        keysToRemove.add(className);
                    }
                }
            }
        }
        for (String keyToRemove : keysToRemove) {
            classMirrorCache.remove(keyToRemove);
        }
        Package pkg = findPackage(packageName);
        loadedPackages.remove(cacheKeyByModule(pkg.getModule(), packageName));
        mustResetLookupEnvironment = true;
    }

    public synchronized void clearClassMirrorCacheForClass(JDTModule module, String classNameToRemove) {
        classMirrorCache.remove(cacheKeyByModule(module, classNameToRemove));
        mustResetLookupEnvironment = true;
    }

    @Override
    protected LazyValue makeToplevelAttribute(ClassMirror classMirror) {
        if (classMirror instanceof SourceClass) {
            return (LazyValue) (((SourceClass) classMirror).getModelDeclaration());
        }
        return super.makeToplevelAttribute(classMirror);
    }

    @Override
    protected LazyMethod makeToplevelMethod(ClassMirror classMirror) {
        if (classMirror instanceof SourceClass) {
            return (LazyMethod) (((SourceClass) classMirror).getModelDeclaration());
        }
        return super.makeToplevelMethod(classMirror);
    }

    @Override
    protected LazyClass makeLazyClass(ClassMirror classMirror, Class superClass, MethodMirror constructor) {
        if (classMirror instanceof SourceClass) {
            return (LazyClass) (((SourceClass) classMirror).getModelDeclaration());
        }
        return super.makeLazyClass(classMirror, superClass, constructor);
    }

    @Override
    protected LazyInterface makeLazyInterface(ClassMirror classMirror) {
        if (classMirror instanceof SourceClass) {
            return (LazyInterface) ((SourceClass) classMirror).getModelDeclaration();
        }
        return super.makeLazyInterface(classMirror);
    }

    public TypeFactory getTypeFactory() {
        return (TypeFactory) typeFactory;
    }

    public synchronized Package findPackage(String quotedPkgName) {
        String pkgName = quotedPkgName.replace("$", "");
        // in theory we only have one package with the same name per module in eclipse
        for (Package pkg : packagesByName.values()) {
            if (pkg.getNameAsString().equals(pkgName))
                return pkg;
        }
        return null;
    }

    @Override
    protected Module findModuleForClassMirror(ClassMirror classMirror) {
        String pkgName = getPackageNameForQualifiedClassName(classMirror);
        return lookupModuleByPackageName(pkgName);
    }

    public void loadJDKModules() {
        for (String jdkModule : JDKUtils.getJDKModuleNames())
            findOrCreateModule(jdkModule, JDKUtils.jdk.version);
        for (String jdkOracleModule : JDKUtils.getOracleJDKModuleNames())
            findOrCreateModule(jdkOracleModule, JDKUtils.jdk.version);
    }

    @Override
    public synchronized LazyPackage findOrCreateModulelessPackage(String pkgName) {
        return (LazyPackage) findPackage(pkgName);
    }

    @Override
    public boolean isModuleInClassPath(Module module) {
        return modulesInClassPath.contains(module)
                || ((module instanceof JDTModule) && ((JDTModule) module).isProjectModule());
    }

    @Override
    protected boolean needsLocalDeclarations() {
        return false;
    }

    void addJDKModuleToClassPath(Module module) {
        modulesInClassPath.add(module);
    }
}