]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java
Compilation of SCL expressions from SCL
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / module / repository / ModuleRepository.java
index 1e43e755be4d79f5ce5933b157765e98203035bf..0f0b8542049a53c7f8eea2667ba15c40789f0f7b 100644 (file)
-package org.simantics.scl.compiler.module.repository;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import java.util.WeakHashMap;\r
-import java.util.concurrent.ConcurrentHashMap;\r
-\r
-import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;\r
-import org.simantics.scl.compiler.elaboration.modules.SCLValue;\r
-import org.simantics.scl.compiler.environment.ConcreteEnvironment;\r
-import org.simantics.scl.compiler.environment.Environment;\r
-import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;\r
-import org.simantics.scl.compiler.environment.NamespaceSpec;\r
-import org.simantics.scl.compiler.environment.filter.NamespaceFilter;\r
-import org.simantics.scl.compiler.environment.filter.NamespaceFilters;\r
-import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;\r
-import org.simantics.scl.compiler.errors.CompilationError;\r
-import org.simantics.scl.compiler.errors.DoesNotExist;\r
-import org.simantics.scl.compiler.errors.Failable;\r
-import org.simantics.scl.compiler.errors.Failure;\r
-import org.simantics.scl.compiler.errors.Success;\r
-import org.simantics.scl.compiler.module.ImportDeclaration;\r
-import org.simantics.scl.compiler.module.Module;\r
-import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;\r
-import org.simantics.scl.compiler.runtime.RuntimeEnvironment;\r
-import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;\r
-import org.simantics.scl.compiler.runtime.RuntimeModule;\r
-import org.simantics.scl.compiler.runtime.RuntimeModuleMap;\r
-import org.simantics.scl.compiler.source.ModuleSource;\r
-import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;\r
-import org.simantics.scl.compiler.top.ModuleInitializer;\r
-import org.simantics.scl.compiler.top.SCLCompilerConfiguration;\r
-import org.simantics.scl.compiler.top.ValueNotFound;\r
-import org.simantics.scl.compiler.types.Types;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.procedure.TObjectObjectProcedure;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-/**\r
- * Manages compilation and caching of SCL modules.\r
- * \r
- * @author Hannu Niemistö\r
- */\r
-public class ModuleRepository {\r
-    private final ModuleRepository parentRepository;\r
-    private final ModuleSourceRepository sourceRepository;\r
-    private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();\r
-    \r
-    private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();\r
-    \r
-    private ModuleCompilationOptionsAdvisor advisor = null;\r
-    \r
-    private static void beginModuleCompilation(String moduleName) {\r
-        THashSet<String> set = PENDING_MODULES.get();\r
-        if(set == null) {\r
-            set = new THashSet<String>();\r
-            PENDING_MODULES.set(set);\r
-        }\r
-        if(!set.add(moduleName))\r
-            throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");\r
-    }\r
-    \r
-    private static void finishModuleCompilation(String moduleName) {\r
-        PENDING_MODULES.get().remove(moduleName);\r
-    }\r
-    \r
-    private class ModuleEntry implements UpdateListener {\r
-        final String moduleName;\r
-        WeakHashMap<UpdateListener,Object> listeners = new WeakHashMap<UpdateListener,Object>();\r
-        \r
-        ModuleSource source;\r
-        Failable<Module> compilationResult;\r
-        Failable<RuntimeModule> runtimeModule; // created lazily\r
-\r
-        public ModuleEntry(String moduleName) {\r
-            this.moduleName = moduleName;\r
-        }\r
-        \r
-        synchronized void addListener(UpdateListener listener) {\r
-            if(listener != null)\r
-                listeners.put(listener, null);\r
-        }\r
-        \r
-        @Override\r
-        public void notifyAboutUpdate() {\r
-            if (listeners == null)\r
-                return;\r
-            ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();\r
-            notifyAboutUpdate(externalListeners);\r
-            for(UpdateListener listener : externalListeners)\r
-                listener.notifyAboutUpdate();\r
-        }\r
-\r
-        synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {\r
-            if(moduleCache.get(moduleName) == this) {\r
-                moduleCache.remove(moduleName);\r
-                if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {\r
-                    System.out.println("Invalidate " + moduleName);\r
-                    for(UpdateListener l : listeners.keySet())\r
-                        System.out.println("    " + l);\r
-                }\r
-                for(UpdateListener l : listeners.keySet())\r
-                    if(l instanceof ModuleEntry)\r
-                        ((ModuleEntry)l).notifyAboutUpdate(externalListeners);\r
-                    else\r
-                        externalListeners.add(l);\r
-            }\r
-        }\r
-\r
-        private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {\r
-            source = sourceRepository.getModuleSource(moduleName, this);\r
-            \r
-            if(source == null)\r
-                compilationResult = DoesNotExist.getInstance();\r
-            else {\r
-                if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)\r
-                    System.out.println("Compile " + source);\r
-                beginModuleCompilation(moduleName);\r
-                compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));\r
-                finishModuleCompilation(moduleName);\r
-            }\r
-        \r
-            ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);\r
-            if(oldEntry != null) {\r
-                oldEntry.addListener(listener);\r
-                return oldEntry;\r
-            }\r
-            \r
-            addListener(listener);\r
-            return this;\r
-        }\r
-        \r
-        @SuppressWarnings({ "rawtypes", "unchecked" })\r
-        public synchronized Failable<RuntimeModule> getRuntimeModule() {\r
-            if(runtimeModule == null) {\r
-                if(compilationResult.didSucceed()) {\r
-                    Module module = compilationResult.getResult();\r
-                    RuntimeModuleMap parentModules = new RuntimeModuleMap();\r
-                    if(!moduleName.equals(Types.BUILTIN)) {\r
-                        parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)\r
-                                .getResult());\r
-                        Collection<ImportDeclaration> dependencies = module.getDependencies();\r
-                        THashMap<String, ModuleEntry> moduleEntries;\r
-                        try {\r
-                            moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);\r
-                        } catch (ImportFailureException e) {\r
-                            throw new InternalCompilerError(e);\r
-                        }\r
-                        for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())\r
-                            parentModules.add(m);\r
-                    }\r
-                    /*for(ImportDeclaration importAst : module.getDependencies()) {\r
-                        RuntimeModule parentModule =\r
-                                ModuleRepository.this.getRuntimeModule(importAst.moduleName)\r
-                                .getResult();\r
-                        if(parentModule != null)\r
-                            parentModules.add(parentModule);\r
-                    }*/\r
-                    RuntimeModule rm = new RuntimeModule(module, parentModules, source.getClassLoader());\r
-                    ModuleInitializer initializer = module.getModuleInitializer();\r
-                    if(initializer != null)\r
-                        try {\r
-                            initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());\r
-                        } catch (Exception e) {\r
-                            compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});\r
-                            e.printStackTrace();\r
-                        }\r
-                    runtimeModule = new Success<RuntimeModule>(rm); \r
-                }\r
-                else\r
-                    runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;\r
-            }\r
-            return runtimeModule;\r
-        }\r
-\r
-        public void dispose() {\r
-            if (listeners != null)\r
-                listeners.clear();\r
-            listeners = null;\r
-            source = null;\r
-            compilationResult = null;\r
-            if (runtimeModule != null) {\r
-                if (runtimeModule.didSucceed())\r
-                    runtimeModule.getResult().dispose();\r
-            }\r
-            runtimeModule = null;\r
-        }\r
-        \r
-        @Override\r
-        public String toString() {\r
-            return "ModuleEntry@" + moduleName + "@" + hashCode();\r
-        }\r
-    }\r
-    \r
-    public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {\r
-        this.parentRepository = parentRepository;\r
-        this.sourceRepository = sourceRepository;\r
-    }\r
-\r
-    public ModuleRepository(ModuleSourceRepository sourceRepository) {\r
-        this(null, sourceRepository);\r
-    }\r
-    \r
-    public Failable<Module> getModule(String moduleName, UpdateListener listener) {\r
-        return getModuleEntry(moduleName, listener).compilationResult;\r
-    }\r
-    \r
-    public Failable<Module> getModule(String moduleName) {\r
-        return getModule(moduleName, null);\r
-    }\r
-    \r
-    public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {\r
-        return getModuleEntry(moduleName, listener).getRuntimeModule();\r
-    }\r
-    \r
-    public Failable<RuntimeModule> getRuntimeModule(String moduleName) {\r
-        return getRuntimeModule(moduleName, null);\r
-    }\r
-    \r
-    private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {\r
-        /* It is deliberate that the following code does not try to prevent\r
-         * simultaneous compilation of the same module. This is because in\r
-         * some situations only certain thread trying compilation can succeed\r
-         * in it.\r
-         */\r
-        ModuleEntry entry = moduleCache.get(moduleName);\r
-        if(entry == null)\r
-            entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);\r
-        else\r
-            entry.addListener(listener);\r
-\r
-        if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)\r
-            return parentRepository.getModuleEntry(moduleName, listener);\r
-        else\r
-            return entry;\r
-    }\r
-    \r
-    private THashMap<String, ModuleEntry> getModuleEntries(\r
-            ImportDeclaration[] imports,\r
-            UpdateListener listener) throws ImportFailureException {\r
-        THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();\r
-        Collection<ImportFailure> failures = null;\r
-        \r
-        ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);\r
-        for(ImportDeclaration import_ : imports)\r
-            stack.add(import_);\r
-        while(!stack.isEmpty()) {\r
-            ImportDeclaration import_ = stack.remove(stack.size()-1);\r
-            if(!result.containsKey(import_.moduleName)) {\r
-                ModuleEntry entry = getModuleEntry(import_.moduleName, listener);\r
-                Failable<Module> compilationResult = entry.compilationResult;\r
-                if(compilationResult.didSucceed()) {\r
-                    result.put(import_.moduleName, entry);\r
-                    stack.addAll(compilationResult.getResult().getDependencies());\r
-                }\r
-                else {\r
-                    if(failures == null)\r
-                        failures = new ArrayList<ImportFailure>(2);\r
-                    failures.add(new ImportFailure(import_.location, import_.moduleName,\r
-                            compilationResult == DoesNotExist.INSTANCE\r
-                                    ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON\r
-                                    : ((Failure)compilationResult).errors));\r
-                }\r
-            }\r
-        }\r
-        \r
-        if(failures != null)\r
-            throw new ImportFailureException(failures);\r
-        \r
-        return result;\r
-    }\r
-\r
-    private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {\r
-        final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());\r
-        entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {\r
-            @Override\r
-            public boolean execute(String a, ModuleEntry b) {\r
-                result.put(a, b.compilationResult.getResult());\r
-                return true;\r
-            }\r
-        });\r
-        return result;\r
-    }\r
-    \r
-    private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {\r
-        final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());\r
-        entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {\r
-            @Override\r
-            public boolean execute(String a, ModuleEntry b) {\r
-                result.put(a, b.getRuntimeModule().getResult());\r
-                return true;\r
-            }\r
-        });\r
-        return result;\r
-    }\r
-    \r
-    public Environment createEnvironment(\r
-            ImportDeclaration[] imports,\r
-            UpdateListener listener) throws ImportFailureException {\r
-        THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);\r
-        THashMap<String, Module> moduleMap = mapEntriesToModules(entries);\r
-        return createEnvironment(moduleMap, imports);\r
-    }\r
-    \r
-    public Environment createEnvironment(\r
-            EnvironmentSpecification specification,\r
-            UpdateListener listener) throws ImportFailureException {\r
-        return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);\r
-    }\r
-    \r
-    public RuntimeEnvironment createRuntimeEnvironment(\r
-            EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {\r
-        return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);\r
-    }\r
-    \r
-    public RuntimeEnvironment createRuntimeEnvironment(\r
-            EnvironmentSpecification environmentSpecification,\r
-            ClassLoader parentClassLoader,\r
-            UpdateListener listener) throws ImportFailureException {\r
-        return createRuntimeEnvironment(\r
-                environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),\r
-                parentClassLoader,\r
-                listener);\r
-    }\r
-    \r
-    public RuntimeEnvironment createRuntimeEnvironment(\r
-            ImportDeclaration[] imports,\r
-            ClassLoader parentClassLoader,\r
-            UpdateListener listener) throws ImportFailureException {\r
-        THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);\r
-        THashMap<String, Module> moduleMap = mapEntriesToModules(entries);\r
-        Environment environment = createEnvironment(moduleMap, imports);\r
-        THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);\r
-        return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);\r
-    }\r
-    \r
-    private static Environment createEnvironment(THashMap<String, Module> moduleMap, \r
-            ImportDeclaration[] imports) {\r
-        NamespaceSpec spec = new NamespaceSpec();\r
-        for(ImportDeclaration import_ : imports)\r
-            if(import_.localName != null)\r
-                addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,\r
-                        NamespaceFilters.createFromSpec(import_.spec));\r
-        \r
-        return new ConcreteEnvironment(moduleMap, spec.toNamespace());\r
-    }\r
-    \r
-    private static void addToNamespace(THashMap<String, Module> moduleMap, \r
-            NamespaceSpec namespace, String moduleName, String localName,\r
-            NamespaceFilter filter) {\r
-        if(localName.isEmpty())\r
-            addToNamespace(moduleMap, namespace, moduleName, filter);\r
-        else\r
-            addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);\r
-    }\r
-    \r
-    private static void addToNamespace(THashMap<String, Module> moduleMap, \r
-            NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {\r
-        ModuleImport moduleImport = namespace.moduleMap.get(moduleName);\r
-        if(moduleImport == null) {\r
-            Module module = moduleMap.get(moduleName);\r
-            namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));\r
-            for(ImportDeclaration import_ : module.getDependencies())\r
-                if(import_.localName != null) {\r
-                    NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);\r
-                    if(import_.localName.equals(""))\r
-                        localFilter = NamespaceFilters.intersection(filter, localFilter);\r
-                    addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);\r
-                }\r
-        }\r
-        else if(!filter.isSubsetOf(moduleImport.filter)) {\r
-            moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);\r
-            for(ImportDeclaration import_ : moduleImport.module.getDependencies())\r
-                // We have to recheck only modules imported to this namespace\r
-                if("".equals(import_.localName)) {\r
-                    NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);\r
-                    localFilter = NamespaceFilters.intersection(filter, localFilter);\r
-                    addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);\r
-                }\r
-        }\r
-    }\r
-\r
-    public Object getValue(String moduleName, String valueName) throws ValueNotFound {\r
-        Failable<RuntimeModule> module = getRuntimeModule(moduleName);\r
-        if(module.didSucceed())\r
-            return module.getResult().getValue(valueName);\r
-        else if(module == DoesNotExist.INSTANCE)\r
-            throw new ValueNotFound("Didn't find module " + moduleName);\r
-        else\r
-            throw new ValueNotFound(((Failure)module).toString());\r
-    }\r
-\r
-    public Object getValue(String fullValueName) throws ValueNotFound {\r
-        int p = fullValueName.lastIndexOf('/');\r
-        if(p < 0)\r
-            throw new ValueNotFound(fullValueName + " is not a valid full value name.");\r
-        return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));\r
-    }\r
-\r
-    public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {\r
-        Failable<Module> module = getModule(moduleName);\r
-        if(module.didSucceed()) {\r
-            SCLValue value = module.getResult().getValue(valueName);\r
-            if(value == null)\r
-                throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");\r
-            return value;\r
-        }\r
-        else if(module == DoesNotExist.INSTANCE)\r
-            throw new ValueNotFound("Didn't find module " + moduleName);\r
-        else\r
-            throw new ValueNotFound(((Failure)module).toString());\r
-    }\r
-    \r
-    public SCLValue getValueRef(String fullValueName) throws ValueNotFound {\r
-        int p = fullValueName.lastIndexOf('/');\r
-        if(p < 0)\r
-            throw new ValueNotFound(fullValueName + " is not a valid full value name.");\r
-        return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));\r
-    }\r
-    \r
-    public ModuleSourceRepository getSourceRepository() {\r
-        return sourceRepository;\r
-    }\r
-    \r
-    public String getDocumentation(String documentationName) {\r
-        String documentation = sourceRepository.getDocumentation(documentationName);\r
-        if(documentation == null && parentRepository != null)\r
-            return parentRepository.getDocumentation(documentationName);\r
-        return documentation;\r
-    }\r
-    \r
-    public void flush() {\r
-        if (parentRepository != null)\r
-            parentRepository.flush();\r
-        if (moduleCache != null) {\r
-            for (ModuleEntry entry : moduleCache.values()) {\r
-                entry.dispose();\r
-            }\r
-            moduleCache.clear();\r
-        }\r
-        moduleCache = null;\r
-    }\r
-\r
-    public Map<String, Module> getModules() {\r
-        Map<String, Module> result = new HashMap<>(moduleCache.size()); \r
-        for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {\r
-            ModuleEntry moduleEntry = entry.getValue();\r
-            if (moduleEntry.compilationResult.didSucceed()) {\r
-                result.put(entry.getKey(), moduleEntry.compilationResult.getResult());\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public ModuleCompilationOptionsAdvisor getAdvisor() {\r
-        return advisor;\r
-    }\r
-\r
-    public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {\r
-        this.advisor = advisor;\r
-    }\r
-\r
-}\r
+package org.simantics.scl.compiler.module.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
+import org.simantics.scl.compiler.compilation.CompilationContext;
+import org.simantics.scl.compiler.elaboration.modules.SCLValue;
+import org.simantics.scl.compiler.environment.ConcreteEnvironment;
+import org.simantics.scl.compiler.environment.EmptyEnvironment;
+import org.simantics.scl.compiler.environment.Environment;
+import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
+import org.simantics.scl.compiler.environment.NamespaceSpec;
+import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
+import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
+import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
+import org.simantics.scl.compiler.errors.CompilationError;
+import org.simantics.scl.compiler.errors.DoesNotExist;
+import org.simantics.scl.compiler.errors.Failable;
+import org.simantics.scl.compiler.errors.Failure;
+import org.simantics.scl.compiler.errors.Locations;
+import org.simantics.scl.compiler.errors.Success;
+import org.simantics.scl.compiler.module.ImportDeclaration;
+import org.simantics.scl.compiler.module.Module;
+import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
+import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
+import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
+import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
+import org.simantics.scl.compiler.runtime.RuntimeModule;
+import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
+import org.simantics.scl.compiler.source.ModuleSource;
+import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
+import org.simantics.scl.compiler.top.ModuleInitializer;
+import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
+import org.simantics.scl.compiler.top.ValueNotFound;
+import org.simantics.scl.compiler.types.Types;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectLongHashMap;
+import gnu.trove.procedure.TObjectObjectProcedure;
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * Manages compilation and caching of SCL modules.
+ * 
+ * @author Hannu Niemist&ouml;
+ */
+public class ModuleRepository {
+    private final ModuleRepository parentRepository;
+    private final ModuleSourceRepository sourceRepository;
+    private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
+    
+    private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
+    
+    private ModuleCompilationOptionsAdvisor advisor = null;
+    
+    private static void beginModuleCompilation(String moduleName) {
+        THashSet<String> set = PENDING_MODULES.get();
+        if(set == null) {
+            set = new THashSet<String>();
+            PENDING_MODULES.set(set);
+        }
+        if(!set.add(moduleName))
+            throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
+    }
+    
+    private static void finishModuleCompilation(String moduleName) {
+        PENDING_MODULES.get().remove(moduleName);
+    }
+    
+    private class ModuleEntry extends UpdateListener implements Observable {
+        final String moduleName;
+        THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
+        
+        ModuleSource source;
+        Failable<Module> compilationResult;
+        Failable<RuntimeModule> runtimeModule; // created lazily
+
+        public ModuleEntry(String moduleName) {
+            this.moduleName = moduleName;
+        }
+        
+        synchronized void addListener(UpdateListener listener) {
+            if(listener == null || listeners == null)
+                return;
+            listeners.add(listener);
+            listener.addObservable(this);
+        }
+
+        public synchronized void removeListener(UpdateListener listener) {
+            if (listeners == null)
+                return;
+            listeners.remove(listener);
+        }
+        
+        @Override
+        public void notifyAboutUpdate() {
+            ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
+            notifyAboutUpdate(externalListeners);
+            for(UpdateListener listener : externalListeners)
+                listener.notifyAboutUpdate();
+        }
+
+        synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
+            stopListening();
+            if (listeners == null)
+                return;
+            if(moduleCache.get(moduleName) == this) {
+                moduleCache.remove(moduleName);
+                if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
+                    System.out.println("Invalidate " + moduleName);
+                    for(UpdateListener l : listeners)
+                        System.out.println("    " + l);
+                }
+                THashSet<UpdateListener> listenersCopy = listeners;
+                listeners = null;
+                for(UpdateListener l : listenersCopy)
+                    l.stopListening();
+                for(UpdateListener l : listenersCopy)
+                    if(l instanceof ModuleEntry)
+                        ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
+                    else {
+                        externalListeners.add(l);
+                    }
+            }
+        }
+
+        private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
+            source = sourceRepository.getModuleSource(moduleName, this);
+            
+            if(source == null)
+                compilationResult = DoesNotExist.getInstance();
+            else {
+                if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
+                    System.out.println("Compile " + source);
+                beginModuleCompilation(moduleName);
+                compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
+                finishModuleCompilation(moduleName);
+            }
+        
+            ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
+            if(oldEntry != null) {
+                oldEntry.addListener(listener);
+                return oldEntry;
+            }
+            
+            addListener(listener);
+            return this;
+        }
+        
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public synchronized Failable<RuntimeModule> getRuntimeModule() {
+            if(runtimeModule == null) {
+                if(compilationResult.didSucceed()) {
+                    Module module = compilationResult.getResult();
+                    RuntimeModuleMap parentModules = new RuntimeModuleMap();
+                    if(!moduleName.equals(Types.BUILTIN)) {
+                        parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
+                                .getResult());
+                        Collection<ImportDeclaration> dependencies = module.getDependencies();
+                        THashMap<String, ModuleEntry> moduleEntries;
+                        try {
+                            moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null, false);
+                        } catch (ImportFailureException e) {
+                            throw new InternalCompilerError(e);
+                        }
+                        for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
+                            parentModules.add(m);
+                    }
+                    /*for(ImportDeclaration importAst : module.getDependencies()) {
+                        RuntimeModule parentModule =
+                                ModuleRepository.this.getRuntimeModule(importAst.moduleName)
+                                .getResult();
+                        if(parentModule != null)
+                            parentModules.add(parentModule);
+                    }*/
+                    RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
+                    ModuleInitializer initializer = module.getModuleInitializer();
+                    if(initializer != null)
+                        try {
+                            initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
+                        } catch (Exception e) {
+                            compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
+                            e.printStackTrace();
+                        }
+                    runtimeModule = new Success<RuntimeModule>(rm); 
+                }
+                else
+                    runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
+            }
+            return runtimeModule;
+        }
+
+        public synchronized void dispose() {
+            if (listeners != null)
+                listeners.clear();
+            listeners = null;
+            stopListening();
+            source = null;
+            compilationResult = null;
+            if (runtimeModule != null) {
+                if (runtimeModule.didSucceed())
+                    runtimeModule.getResult().dispose();
+            }
+            runtimeModule = null;
+        }
+        
+        @Override
+        public String toString() {
+            return "ModuleEntry@" + moduleName + "@" + hashCode();
+        }
+    }
+    
+    public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
+        this.parentRepository = parentRepository;
+        this.sourceRepository = sourceRepository;
+    }
+
+    public ModuleRepository(ModuleSourceRepository sourceRepository) {
+        this(null, sourceRepository);
+    }
+    
+    public Failable<Module> getModule(String moduleName, UpdateListener listener) {
+        return getModuleEntry(moduleName, listener).compilationResult;
+    }
+    
+    public Failable<Module> getModule(String moduleName) {
+        return getModule(moduleName, null);
+    }
+    
+    public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
+        return getModuleEntry(moduleName, listener).getRuntimeModule();
+    }
+    
+    public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
+        return getRuntimeModule(moduleName, null);
+    }
+
+    private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
+        /* It is deliberate that the following code does not try to prevent
+         * simultaneous compilation of the same module. This is because in
+         * some situations only certain thread trying compilation can succeed
+         * in it.
+         */
+        ModuleEntry entry = moduleCache.get(moduleName);
+        if(entry == null)
+            entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
+        else
+            entry.addListener(listener);
+
+        if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
+            return parentRepository.getModuleEntry(moduleName, listener);
+        else
+            return entry;
+    }
+    
+    private THashMap<String, ModuleEntry> getModuleEntries(
+            CompilationContext compilationContext,
+            ImportDeclaration[] imports,
+            UpdateListener listener,
+            boolean robustly) throws ImportFailureException {
+        THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
+        Collection<ImportFailure> failures = null;
+        
+        TObjectLongHashMap<String> originalImports = new TObjectLongHashMap<String>(); 
+        ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
+        for(ImportDeclaration import_ : imports) {
+            stack.add(import_);
+            originalImports.put(import_.moduleName, import_.location);
+        }
+        while(!stack.isEmpty()) {
+            ImportDeclaration import_ = stack.remove(stack.size()-1);
+            if(!result.containsKey(import_.moduleName)) {
+                boolean originalImport = originalImports.contains(import_.moduleName);
+                ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
+                Failable<Module> compilationResult = entry.compilationResult;
+                if(compilationResult.didSucceed()) {
+                    result.put(import_.moduleName, entry);
+                    stack.addAll(compilationResult.getResult().getDependencies());
+                    if(originalImport) {
+                        String deprecation = compilationResult.getResult().getDeprecation();
+                        if(deprecation != null && compilationContext != null) {
+                            long location = originalImport ? originalImports.get(import_.moduleName) : Locations.NO_LOCATION;
+                            compilationContext.errorLog.logWarning(location, "Deprecated module " + import_.moduleName   + (deprecation.isEmpty() ? "." : ": " + deprecation));
+                        }
+                    }
+                }
+                else {
+                    if(failures == null)
+                        failures = new ArrayList<ImportFailure>(2);
+                    failures.add(new ImportFailure(import_.location, import_.moduleName,
+                            compilationResult == DoesNotExist.INSTANCE
+                                    ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
+                                    : ((Failure)compilationResult).errors));
+                }
+            }
+        }
+        
+        if(failures != null && !robustly)
+            throw new ImportFailureException(failures);
+        
+        return result;
+    }
+
+    private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
+        final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
+        entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
+            @Override
+            public boolean execute(String a, ModuleEntry b) {
+                result.put(a, b.compilationResult.getResult());
+                return true;
+            }
+        });
+        return result;
+    }
+    
+    private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
+        final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
+        entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
+            @Override
+            public boolean execute(String a, ModuleEntry b) {
+                result.put(a, b.getRuntimeModule().getResult());
+                return true;
+            }
+        });
+        return result;
+    }
+    
+    public Environment createEnvironment(
+            ImportDeclaration[] imports,
+            UpdateListener listener) throws ImportFailureException {
+        return createEnvironment(null, imports, listener);
+    }
+    
+    public Environment createEnvironment(
+            CompilationContext compilationContext,
+            ImportDeclaration[] imports,
+            UpdateListener listener) throws ImportFailureException {
+        THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, false);
+        THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
+        return createEnvironment(moduleMap, imports);
+    }
+    
+    public Environment createEnvironmentRobustly(
+            CompilationContext compilationContext,
+            ImportDeclaration[] imports,
+            UpdateListener listener) {
+        try {
+            THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, true);
+            THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
+            return createEnvironment(moduleMap, imports);
+        } catch(ImportFailureException e) {
+            // Should not happen because of robust flag
+            return EmptyEnvironment.INSTANCE;
+        }
+    }
+    
+    public Environment createEnvironment(
+            EnvironmentSpecification specification,
+            UpdateListener listener) throws ImportFailureException {
+        return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
+    }
+    
+    public RuntimeEnvironment createRuntimeEnvironment(
+            EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
+        return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
+    }
+    
+    public RuntimeEnvironment createRuntimeEnvironment(EnvironmentSpecification environmentSpecification) throws ImportFailureException {
+        return createRuntimeEnvironment(environmentSpecification, getClass().getClassLoader());
+    }
+    
+    public RuntimeEnvironment createRuntimeEnvironment(
+            EnvironmentSpecification environmentSpecification,
+            ClassLoader parentClassLoader,
+            UpdateListener listener) throws ImportFailureException {
+        return createRuntimeEnvironment(
+                environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
+                parentClassLoader,
+                listener);
+    }
+    
+    public RuntimeEnvironment createRuntimeEnvironment(
+            ImportDeclaration[] imports,
+            ClassLoader parentClassLoader,
+            UpdateListener listener) throws ImportFailureException {
+        THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener, false);
+        THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
+        Environment environment = createEnvironment(moduleMap, imports);
+        THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
+        return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
+    }
+    
+    private static Environment createEnvironment(
+            THashMap<String, Module> moduleMap, 
+            ImportDeclaration[] imports) {
+        NamespaceSpec spec = new NamespaceSpec();
+        for(ImportDeclaration import_ : imports)
+            if(import_.localName != null)
+                addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
+                        NamespaceFilters.createFromSpec(import_.spec));
+        
+        return new ConcreteEnvironment(moduleMap, spec.toNamespace());
+    }
+    
+    private static void addToNamespace(THashMap<String, Module> moduleMap, 
+            NamespaceSpec namespace, String moduleName, String localName,
+            NamespaceFilter filter) {
+        if(localName.isEmpty())
+            addToNamespace(moduleMap, namespace, moduleName, filter);
+        else
+            addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
+    }
+    
+    private static void addToNamespace(THashMap<String, Module> moduleMap, 
+            NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
+        ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
+        if(moduleImport == null) {
+            Module module = moduleMap.get(moduleName);
+            namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
+            for(ImportDeclaration import_ : module.getDependencies())
+                if(import_.localName != null) {
+                    NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
+                    if(import_.localName.equals(""))
+                        localFilter = NamespaceFilters.intersection(filter, localFilter);
+                    addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
+                }
+        }
+        else if(!filter.isSubsetOf(moduleImport.filter)) {
+            moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
+            for(ImportDeclaration import_ : moduleImport.module.getDependencies())
+                // We have to recheck only modules imported to this namespace
+                if("".equals(import_.localName)) {
+                    NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
+                    localFilter = NamespaceFilters.intersection(filter, localFilter);
+                    addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
+                }
+        }
+    }
+
+    public Object getValue(String moduleName, String valueName) throws ValueNotFound {
+        Failable<RuntimeModule> module = getRuntimeModule(moduleName);
+        if(module.didSucceed())
+            return module.getResult().getValue(valueName);
+        else if(module == DoesNotExist.INSTANCE)
+            throw new ValueNotFound("Didn't find module " + moduleName);
+        else
+            throw new ValueNotFound(((Failure)module).toString());
+    }
+
+    public Object getValue(String fullValueName) throws ValueNotFound {
+        int p = fullValueName.lastIndexOf('/');
+        if(p < 0)
+            throw new ValueNotFound(fullValueName + " is not a valid full value name.");
+        return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
+    }
+
+    public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
+        Failable<Module> module = getModule(moduleName);
+        if(module.didSucceed()) {
+            SCLValue value = module.getResult().getValue(valueName);
+            if(value == null)
+                throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
+            return value;
+        }
+        else if(module == DoesNotExist.INSTANCE)
+            throw new ValueNotFound("Didn't find module " + moduleName);
+        else
+            throw new ValueNotFound(((Failure)module).toString());
+    }
+    
+    public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
+        int p = fullValueName.lastIndexOf('/');
+        if(p < 0)
+            throw new ValueNotFound(fullValueName + " is not a valid full value name.");
+        return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
+    }
+    
+    public ModuleSourceRepository getSourceRepository() {
+        return sourceRepository;
+    }
+    
+    public String getDocumentation(String documentationName) {
+        String documentation = sourceRepository.getDocumentation(documentationName);
+        if(documentation == null && parentRepository != null)
+            return parentRepository.getDocumentation(documentationName);
+        return documentation;
+    }
+    
+    public void flush() {
+        if (parentRepository != null)
+            parentRepository.flush();
+        if (moduleCache != null) {
+            for (ModuleEntry entry : moduleCache.values()) {
+                entry.dispose();
+            }
+            moduleCache.clear();
+        }
+        moduleCache = null;
+    }
+
+    public Map<String, Module> getModules() {
+        Map<String, Module> result = new HashMap<>(moduleCache.size()); 
+        for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
+            ModuleEntry moduleEntry = entry.getValue();
+            if (moduleEntry.compilationResult.didSucceed()) {
+                result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
+            }
+        }
+        return result;
+    }
+
+    public ModuleCompilationOptionsAdvisor getAdvisor() {
+        return advisor;
+    }
+
+    public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
+        this.advisor = advisor;
+    }
+
+}
  
\ No newline at end of file