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

Java tutorial

Introduction

Here is the source code for com.redhat.ceylon.eclipse.core.model.JDTModule.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 java.io.File;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
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.IParent;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.PackageFragment;

import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ArtifactResult;
import com.redhat.ceylon.cmr.api.ArtifactResultType;
import com.redhat.ceylon.cmr.api.ImportType;
import com.redhat.ceylon.cmr.api.JDKUtils;
import com.redhat.ceylon.cmr.api.PathFilter;
import com.redhat.ceylon.cmr.api.Repository;
import com.redhat.ceylon.cmr.api.RepositoryException;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.api.VisibilityType;
import com.redhat.ceylon.cmr.impl.JDKRepository;
import com.redhat.ceylon.compiler.java.tools.NewlineFixingStringStream;
import com.redhat.ceylon.compiler.loader.model.LazyModule;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnitMap;
import com.redhat.ceylon.compiler.typechecker.io.ClosableVirtualFile;
import com.redhat.ceylon.compiler.typechecker.io.VirtualFile;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.ModuleImport;
import com.redhat.ceylon.compiler.typechecker.model.Modules;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.Unit;
import com.redhat.ceylon.compiler.typechecker.model.Util;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonParser;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.classpath.CeylonLanguageModuleContainer;
import com.redhat.ceylon.eclipse.core.classpath.CeylonProjectModulesContainer;
import com.redhat.ceylon.eclipse.core.model.JDTModuleManager.ExternalModulePhasedUnits;
import com.redhat.ceylon.eclipse.core.model.ModuleDependencies.TraversalAction;
import com.redhat.ceylon.eclipse.core.typechecker.CrossProjectPhasedUnit;
import com.redhat.ceylon.eclipse.core.typechecker.ExternalPhasedUnit;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.util.CarUtils;
import com.redhat.ceylon.eclipse.util.SingleSourceUnitPackage;

public class JDTModule extends LazyModule {
    private JDTModuleManager moduleManager;
    private List<IPackageFragmentRoot> packageFragmentRoots;
    private File artifact;
    private String repositoryDisplayString = "";
    private WeakReference<ExternalModulePhasedUnits> sourceModulePhasedUnits; // Here we have a weak ref on the PhasedUnits because there are already stored as strong references in the TypeChecker phasedUnitsOfDependencies list. 
    // But in the future we might remove the strong reference in the typeChecker and use strong references here, which would be 
    // much more modular (think of several versions of a module imported in a non-shared way in the same projet).
    private PhasedUnitMap<ExternalPhasedUnit, SoftReference<ExternalPhasedUnit>> binaryModulePhasedUnits;
    private Properties classesToSources = new Properties();
    private Map<String, String> javaImplFilesToCeylonDeclFiles = new HashMap<String, String>();
    private String sourceArchivePath = null;
    private IProject originalProject = null;
    private JDTModule originalModule = null;
    private Set<String> originalUnitsToRemove = new LinkedHashSet<>();
    private Set<String> originalUnitsToAdd = new LinkedHashSet<>();
    private ArtifactResultType artifactType = ArtifactResultType.OTHER;
    private Exception resolutionException = null;

    public JDTModule(JDTModuleManager jdtModuleManager, List<IPackageFragmentRoot> packageFragmentRoots) {
        this.moduleManager = jdtModuleManager;
        this.packageFragmentRoots = packageFragmentRoots;
    }

    private File returnCarFile() {
        if (isCeylonBinaryArchive()) {
            return artifact;
        }
        if (isSourceArchive()) {
            return new File(
                    sourceArchivePath.substring(0, sourceArchivePath.length() - ArtifactContext.SRC.length())
                            + ArtifactContext.CAR);
        }
        return null;
    }

    synchronized void setArtifact(ArtifactResult artifactResult) {
        artifact = artifactResult.artifact();
        repositoryDisplayString = artifactResult.repositoryDisplayString();

        if (artifact.getName().endsWith(ArtifactContext.SRC)) {
            moduleType = ModuleType.CEYLON_SOURCE_ARCHIVE;
        } else if (artifact.getName().endsWith(ArtifactContext.CAR)) {
            moduleType = ModuleType.CEYLON_BINARY_ARCHIVE;
        } else if (artifact.getName().endsWith(ArtifactContext.JAR)) {
            moduleType = ModuleType.JAVA_BINARY_ARCHIVE;
        }

        artifactType = artifactResult.type();
        if (isCeylonBinaryArchive()) {
            String carPath = artifact.getPath();
            sourceArchivePath = carPath.substring(0, carPath.length() - ArtifactContext.CAR.length())
                    + ArtifactContext.SRC;
            try {
                classesToSources = CarUtils.retrieveMappingFile(returnCarFile());
                javaImplFilesToCeylonDeclFiles = CarUtils.searchCeylonFilesForJavaImplementations(classesToSources,
                        new File(sourceArchivePath));
            } catch (Exception e) {
                CeylonPlugin.getInstance().getLog().log(new Status(IStatus.WARNING, CeylonPlugin.PLUGIN_ID,
                        "Cannot find the source archive for the Ceylon binary module " + getSignature(), e));
            }
            class BinaryPhasedUnits extends PhasedUnitMap<ExternalPhasedUnit, SoftReference<ExternalPhasedUnit>> {
                Set<String> sourceCannotBeResolved = new HashSet<String>();

                public BinaryPhasedUnits() {
                    String fullPathPrefix = sourceArchivePath + "!/";
                    for (Object value : classesToSources.values()) {
                        String sourceRelativePath = (String) value;
                        String path = fullPathPrefix + sourceRelativePath;
                        phasedUnitPerPath.put(path, new SoftReference<ExternalPhasedUnit>(null));
                        relativePathToPath.put(sourceRelativePath, path);
                    }
                }

                @Override
                public ExternalPhasedUnit getPhasedUnit(String path) {
                    if (!phasedUnitPerPath.containsKey(path)) {
                        if (path.endsWith(".ceylon")) {
                            // Case of a Ceylon file with a Java implementation, the classesToSources key is the Java source file.
                            String javaFileRelativePath = getJavaImplementationFile(
                                    path.replace(sourceArchivePath + "!/", ""));
                            if (javaFileRelativePath != null) {
                                return super.getPhasedUnit(sourceArchivePath + "!/" + javaFileRelativePath);
                            }
                        }
                        return null;
                    }
                    return super.getPhasedUnit(path);
                }

                @Override
                public ExternalPhasedUnit getPhasedUnitFromRelativePath(String relativePath) {
                    if (relativePath.startsWith("/")) {
                        relativePath = relativePath.substring(1);
                    }
                    if (!relativePathToPath.containsKey(relativePath)) {
                        if (relativePath.endsWith(".ceylon")) {
                            // Case of a Ceylon file with a Java implementation, the classesToSources key is the Java source file.
                            String javaFileRelativePath = getJavaImplementationFile(relativePath);
                            if (javaFileRelativePath != null) {
                                return super.getPhasedUnitFromRelativePath(javaFileRelativePath);
                            }
                        }
                        return null;
                    }
                    return super.getPhasedUnitFromRelativePath(relativePath);
                }

                @Override
                protected ExternalPhasedUnit fromStoredType(SoftReference<ExternalPhasedUnit> storedValue,
                        String path) {
                    ExternalPhasedUnit result = storedValue.get();
                    if (result == null) {
                        if (!sourceCannotBeResolved.contains(path)) {
                            result = buildPhasedUnitForBinaryUnit(path);
                            if (result != null) {
                                phasedUnitPerPath.put(path, toStoredType(result));
                            } else {
                                sourceCannotBeResolved.add(path);
                            }
                        }
                    }
                    return result;
                }

                @Override
                protected void addInReturnedList(List<ExternalPhasedUnit> list, ExternalPhasedUnit phasedUnit) {
                    if (phasedUnit != null) {
                        list.add(phasedUnit);
                    }
                }

                @Override
                protected SoftReference<ExternalPhasedUnit> toStoredType(ExternalPhasedUnit phasedUnit) {
                    return new SoftReference<ExternalPhasedUnit>(phasedUnit);
                }

                @Override
                public void removePhasedUnitForRelativePath(String relativePath) {
                    // Don't clean the Package since we are in the binary case 
                    String path = relativePathToPath.get(relativePath);
                    relativePathToPath.remove(relativePath);
                    phasedUnitPerPath.remove(path);
                }

            }
            ;
            binaryModulePhasedUnits = new BinaryPhasedUnits();
        }

        if (isSourceArchive()) {
            sourceArchivePath = artifact.getPath();
            try {
                classesToSources = CarUtils.retrieveMappingFile(returnCarFile());
                javaImplFilesToCeylonDeclFiles = CarUtils.searchCeylonFilesForJavaImplementations(classesToSources,
                        artifact);
            } catch (Exception e) {
                e.printStackTrace();
            }
            sourceModulePhasedUnits = new WeakReference<ExternalModulePhasedUnits>(null);
        }

        try {
            IJavaProject javaProject = moduleManager.getJavaProject();
            if (javaProject != null) {
                for (IProject refProject : javaProject.getProject().getReferencedProjects()) {
                    if (artifact.getAbsolutePath().contains(
                            CeylonBuilder.getCeylonModulesOutputDirectory(refProject).getAbsolutePath())) {
                        originalProject = refProject;
                    }
                }
            }
        } catch (CoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if (isJavaBinaryArchive()) {
            String carPath = artifact.getPath();
            sourceArchivePath = carPath.substring(0, carPath.length() - ArtifactContext.JAR.length())
                    + (artifactResult.type().equals(ArtifactResultType.MAVEN) ? ArtifactContext.MAVEN_SRC
                            : ArtifactContext.SRC);
        }
    }

    public String getRepositoryDisplayString() {
        if (isJDKModule()) {
            return JDKRepository.JDK_REPOSITORY_DISPLAY_STRING;
        }
        return repositoryDisplayString;
    }

    synchronized void setSourcePhasedUnits(final ExternalModulePhasedUnits modulePhasedUnits) {
        sourceModulePhasedUnits = new WeakReference<ExternalModulePhasedUnits>(modulePhasedUnits);
    }

    public File getArtifact() {
        return artifact;
    }

    public ArtifactResultType getArtifactType() {
        return artifactType;
    }

    private Properties getClassesToSources() {
        if (classesToSources.isEmpty() && getNameAsString().equals("java.base")) {
            for (Map.Entry<Object, Object> entry : ((JDTModule) getLanguageModule()).getClassesToSources()
                    .entrySet()) {
                if (entry.getKey().toString().startsWith("com/redhat/ceylon/compiler/java/language/")) {
                    classesToSources.put(entry.getKey().toString()
                            .replace("com/redhat/ceylon/compiler/java/language/", "java/lang/"),
                            entry.getValue().toString());
                }
            }
        }
        return classesToSources;
    }

    public String toSourceUnitRelativePath(String binaryUnitRelativePath) {
        return getClassesToSources().getProperty(binaryUnitRelativePath);
    }

    public String getJavaImplementationFile(String ceylonFileRelativePath) {
        String javaFileRelativePath = null;
        for (Entry<String, String> entry : javaImplFilesToCeylonDeclFiles.entrySet()) {
            if (entry.getValue().equals(ceylonFileRelativePath)) {
                javaFileRelativePath = entry.getKey();
            }
        }
        return javaFileRelativePath;
    }

    public String getCeylonDeclarationFile(String sourceUnitRelativePath) {
        if (sourceUnitRelativePath == null || sourceUnitRelativePath.endsWith(".ceylon")) {
            return sourceUnitRelativePath;
        }
        return javaImplFilesToCeylonDeclFiles.get(sourceUnitRelativePath);
    }

    public List<String> toBinaryUnitRelativePaths(String sourceUnitRelativePath) {
        if (sourceUnitRelativePath == null) {
            return Collections.emptyList();
        }

        List<String> result = new ArrayList<String>();
        for (Entry<Object, Object> entry : classesToSources.entrySet()) {
            if (sourceUnitRelativePath.equals(entry.getValue())) {
                result.add((String) entry.getKey());
            }
        }
        return result;
    }

    public synchronized List<IPackageFragmentRoot> getPackageFragmentRoots() {
        if (packageFragmentRoots.isEmpty() && !moduleManager.isExternalModuleLoadedFromSource(getNameAsString())) {
            IJavaProject javaProject = moduleManager.getJavaProject();
            if (javaProject != null) {
                if (this.equals(getLanguageModule())) {
                    IClasspathEntry runtimeClasspathEntry = null;

                    try {
                        for (IClasspathEntry entry : javaProject.getRawClasspath()) {
                            if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && entry.getPath().segment(0)
                                    .equals(CeylonLanguageModuleContainer.CONTAINER_ID)) {
                                runtimeClasspathEntry = entry;
                                break;
                            }
                        }

                        if (runtimeClasspathEntry != null) {
                            for (IPackageFragmentRoot root : javaProject.getPackageFragmentRoots()) {
                                if (root.exists() && javaProject.isOnClasspath(root)
                                        && root.getRawClasspathEntry().equals(runtimeClasspathEntry)) {
                                    packageFragmentRoots.add(root);
                                }
                            }
                        }
                    } catch (JavaModelException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                } else {
                    File jarToSearch = null;
                    try {
                        jarToSearch = returnCarFile();
                        if (jarToSearch == null) {
                            RepositoryManager repoMgr = CeylonBuilder
                                    .getProjectRepositoryManager(javaProject.getProject());
                            if (repoMgr != null) {
                                jarToSearch = CeylonProjectModulesContainer.getModuleArtifact(repoMgr, this);
                            }
                        }

                        if (jarToSearch != null) {
                            IPackageFragmentRoot root = moduleManager.getJavaProject()
                                    .getPackageFragmentRoot(jarToSearch.toString());
                            if (root instanceof JarPackageFragmentRoot) {
                                JarPackageFragmentRoot jarRoot = (JarPackageFragmentRoot) root;
                                if (jarRoot.getJar().getName().equals(jarToSearch.getPath())) {
                                    packageFragmentRoots.add(root);
                                }
                            }
                        }
                    } catch (CoreException e) {
                        if (jarToSearch != null) {
                            System.err.println("Exception trying to get Jar file '" + jarToSearch + "' :");
                        }
                        e.printStackTrace();
                    }
                }
            }
        }
        return packageFragmentRoots;
    }

    @Override
    protected JDTModelLoader getModelLoader() {
        return moduleManager.getModelLoader();
    }

    public JDTModuleManager getModuleManager() {
        return moduleManager;
    }

    @Override
    public List<Package> getAllPackages() {
        synchronized (getModelLoader()) {
            // force-load every package from the module if we can
            loadAllPackages(new HashSet<String>());

            // now delegate
            return super.getAllPackages();
        }
    }

    private void loadAllPackages(Set<String> alreadyScannedModules) {
        Set<String> packageList = listPackages();
        for (String packageName : packageList) {
            getPackage(packageName);
        }

        // now force-load other modules
        for (ModuleImport mi : getImports()) {
            Module importedModule = mi.getModule();
            if (importedModule instanceof JDTModule
                    && alreadyScannedModules.add(importedModule.getNameAsString())) {
                ((JDTModule) importedModule).loadAllPackages(alreadyScannedModules);
            }
        }
    }

    private Set<String> listPackages() {
        Set<String> packageList = new TreeSet<String>();
        String name = getNameAsString();
        if (JDKUtils.isJDKModule(name)) {
            packageList.addAll(JDKUtils.getJDKPackagesByModule(name));
        } else if (JDKUtils.isOracleJDKModule(name)) {
            packageList.addAll(JDKUtils.getOracleJDKPackagesByModule(name));
        } else if (isJava() || true) {
            for (IPackageFragmentRoot fragmentRoot : getPackageFragmentRoots()) {
                if (!fragmentRoot.exists())
                    continue;
                IParent parent = fragmentRoot;
                listPackages(packageList, parent);
            }
        }
        return packageList;
    }

    private void listPackages(Set<String> packageList, IParent parent) {
        try {
            for (IJavaElement child : parent.getChildren()) {
                if (child instanceof PackageFragment) {
                    packageList.add(child.getElementName());
                    listPackages(packageList, (IPackageFragment) child);
                }
            }
        } catch (JavaModelException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void loadPackageList(ArtifactResult artifact) {
        try {
            super.loadPackageList(artifact);
        } catch (Exception e) {
            CeylonPlugin.getInstance().getLog().log(new Status(IStatus.ERROR, CeylonPlugin.PLUGIN_ID,
                    "Failed loading the package list of module " + getSignature(), e));
        }
        JDTModelLoader modelLoader = getModelLoader();
        if (modelLoader != null) {
            synchronized (modelLoader) {
                String name = getNameAsString();
                for (String pkg : jarPackages) {
                    if (name.equals("ceylon.language") && !pkg.startsWith("ceylon.language")) {
                        // special case for the language module to hide stuff
                        continue;
                    }
                    modelLoader.findOrCreatePackage(this, pkg);
                }
            }
        }
    }

    private ModuleType moduleType;

    private enum ModuleType {
        PROJECT_MODULE, CEYLON_SOURCE_ARCHIVE, CEYLON_BINARY_ARCHIVE, JAVA_BINARY_ARCHIVE, SDK_MODULE, UNKNOWN
    }

    public void setProjectModule() {
        moduleType = ModuleType.PROJECT_MODULE;
    }

    public boolean isCeylonArchive() {
        return isCeylonBinaryArchive() || isSourceArchive();
    }

    public boolean isDefaultModule() {
        return this.equals(moduleManager.getContext().getModules().getDefaultModule());
    }

    public boolean isProjectModule() {
        return ModuleType.PROJECT_MODULE.equals(moduleType);
    }

    public boolean isJDKModule() {
        synchronized (this) {
            if (moduleType == null) {
                if (JDKUtils.isJDKModule(getNameAsString()) || JDKUtils.isOracleJDKModule(getNameAsString())) {
                    moduleType = ModuleType.SDK_MODULE;
                }
            }
        }
        return ModuleType.SDK_MODULE.equals(moduleType);
    }

    public boolean isUnresolved() {
        return artifact == null && !isAvailable();
    }

    public boolean isJavaBinaryArchive() {
        return ModuleType.JAVA_BINARY_ARCHIVE.equals(moduleType);
    }

    public boolean isCeylonBinaryArchive() {
        return ModuleType.CEYLON_BINARY_ARCHIVE.equals(moduleType);
    }

    public boolean isSourceArchive() {
        return ModuleType.CEYLON_SOURCE_ARCHIVE.equals(moduleType);
    }

    public synchronized List<? extends PhasedUnit> getPhasedUnits() {
        PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
        if (isCeylonBinaryArchive()) {
            phasedUnitMap = binaryModulePhasedUnits;
        }
        if (isSourceArchive()) {
            phasedUnitMap = sourceModulePhasedUnits.get();
        }
        if (phasedUnitMap != null) {
            synchronized (phasedUnitMap) {
                return phasedUnitMap.getPhasedUnits();
            }
        }
        return Collections.emptyList();
    }

    public ExternalPhasedUnit getPhasedUnit(IPath path) {
        PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
        if (isCeylonBinaryArchive()) {
            phasedUnitMap = binaryModulePhasedUnits;
        }
        if (isSourceArchive()) {
            phasedUnitMap = sourceModulePhasedUnits.get();
        }
        if (phasedUnitMap != null) {
            IPath sourceArchiveIPath = new Path(sourceArchivePath + "!");
            synchronized (phasedUnitMap) {
                return (ExternalPhasedUnit) phasedUnitMap
                        .getPhasedUnitFromRelativePath(path.makeRelativeTo(sourceArchiveIPath).toString());
            }
        }
        return null;
    }

    public ExternalPhasedUnit getPhasedUnit(String path) {
        PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
        if (isCeylonBinaryArchive()) {
            phasedUnitMap = binaryModulePhasedUnits;
        }
        if (isSourceArchive()) {
            phasedUnitMap = sourceModulePhasedUnits.get();
        }
        if (phasedUnitMap != null) {
            synchronized (phasedUnitMap) {
                return (ExternalPhasedUnit) phasedUnitMap.getPhasedUnit(path);
            }
        }
        return null;
    }

    public ExternalPhasedUnit getPhasedUnit(VirtualFile file) {
        PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
        if (isCeylonBinaryArchive()) {
            phasedUnitMap = binaryModulePhasedUnits;
        }
        if (isSourceArchive()) {
            phasedUnitMap = sourceModulePhasedUnits.get();
        }
        if (phasedUnitMap != null) {
            synchronized (phasedUnitMap) {
                return (ExternalPhasedUnit) phasedUnitMap.getPhasedUnit(file);
            }
        }
        return null;
    }

    public ExternalPhasedUnit getPhasedUnitFromRelativePath(String relativePathToSource) {
        PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
        if (isCeylonBinaryArchive()) {
            phasedUnitMap = binaryModulePhasedUnits;
        }
        if (isSourceArchive()) {
            phasedUnitMap = sourceModulePhasedUnits.get();
        }
        if (phasedUnitMap != null) {
            synchronized (phasedUnitMap) {
                return (ExternalPhasedUnit) phasedUnitMap.getPhasedUnitFromRelativePath(relativePathToSource);
            }
        }
        return null;
    }

    public void removedOriginalUnit(String relativePathToSource) {
        if (isProjectModule()) {
            return;
        }
        originalUnitsToRemove.add(relativePathToSource);

        try {
            if (isCeylonBinaryArchive() || JavaCore.isJavaLikeFileName(relativePathToSource)) {
                List<String> unitPathsToSearch = new ArrayList<>();
                unitPathsToSearch.add(relativePathToSource);
                unitPathsToSearch.addAll(toBinaryUnitRelativePaths(relativePathToSource));
                for (String relativePathOfUnitToRemove : unitPathsToSearch) {
                    Package p = getPackageFromRelativePath(relativePathOfUnitToRemove);
                    if (p != null) {
                        Set<Unit> units = new HashSet<>();
                        try {
                            for (Declaration d : p.getMembers()) {
                                Unit u = d.getUnit();
                                if (u.getRelativePath().equals(relativePathOfUnitToRemove)) {
                                    units.add(u);
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                        for (Unit u : units) {
                            try {
                                for (Declaration d : u.getDeclarations()) {
                                    d.getMembers();
                                    // Just to fully load the declaration before 
                                    // the corresponding class is removed (so that 
                                    // the real removing from the model loader
                                    // will not require reading the bindings.
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void addedOriginalUnit(String relativePathToSource) {
        if (isProjectModule()) {
            return;
        }
        originalUnitsToAdd.add(relativePathToSource);
    }

    public void refresh() {
        if (originalUnitsToAdd.size() + originalUnitsToRemove.size() == 0) {
            // Nothing to refresh
            return;
        }

        try {
            PhasedUnitMap<? extends PhasedUnit, ?> phasedUnitMap = null;
            if (isCeylonBinaryArchive()) {
                JavaModelManager.getJavaModelManager().resetClasspathListCache();
                JavaModelManager.getJavaModelManager().getJavaModel().refreshExternalArchives(
                        getPackageFragmentRoots().toArray(new IPackageFragmentRoot[0]), null);
                phasedUnitMap = binaryModulePhasedUnits;
            }
            if (isSourceArchive()) {
                phasedUnitMap = sourceModulePhasedUnits.get();
            }
            if (phasedUnitMap != null) {
                synchronized (phasedUnitMap) {
                    for (String relativePathToRemove : originalUnitsToRemove) {
                        if (isCeylonBinaryArchive() || JavaCore.isJavaLikeFileName(relativePathToRemove)) {
                            List<String> unitPathsToSearch = new ArrayList<>();
                            unitPathsToSearch.add(relativePathToRemove);
                            unitPathsToSearch.addAll(toBinaryUnitRelativePaths(relativePathToRemove));
                            for (String relativePathOfUnitToRemove : unitPathsToSearch) {
                                Package p = getPackageFromRelativePath(relativePathOfUnitToRemove);
                                if (p != null) {
                                    Set<Unit> units = new HashSet<>();
                                    for (Declaration d : p.getMembers()) {
                                        Unit u = d.getUnit();
                                        if (u.getRelativePath().equals(relativePathOfUnitToRemove)) {
                                            units.add(u);
                                        }
                                    }
                                    for (Unit u : units) {
                                        try {
                                            p.removeUnit(u);
                                            // In the future, when we are sure that we cannot add several unit objects with the 
                                            // same relative path, we can add a break.
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    }
                                } else {
                                    System.out.println("WARNING : The package of the following binary unit ("
                                            + relativePathOfUnitToRemove + ") " + "cannot be found in module "
                                            + getNameAsString() + artifact != null
                                                    ? " (artifact=" + artifact.getAbsolutePath() + ")"
                                                    : "");
                                }
                            }
                        }
                        phasedUnitMap.removePhasedUnitForRelativePath(relativePathToRemove);
                    }

                    if (isSourceArchive()) {
                        ClosableVirtualFile sourceArchive = null;
                        try {
                            sourceArchive = moduleManager.getContext().getVfs()
                                    .getFromZipFile(new File(sourceArchivePath));
                            for (String relativePathToAdd : originalUnitsToAdd) {
                                VirtualFile archiveEntry = null;
                                archiveEntry = searchInSourceArchive(relativePathToAdd, sourceArchive);

                                if (archiveEntry != null) {
                                    Package pkg = getPackageFromRelativePath(relativePathToAdd);
                                    ((ExternalModulePhasedUnits) phasedUnitMap).parseFile(archiveEntry,
                                            sourceArchive, pkg);
                                }
                            }
                        } catch (Exception e) {
                            StringBuilder error = new StringBuilder("Unable to read source artifact from ");
                            error.append(sourceArchive);
                            error.append("\ndue to connection error: ").append(e.getMessage());
                            throw e;
                        } finally {
                            if (sourceArchive != null) {
                                sourceArchive.close();
                            }
                        }
                    }

                    classesToSources = CarUtils.retrieveMappingFile(returnCarFile());
                    javaImplFilesToCeylonDeclFiles = CarUtils
                            .searchCeylonFilesForJavaImplementations(classesToSources, new File(sourceArchivePath));

                    originalUnitsToRemove.clear();
                    originalUnitsToAdd.clear();
                }
            }
            if (isCeylonBinaryArchive() || isJavaBinaryArchive()) {
                jarPackages.clear();
                loadPackageList(new ArtifactResult() {
                    @Override
                    public VisibilityType visibilityType() {
                        return null;
                    }

                    @Override
                    public String version() {
                        return null;
                    }

                    @Override
                    public ArtifactResultType type() {
                        return null;
                    }

                    @Override
                    public String name() {
                        return null;
                    }

                    @Override
                    public ImportType importType() {
                        return null;
                    }

                    @Override
                    public List<ArtifactResult> dependencies() throws RepositoryException {
                        return null;
                    }

                    @Override
                    public File artifact() throws RepositoryException {
                        return artifact;
                    }

                    @Override
                    public String repositoryDisplayString() {
                        return "";
                    }

                    @Override
                    public PathFilter filter() {
                        return null;
                    }

                    @Override
                    public Repository repository() {
                        return null;
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Package getPackageFromRelativePath(String relativePathOfClassToRemove) {
        List<String> pathElements = Arrays.asList(relativePathOfClassToRemove.split("/"));
        String packageName = Util.formatPath(pathElements.subList(0, pathElements.size() - 1));
        Package p = findPackageNoLazyLoading(packageName);
        return p;
    }

    private ExternalPhasedUnit buildPhasedUnitForBinaryUnit(String sourceUnitFullPath) {
        if (sourceArchivePath == null || sourceUnitFullPath == null) {
            return null;
        }

        if (!sourceUnitFullPath.startsWith(sourceArchivePath)) {
            return null;
        }

        File sourceArchiveFile = new File(sourceArchivePath);
        if (!sourceArchiveFile.exists()) {
            return null;
        }

        ExternalPhasedUnit phasedUnit = null;
        String sourceUnitRelativePath = sourceUnitFullPath.replace(sourceArchivePath + "!/", "");
        Package pkg = getPackageFromRelativePath(sourceUnitRelativePath);
        if (pkg != null) {
            try {
                JDTModuleManager moduleManager = getModuleManager();
                ClosableVirtualFile sourceArchive = null;
                try {
                    sourceArchive = moduleManager.getContext().getVfs().getFromZipFile(sourceArchiveFile);

                    String ceylonSourceUnitRelativePath = sourceUnitRelativePath;
                    if (sourceUnitRelativePath.endsWith(".java")) {
                        ceylonSourceUnitRelativePath = javaImplFilesToCeylonDeclFiles.get(sourceUnitRelativePath);
                    }

                    VirtualFile archiveEntry = null;
                    if (ceylonSourceUnitRelativePath != null) {
                        archiveEntry = searchInSourceArchive(ceylonSourceUnitRelativePath, sourceArchive);
                    }

                    if (archiveEntry != null) {
                        IProject project = moduleManager.getJavaProject().getProject();
                        CeylonLexer lexer = new CeylonLexer(NewlineFixingStringStream
                                .fromStream(archiveEntry.getInputStream(), project.getDefaultCharset()));
                        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
                        CeylonParser parser = new CeylonParser(tokenStream);
                        Tree.CompilationUnit cu = parser.compilationUnit();
                        List<CommonToken> tokens = new ArrayList<CommonToken>(tokenStream.getTokens().size());
                        tokens.addAll(tokenStream.getTokens());
                        if (originalProject == null) {
                            phasedUnit = new ExternalPhasedUnit(archiveEntry, sourceArchive, cu,
                                    new SingleSourceUnitPackage(pkg, sourceUnitFullPath), moduleManager,
                                    CeylonBuilder.getProjectTypeChecker(project), tokens) {
                                @Override
                                protected boolean reuseExistingDescriptorModels() {
                                    return true;
                                }
                            };
                        } else {
                            phasedUnit = new CrossProjectPhasedUnit(archiveEntry, sourceArchive, cu,
                                    new SingleSourceUnitPackage(pkg, sourceUnitFullPath), moduleManager,
                                    CeylonBuilder.getProjectTypeChecker(project), tokens, originalProject) {
                                @Override
                                protected boolean reuseExistingDescriptorModels() {
                                    return true;
                                }
                            };
                        }
                    }
                } catch (Exception e) {
                    StringBuilder error = new StringBuilder("Unable to read source artifact from ");
                    error.append(sourceArchive);
                    error.append("\ndue to connection error: ").append(e.getMessage());
                    System.err.println(error);
                } finally {
                    if (sourceArchive != null) {
                        sourceArchive.close();
                    }
                }
                if (phasedUnit != null) {
                    phasedUnit.validateTree();
                    phasedUnit.visitSrcModulePhase();
                    phasedUnit.visitRemainingModulePhase();
                    phasedUnit.scanDeclarations();
                    phasedUnit.scanTypeDeclarations();
                    phasedUnit.validateRefinement();
                }
            } catch (Exception e) {
                e.printStackTrace();
                phasedUnit = null;
            }
        }
        return phasedUnit;
    }

    private VirtualFile searchInSourceArchive(String sourceUnitRelativePath, ClosableVirtualFile sourceArchive) {
        VirtualFile archiveEntry;
        archiveEntry = sourceArchive;
        for (String part : sourceUnitRelativePath.split("/")) {
            boolean found = false;
            for (VirtualFile vf : archiveEntry.getChildren()) {
                if (part.equals(vf.getName().replace("/", ""))) {
                    archiveEntry = vf;
                    found = true;
                    break;
                }
            }
            if (!found) {
                archiveEntry = null;
                break;
            }
        }
        return archiveEntry;
    }

    public String getSourceArchivePath() {
        return sourceArchivePath;
    }

    public IProject getOriginalProject() {
        return originalProject;
    }

    public JDTModule getOriginalModule() {
        if (originalProject != null) {
            if (originalModule == null) {
                for (Module m : CeylonBuilder.getProjectModules(originalProject).getListOfModules()) {
                    // TODO : in the future : manage version ?? in case we reference 2 identical projects with different version in the workspace
                    if (m.getNameAsString().equals(getNameAsString())) {
                        assert (m instanceof JDTModule);
                        if (((JDTModule) m).isProjectModule()) {
                            originalModule = (JDTModule) m;
                            break;
                        }
                    }
                }
            }
            return originalModule;
        }
        return null;
    }

    public boolean containsClass(String className) {
        return className != null && (classesToSources != null ? classesToSources.containsKey(className) : false);
    }

    @Override
    public List<Package> getPackages() {
        return super.getPackages();
    }

    @Override
    public void clearCache(final TypeDeclaration declaration) {
        clearCacheLocally(declaration);
        if (getProjectModuleDependencies() != null) {
            getProjectModuleDependencies().doWithReferencingModules(this, new TraversalAction<Module>() {
                @Override
                public void applyOn(Module module) {
                    assert (module instanceof JDTModule);
                    if (module instanceof JDTModule) {
                        ((JDTModule) module).clearCacheLocally(declaration);
                    }
                }
            });
            getProjectModuleDependencies().doWithTransitiveDependencies(this, new TraversalAction<Module>() {
                @Override
                public void applyOn(Module module) {
                    assert (module instanceof JDTModule);
                    if (module instanceof JDTModule) {
                        ((JDTModule) module).clearCacheLocally(declaration);
                    }
                }
            });
            ((JDTModule) getLanguageModule()).clearCacheLocally(declaration);
        }
    }

    private void clearCacheLocally(final TypeDeclaration declaration) {
        super.clearCache(declaration);
    }

    private ModuleDependencies projectModuleDependencies = null;

    private ModuleDependencies getProjectModuleDependencies() {
        if (projectModuleDependencies == null) {
            IJavaProject javaProject = moduleManager.getJavaProject();
            if (javaProject != null) {
                projectModuleDependencies = CeylonBuilder.getModuleDependenciesForProject(javaProject.getProject());
            }
        }
        return projectModuleDependencies;
    }

    public Iterable<Module> getReferencingModules() {
        if (getProjectModuleDependencies() != null) {
            return getProjectModuleDependencies().getReferencingModules(this);
        }
        return Collections.emptyList();
    }

    public boolean resolutionFailed() {
        return resolutionException != null;
    }

    public void setResolutionException(Exception resolutionException) {
        if (resolutionException instanceof RuntimeException)
            this.resolutionException = resolutionException;
    }

    public List<JDTModule> getModuleInReferencingProjects() {
        if (!isProjectModule()) {
            return Collections.emptyList();
        }
        IProject project = moduleManager.getJavaProject().getProject();
        IProject[] referencingProjects = project.getReferencingProjects();
        if (referencingProjects.length == 0) {
            return Collections.emptyList();
        }

        List<JDTModule> result = new ArrayList<>();
        for (IProject referencingProject : referencingProjects) {
            JDTModule referencingModule = null;
            Modules referencingProjectModules = CeylonBuilder.getProjectModules(referencingProject);
            if (referencingProjectModules != null) {
                for (Module m : referencingProjectModules.getListOfModules()) {
                    if (m.getSignature().equals(getSignature())) {
                        assert (m instanceof JDTModule);
                        referencingModule = (JDTModule) m;
                        break;
                    }
                }
            }
            if (referencingModule != null) {
                result.add(referencingModule);
            }
        }
        return result;
    }

}