Package com.redhat.ceylon.compiler.typechecker.analyzer

Source Code of com.redhat.ceylon.compiler.typechecker.analyzer.ModuleManager$ModuleDependencyAnalysisError

package com.redhat.ceylon.compiler.typechecker.analyzer;

import static com.redhat.ceylon.compiler.typechecker.model.Util.formatPath;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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 com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ArtifactResult;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.compiler.typechecker.TypeChecker;
import com.redhat.ceylon.compiler.typechecker.context.Context;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnits;
import com.redhat.ceylon.compiler.typechecker.io.ClosableVirtualFile;
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.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ModuleDescriptor;

/**
* Manager modules and packages (build, retrieve, handle errors etc)
*
* @author Emmanuel Bernard <emmanuel@hibernate.org>
*/
public class ModuleManager {
    public static class ModuleDependencyAnalysisError extends AnalysisError {
   
        public ModuleDependencyAnalysisError(Node treeNode, String message, int code) {
            super(treeNode, message, code);
        }
       
        public ModuleDependencyAnalysisError(Node treeNode, String message) {
            super(treeNode, message);
        }
    }

    public static final String MODULE_FILE = "module.ceylon";
    public static final String PACKAGE_FILE = "package.ceylon";
    private final Context context;
    private final LinkedList<Package> packageStack = new LinkedList<Package>();
    private Module currentModule;
    private Modules modules;
    private static Object PRESENT = new Object();
    private final Map<ModuleImport,WeakHashMap<Node, Object>> moduleImportToNode = new HashMap<ModuleImport, WeakHashMap<Node, Object>>();
    private Map<List<String>, Set<String>> topLevelErrorsPerModuleName = new HashMap<List<String>,Set<String>>();
    private Map<Module, Node> moduleToNode = new TreeMap<Module, Node>();

    public ModuleManager(Context context) {
        this.context = context;
    }
   
    protected Package createPackage(String pkgName, Module module) {
        final Package pkg = new Package();
        List<String> name = pkgName.isEmpty() ? Arrays.asList("") : splitModuleName(pkgName);
        pkg.setName(name);
        if (module != null) {
            module.getPackages().add(pkg);
            pkg.setModule(module);
        }
        return pkg;
    }

    public void initCoreModules() {
        packageStack.clear();
        currentModule = null;
       
        modules = context.getModules();
        if ( modules == null ) {
            modules = new Modules();
            context.setModules(modules);
            //build empty package
            final Package emptyPackage = createPackage("", null);
            packageStack.addLast(emptyPackage);

            //build default module (module in which packages belong to when not explicitly under a module
            final List<String> defaultModuleName = Collections.singletonList(Module.DEFAULT_MODULE_NAME);
            final Module defaultModule = createModule(defaultModuleName, "unversioned");
            defaultModule.setDefault(true);
            defaultModule.setAvailable(true);
            bindPackageToModule(emptyPackage, defaultModule);
            modules.getListOfModules().add(defaultModule);
            modules.setDefaultModule(defaultModule);

            //create language module and add it as a dependency of defaultModule
            //since packages outside a module cannot declare dependencies
            final List<String> languageName = Arrays.asList("ceylon", "language");
            Module languageModule = createModule(languageName, TypeChecker.LANGUAGE_MODULE_VERSION);
            languageModule.setLanguageModule(languageModule);
            languageModule.setAvailable(false); //not available yet
            modules.setLanguageModule(languageModule);
            modules.getListOfModules().add(languageModule);
            defaultModule.addImport(new ModuleImport(languageModule, false, false));
            defaultModule.setLanguageModule(languageModule);
        }
        else {
            modules = context.getModules();
            packageStack.addLast( modules.getDefaultModule().getPackages().get(0) );
        }
    }

    protected Module createModule(List<String> moduleName, String version) {
    Module module = new Module();
    module.setName(moduleName);
    module.setVersion(version);
    return module;
  }

  public void push(String path) {
        createPackageAndAddToModule(path);
    }

    public void pop() {
        removeLastPackageAndModuleIfNecessary();
    }

    public Package getCurrentPackage() {
        return packageStack.peekLast();
    }

    /**
     * Get or create a module.
     * version == null is considered equal to any version.
     * Likewise a module with no version will match any version passed
     */
    public Module getOrCreateModule(List<String> moduleName, String version) {
        if (moduleName.size() == 0) {
            return null;
        }
        Module module = null;
        final Set<Module> moduleList = context.getModules().getListOfModules();
        for (Module current : moduleList) {
            final List<String> names = current.getName();
            if (moduleName.equals(names)
                    && compareVersions(current, version, current.getVersion())) {
                module = current;
                break;
            }
        }
        if (module == null) {
            module = createModule(moduleName, version);
            module.setLanguageModule(modules.getLanguageModule());
            moduleList.add(module);
        }
        return module;
    }

    protected boolean compareVersions(Module current, String version, String currentVersion) {
        return currentVersion == null || version == null || currentVersion.equals(version);
    }

    public void visitModuleFile() {
        if ( currentModule == null ) {
            final Package currentPkg = packageStack.peekLast();
            final List<String> moduleName = currentPkg.getName();
            //we don't know the version at this stage, will be filled later
            currentModule = getOrCreateModule(moduleName, null);
            if ( currentModule != null ) {
                currentModule.setAvailable(true); // TODO : not necessary anymore ? the phasedUnit will be added. And the buildModuleImport()
                                                  //        function (which calls module.setAvailable()) will be called by the typeChecker
                                                  //        BEFORE the ModuleValidator.verifyModuleDependencyTree() call that uses
                                                  //        isAvailable()
                bindPackageToModule(currentPkg, currentModule);
            }
            else {
                addErrorToModule(new ArrayList<String>(),
                        "module may not be defined at the top level of the hierarchy");
            }
        }
        else {
            StringBuilder error = new StringBuilder("two modules within the same hierarchy: '");
            error.append( formatPath( currentModule.getName() ) )
                .append( "' and '" )
                .append( formatPath( packageStack.peekLast().getName() ) )
                .append("'");
            addErrorToModule(currentModule.getName(), error.toString());
            addErrorToModule(packageStack.peekLast().getName(), error.toString());
        }
    }

    private void createPackageAndAddToModule(String path) {
        final Package lastPkg = packageStack.peekLast();
        List<String> parentName = lastPkg.getName();
        final ArrayList<String> name = new ArrayList<String>(parentName.size() + 1);
        name.addAll(parentName);
        name.add(path);
       
        Package pkg = createPackage(formatPath(name),
                currentModule != null ? currentModule : modules.getDefaultModule());
        packageStack.addLast(pkg);
    }

    private void removeLastPackageAndModuleIfNecessary() {
        packageStack.pollLast();
        final boolean moveAboveModuleLevel = currentModule != null
                && currentModule.getName().size() > packageStack.size() -1; //first package is the empty package
        if (moveAboveModuleLevel) {
            currentModule = null;
        }
    }

    private void bindPackageToModule(Package pkg, Module module) {
        //undo nomodule setting if necessary
        if (pkg.getModule() != null) {
            pkg.getModule().getPackages().remove(pkg);
            pkg.setModule(null);
        }
        module.getPackages().add(pkg);
        pkg.setModule(module);
    }

    public void addModuleDependencyDefinition(ModuleImport moduleImport, Node definition) {
        WeakHashMap<Node, Object> moduleDepDefinition = moduleImportToNode.get(moduleImport);
        if (moduleDepDefinition == null) {
            moduleDepDefinition = new WeakHashMap<Node, Object>();
            moduleImportToNode.put(moduleImport, moduleDepDefinition);
        }
        moduleDepDefinition.put(definition, PRESENT);
    }

    public void attachErrorToDependencyDeclaration(ModuleImport moduleImport, List<Module> dependencyTree, String error) {
        if (!attachErrorToDependencyDeclaration(moduleImport, error)) {
            //This probably can happen if the missing dependency is found deep in the dependency structure (ie the binary version of a module)
            // in theory the first item in the dependency tree is the compiled module, and the second one is the import we have to add
            // the error to
            if(dependencyTree.size() >= 2){
                Module rootModule = dependencyTree.get(0);
                Module originalImportedModule = dependencyTree.get(1);
                // find the original import
                for(ModuleImport imp : rootModule.getImports()){
                    if(imp.getModule() == originalImportedModule){
                        // found it, try to attach the error
                        if(attachErrorToDependencyDeclaration(imp, error)){
                            // we're done
                            return;
                        }else{
                            // failed
                            break;
                        }
                    }
                }
            }
            System.err.println("This might be a type checker bug, please report. \nExpecting to add missing dependency error on non present definition: " + error);
        }
    }

    private boolean attachErrorToDependencyDeclaration(ModuleImport moduleImport, String error) {
        WeakHashMap<Node, Object> moduleDepError = moduleImportToNode.get(moduleImport);
        if (moduleDepError != null) {
            for ( Node definition :  moduleDepError.keySet() ) {
                definition.addError(new ModuleDependencyAnalysisError(definition, error));
            }
            return true;
        }
        return false;
    }

    public void attachErrorToOriginalModuleImport(Module module, String error){
        if(getCompiledModules().contains(module)){
            // we're compiling it, just add it to the module node then
            addErrorToModule(module, error);
        }else{
            // we must be importing it
            for(Entry<ModuleImport, WeakHashMap<Node, Object>> entry : moduleImportToNode.entrySet()){
                if(entry.getKey().getModule() == module){
                    for ( Node definition :  entry.getValue().keySet() ) {
                        definition.addError(new ModuleDependencyAnalysisError(definition, error));
                    }
                }
            }
        }
    }

    //must be used *after* addLinkBetweenModuleAndNode has been set ie post ModuleVisitor visit
    public void addErrorToModule(Module module, String error) {
        Node node = moduleToNode.get(module);
        if (node != null) {
            node.addError(new ModuleDependencyAnalysisError(node, error));
        }
        else {
            //might happen if the faulty module is a compiled module
            System.err.println("This is a type checker bug, please report. " +
                    "\nExpecting to add error on non present module node: " + module.toString() + ". Error " + error);
        }
    }

    //only used if we really don't know the version
    protected void addErrorToModule(List<String> moduleName, String error) {
        Set<String> errors = topLevelErrorsPerModuleName.get(moduleName);
        if (errors == null) {
            errors = new HashSet<String>();
            topLevelErrorsPerModuleName.put(moduleName, errors);
        }
        errors.add(error);
    }

    public void addLinkBetweenModuleAndNode(Module module, ModuleDescriptor descriptor) {
        //keep link and display errors on modules where we don't know the version of
        Set<String> errors = topLevelErrorsPerModuleName.get(module.getName());
        if (errors != null) {
            for(String error : errors) {
                descriptor.addError(new ModuleDependencyAnalysisError(descriptor, error));
            }
            errors.clear();
        }
        moduleToNode.put(module,descriptor);
    }
   
    public Set<Module> getCompiledModules(){
        return moduleToNode.keySet();
    }

    public ModuleImport findImport(Module owner, Module dependency) {
        for (ModuleImport modImprt : owner.getImports()) {
            if (equalsForModules(modImprt.getModule(), dependency, true)) return modImprt;
        }
        return null;
    }

    public boolean equalsForModules(Module left, Module right, boolean exactVersionMatch) {
        if (left == right) return true;
        List<String> leftName = left.getName();
        List<String> rightName = right.getName();
        if (leftName.size() != rightName.size()) return false;
        for(int index = 0 ; index < leftName.size(); index++) {
            if (!leftName.get(index).equals(rightName.get(index))) return false;
        }
        if (exactVersionMatch && !left.getVersion().equals(right.getVersion())) return false;
        return true;
    }

    public Module findModule(Module module, List<Module> listOfModules, boolean exactVersionMatch) {
        for(Module current : listOfModules) {
            if (equalsForModules(module, current, exactVersionMatch)) return current;
        }
        return null;
    }

    public Module findLoadedModule(String moduleName, String searchedVersion) {
        return findLoadedModule(moduleName, searchedVersion, modules);
    }
   
    public Module findLoadedModule(String moduleName, String searchedVersion, Modules modules) {
        for(Module module : modules.getListOfModules()){
            if(module.getNameAsString().equals(moduleName)) {
                if (searchedVersion != null && searchedVersion.equals(module.getVersion())){
                    return module;
                }
            }
        }
        return null;
    }

    public void resolveModule(ArtifactResult artifact, Module module, ModuleImport moduleImport, LinkedList<Module> dependencyTree, List<PhasedUnits> phasedUnitsOfDependencies, boolean forCompiledModule) {
        //This implementation relies on the ability to read the model from source
        //the compiler for example subclasses this to read lazily and from the compiled model
        ArtifactContext artifactContext = new ArtifactContext(module.getNameAsString(), module.getVersion(), ArtifactContext.SRC);
        RepositoryManager repositoryManager = context.getRepositoryManager();
        Exception exceptionOnGetArtifact = null;
        ArtifactResult sourceArtifact = null;
        try {
            sourceArtifact = repositoryManager.getArtifactResult(artifactContext);
        } catch (Exception e) {
            exceptionOnGetArtifact = e;
        }
        if ( sourceArtifact == null ) {
            ModuleHelper.buildErrorOnMissingArtifact(artifactContext, module, moduleImport, dependencyTree, exceptionOnGetArtifact, this);
        }
        else {
           
            PhasedUnits modulePhasedUnits = createPhasedUnits();
            ClosableVirtualFile virtualArtifact= null;
            try {
                virtualArtifact = context.getVfs().getFromZipFile(sourceArtifact.artifact());
                modulePhasedUnits.parseUnit(virtualArtifact);
                //populate module.getDependencies()
                modulePhasedUnits.visitModules();
                addToPhasedUnitsOfDependencies(modulePhasedUnits, phasedUnitsOfDependencies, module);
            } catch (Exception e) {
                StringBuilder error = new StringBuilder("unable to read source artifact for ");
                error.append(artifactContext.toString());
                error.append( "\ndue to connection error: ").append(e.getMessage());
                attachErrorToDependencyDeclaration(moduleImport, dependencyTree, error.toString());
            } finally {
                if (virtualArtifact != null) {
                    virtualArtifact.close();
                }
            }
        }
    }

    protected void addToPhasedUnitsOfDependencies(PhasedUnits modulePhasedUnits, List<PhasedUnits> phasedUnitsOfDependencies, Module module) {
        phasedUnitsOfDependencies.add(modulePhasedUnits);
    }
   
    protected PhasedUnits createPhasedUnits() {
        return new PhasedUnits(getContext());
    }

    public Iterable<String> getSearchedArtifactExtensions() {
        return Arrays.asList("src");
    }

    public static List<String> splitModuleName(String moduleName) {
        return Arrays.asList(moduleName.split("[\\.]"));
    }

    public Context getContext(){
        return context;
    }

    public void prepareForTypeChecking() {
        // to be overridden by subclasses
    }

    public void addImplicitImports() {
    }

    public void modulesVisited() {
        // to be overridden by subclasses
    }

    public void visitedModule(Module module, boolean forCompiledModule) {
        // to be overridden by subclasses
    }
}
TOP

Related Classes of com.redhat.ceylon.compiler.typechecker.analyzer.ModuleManager$ModuleDependencyAnalysisError

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.