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.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import org.simantics.scl.compiler.common.exceptions.InternalCompilerError; import org.simantics.scl.compiler.elaboration.modules.SCLValue; import org.simantics.scl.compiler.environment.ConcreteEnvironment; 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.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.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.procedure.TObjectObjectProcedure; import gnu.trove.set.hash.THashSet; /** * Manages compilation and caching of SCL modules. * * @author Hannu Niemistö */ public class ModuleRepository { private final ModuleRepository parentRepository; private final ModuleSourceRepository sourceRepository; private ConcurrentHashMap moduleCache = new ConcurrentHashMap(); private static final ThreadLocal> PENDING_MODULES = new ThreadLocal>(); private ModuleCompilationOptionsAdvisor advisor = null; private static void beginModuleCompilation(String moduleName) { THashSet set = PENDING_MODULES.get(); if(set == null) { set = new THashSet(); 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 implements UpdateListener { final String moduleName; WeakHashMap listeners = new WeakHashMap(); ModuleSource source; Failable compilationResult; Failable runtimeModule; // created lazily public ModuleEntry(String moduleName) { this.moduleName = moduleName; } synchronized void addListener(UpdateListener listener) { if(listener != null) listeners.put(listener, null); } @Override public void notifyAboutUpdate() { if (listeners == null) return; ArrayList externalListeners = new ArrayList(); notifyAboutUpdate(externalListeners); for(UpdateListener listener : externalListeners) listener.notifyAboutUpdate(); } synchronized void notifyAboutUpdate(ArrayList externalListeners) { if(moduleCache.get(moduleName) == this) { moduleCache.remove(moduleName); if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) { System.out.println("Invalidate " + moduleName); for(UpdateListener l : listeners.keySet()) System.out.println(" " + l); } for(UpdateListener l : listeners.keySet()) 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 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 dependencies = module.getDependencies(); THashMap moduleEntries; try { moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null); } 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, source.getClassLoader()); 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(rm); } else runtimeModule = (Failable)(Failable)compilationResult; } return runtimeModule; } public void dispose() { if (listeners != null) listeners.clear(); listeners = null; 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 getModule(String moduleName, UpdateListener listener) { return getModuleEntry(moduleName, listener).compilationResult; } public Failable getModule(String moduleName) { return getModule(moduleName, null); } public Failable getRuntimeModule(String moduleName, UpdateListener listener) { return getModuleEntry(moduleName, listener).getRuntimeModule(); } public Failable 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 getModuleEntries( ImportDeclaration[] imports, UpdateListener listener) throws ImportFailureException { THashMap result = new THashMap(); Collection failures = null; ArrayList stack = new ArrayList(imports.length); for(ImportDeclaration import_ : imports) stack.add(import_); while(!stack.isEmpty()) { ImportDeclaration import_ = stack.remove(stack.size()-1); if(!result.containsKey(import_.moduleName)) { ModuleEntry entry = getModuleEntry(import_.moduleName, listener); Failable compilationResult = entry.compilationResult; if(compilationResult.didSucceed()) { result.put(import_.moduleName, entry); stack.addAll(compilationResult.getResult().getDependencies()); } else { if(failures == null) failures = new ArrayList(2); failures.add(new ImportFailure(import_.location, import_.moduleName, compilationResult == DoesNotExist.INSTANCE ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON : ((Failure)compilationResult).errors)); } } } if(failures != null) throw new ImportFailureException(failures); return result; } private static THashMap mapEntriesToModules(THashMap entries) { final THashMap result = new THashMap(entries.size()); entries.forEachEntry(new TObjectObjectProcedure() { @Override public boolean execute(String a, ModuleEntry b) { result.put(a, b.compilationResult.getResult()); return true; } }); return result; } private static THashMap mapEntriesToRuntimeModules(THashMap entries) { final THashMap result = new THashMap(entries.size()); entries.forEachEntry(new TObjectObjectProcedure() { @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 { THashMap entries = getModuleEntries(imports, listener); THashMap moduleMap = mapEntriesToModules(entries); return createEnvironment(moduleMap, imports); } 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, 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 entries = getModuleEntries(imports, listener); THashMap moduleMap = mapEntriesToModules(entries); Environment environment = createEnvironment(moduleMap, imports); THashMap runtimeModuleMap = mapEntriesToRuntimeModules(entries); return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap); } private static Environment createEnvironment(THashMap 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 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 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 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 = 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 getModules() { Map result = new HashMap<>(moduleCache.size()); for (Map.Entry 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; } }