+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