1 package org.simantics.scl.compiler.module.repository;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
7 import java.util.concurrent.ConcurrentHashMap;
9 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
10 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
11 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
12 import org.simantics.scl.compiler.environment.Environment;
13 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
14 import org.simantics.scl.compiler.environment.NamespaceSpec;
15 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
16 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
17 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
18 import org.simantics.scl.compiler.errors.CompilationError;
19 import org.simantics.scl.compiler.errors.DoesNotExist;
20 import org.simantics.scl.compiler.errors.Failable;
21 import org.simantics.scl.compiler.errors.Failure;
22 import org.simantics.scl.compiler.errors.Success;
23 import org.simantics.scl.compiler.module.ImportDeclaration;
24 import org.simantics.scl.compiler.module.Module;
25 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
26 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
27 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
28 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
29 import org.simantics.scl.compiler.runtime.RuntimeModule;
30 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
31 import org.simantics.scl.compiler.source.ModuleSource;
32 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
33 import org.simantics.scl.compiler.top.ModuleInitializer;
34 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
35 import org.simantics.scl.compiler.top.ValueNotFound;
36 import org.simantics.scl.compiler.types.Types;
38 import gnu.trove.map.hash.THashMap;
39 import gnu.trove.procedure.TObjectObjectProcedure;
40 import gnu.trove.set.hash.THashSet;
43 * Manages compilation and caching of SCL modules.
45 * @author Hannu Niemistö
47 public class ModuleRepository {
48 private final ModuleRepository parentRepository;
49 private final ModuleSourceRepository sourceRepository;
50 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
52 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
54 private ModuleCompilationOptionsAdvisor advisor = null;
56 private static void beginModuleCompilation(String moduleName) {
57 THashSet<String> set = PENDING_MODULES.get();
59 set = new THashSet<String>();
60 PENDING_MODULES.set(set);
62 if(!set.add(moduleName))
63 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
66 private static void finishModuleCompilation(String moduleName) {
67 PENDING_MODULES.get().remove(moduleName);
70 private class ModuleEntry extends UpdateListener implements Observable {
71 final String moduleName;
72 THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
75 Failable<Module> compilationResult;
76 Failable<RuntimeModule> runtimeModule; // created lazily
78 public ModuleEntry(String moduleName) {
79 this.moduleName = moduleName;
82 synchronized void addListener(UpdateListener listener) {
83 if(listener == null || listeners == null)
85 listeners.add(listener);
86 listener.addObservable(this);
89 public synchronized void removeListener(UpdateListener listener) {
90 if (listeners == null)
92 listeners.remove(listener);
96 public void notifyAboutUpdate() {
97 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
98 notifyAboutUpdate(externalListeners);
99 for(UpdateListener listener : externalListeners)
100 listener.notifyAboutUpdate();
103 synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
105 if (listeners == null)
107 if(moduleCache.get(moduleName) == this) {
108 moduleCache.remove(moduleName);
109 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
110 System.out.println("Invalidate " + moduleName);
111 for(UpdateListener l : listeners)
112 System.out.println(" " + l);
114 THashSet<UpdateListener> listenersCopy = listeners;
116 for(UpdateListener l : listenersCopy)
118 for(UpdateListener l : listenersCopy)
119 if(l instanceof ModuleEntry)
120 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
122 externalListeners.add(l);
127 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
128 source = sourceRepository.getModuleSource(moduleName, this);
131 compilationResult = DoesNotExist.getInstance();
133 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
134 System.out.println("Compile " + source);
135 beginModuleCompilation(moduleName);
136 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
137 finishModuleCompilation(moduleName);
140 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
141 if(oldEntry != null) {
142 oldEntry.addListener(listener);
146 addListener(listener);
150 @SuppressWarnings({ "rawtypes", "unchecked" })
151 public synchronized Failable<RuntimeModule> getRuntimeModule() {
152 if(runtimeModule == null) {
153 if(compilationResult.didSucceed()) {
154 Module module = compilationResult.getResult();
155 RuntimeModuleMap parentModules = new RuntimeModuleMap();
156 if(!moduleName.equals(Types.BUILTIN)) {
157 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
159 Collection<ImportDeclaration> dependencies = module.getDependencies();
160 THashMap<String, ModuleEntry> moduleEntries;
162 moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);
163 } catch (ImportFailureException e) {
164 throw new InternalCompilerError(e);
166 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
167 parentModules.add(m);
169 /*for(ImportDeclaration importAst : module.getDependencies()) {
170 RuntimeModule parentModule =
171 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
173 if(parentModule != null)
174 parentModules.add(parentModule);
176 RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
177 ModuleInitializer initializer = module.getModuleInitializer();
178 if(initializer != null)
180 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
181 } catch (Exception e) {
182 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
185 runtimeModule = new Success<RuntimeModule>(rm);
188 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
190 return runtimeModule;
193 public synchronized void dispose() {
194 if (listeners != null)
199 compilationResult = null;
200 if (runtimeModule != null) {
201 if (runtimeModule.didSucceed())
202 runtimeModule.getResult().dispose();
204 runtimeModule = null;
208 public String toString() {
209 return "ModuleEntry@" + moduleName + "@" + hashCode();
213 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
214 this.parentRepository = parentRepository;
215 this.sourceRepository = sourceRepository;
218 public ModuleRepository(ModuleSourceRepository sourceRepository) {
219 this(null, sourceRepository);
222 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
223 return getModuleEntry(moduleName, listener).compilationResult;
226 public Failable<Module> getModule(String moduleName) {
227 return getModule(moduleName, null);
230 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
231 return getModuleEntry(moduleName, listener).getRuntimeModule();
234 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
235 return getRuntimeModule(moduleName, null);
238 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
239 /* It is deliberate that the following code does not try to prevent
240 * simultaneous compilation of the same module. This is because in
241 * some situations only certain thread trying compilation can succeed
244 ModuleEntry entry = moduleCache.get(moduleName);
246 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
248 entry.addListener(listener);
250 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
251 return parentRepository.getModuleEntry(moduleName, listener);
256 private THashMap<String, ModuleEntry> getModuleEntries(
257 ImportDeclaration[] imports,
258 UpdateListener listener) throws ImportFailureException {
259 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
260 Collection<ImportFailure> failures = null;
262 THashSet<String> originalImports = new THashSet<String>();
263 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
264 for(ImportDeclaration import_ : imports) {
266 originalImports.add(import_.moduleName);
268 while(!stack.isEmpty()) {
269 ImportDeclaration import_ = stack.remove(stack.size()-1);
270 if(!result.containsKey(import_.moduleName)) {
271 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImports.contains(import_.moduleName) ? listener : null);
272 Failable<Module> compilationResult = entry.compilationResult;
273 if(compilationResult.didSucceed()) {
274 result.put(import_.moduleName, entry);
275 stack.addAll(compilationResult.getResult().getDependencies());
279 failures = new ArrayList<ImportFailure>(2);
280 failures.add(new ImportFailure(import_.location, import_.moduleName,
281 compilationResult == DoesNotExist.INSTANCE
282 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
283 : ((Failure)compilationResult).errors));
289 throw new ImportFailureException(failures);
294 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
295 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
296 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
298 public boolean execute(String a, ModuleEntry b) {
299 result.put(a, b.compilationResult.getResult());
306 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
307 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
308 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
310 public boolean execute(String a, ModuleEntry b) {
311 result.put(a, b.getRuntimeModule().getResult());
318 public Environment createEnvironment(
319 ImportDeclaration[] imports,
320 UpdateListener listener) throws ImportFailureException {
321 THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
322 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
323 return createEnvironment(moduleMap, imports);
326 public Environment createEnvironment(
327 EnvironmentSpecification specification,
328 UpdateListener listener) throws ImportFailureException {
329 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
332 public RuntimeEnvironment createRuntimeEnvironment(
333 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
334 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
337 public RuntimeEnvironment createRuntimeEnvironment(
338 EnvironmentSpecification environmentSpecification,
339 ClassLoader parentClassLoader,
340 UpdateListener listener) throws ImportFailureException {
341 return createRuntimeEnvironment(
342 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
347 public RuntimeEnvironment createRuntimeEnvironment(
348 ImportDeclaration[] imports,
349 ClassLoader parentClassLoader,
350 UpdateListener listener) throws ImportFailureException {
351 THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
352 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
353 Environment environment = createEnvironment(moduleMap, imports);
354 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
355 return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
358 private static Environment createEnvironment(THashMap<String, Module> moduleMap,
359 ImportDeclaration[] imports) {
360 NamespaceSpec spec = new NamespaceSpec();
361 for(ImportDeclaration import_ : imports)
362 if(import_.localName != null)
363 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
364 NamespaceFilters.createFromSpec(import_.spec));
366 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
369 private static void addToNamespace(THashMap<String, Module> moduleMap,
370 NamespaceSpec namespace, String moduleName, String localName,
371 NamespaceFilter filter) {
372 if(localName.isEmpty())
373 addToNamespace(moduleMap, namespace, moduleName, filter);
375 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
378 private static void addToNamespace(THashMap<String, Module> moduleMap,
379 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
380 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
381 if(moduleImport == null) {
382 Module module = moduleMap.get(moduleName);
383 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
384 for(ImportDeclaration import_ : module.getDependencies())
385 if(import_.localName != null) {
386 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
387 if(import_.localName.equals(""))
388 localFilter = NamespaceFilters.intersection(filter, localFilter);
389 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
392 else if(!filter.isSubsetOf(moduleImport.filter)) {
393 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
394 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
395 // We have to recheck only modules imported to this namespace
396 if("".equals(import_.localName)) {
397 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
398 localFilter = NamespaceFilters.intersection(filter, localFilter);
399 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
404 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
405 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
406 if(module.didSucceed())
407 return module.getResult().getValue(valueName);
408 else if(module == DoesNotExist.INSTANCE)
409 throw new ValueNotFound("Didn't find module " + moduleName);
411 throw new ValueNotFound(((Failure)module).toString());
414 public Object getValue(String fullValueName) throws ValueNotFound {
415 int p = fullValueName.lastIndexOf('/');
417 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
418 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
421 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
422 Failable<Module> module = getModule(moduleName);
423 if(module.didSucceed()) {
424 SCLValue value = module.getResult().getValue(valueName);
426 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
429 else if(module == DoesNotExist.INSTANCE)
430 throw new ValueNotFound("Didn't find module " + moduleName);
432 throw new ValueNotFound(((Failure)module).toString());
435 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
436 int p = fullValueName.lastIndexOf('/');
438 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
439 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
442 public ModuleSourceRepository getSourceRepository() {
443 return sourceRepository;
446 public String getDocumentation(String documentationName) {
447 String documentation = sourceRepository.getDocumentation(documentationName);
448 if(documentation == null && parentRepository != null)
449 return parentRepository.getDocumentation(documentationName);
450 return documentation;
453 public void flush() {
454 if (parentRepository != null)
455 parentRepository.flush();
456 if (moduleCache != null) {
457 for (ModuleEntry entry : moduleCache.values()) {
465 public Map<String, Module> getModules() {
466 Map<String, Module> result = new HashMap<>(moduleCache.size());
467 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
468 ModuleEntry moduleEntry = entry.getValue();
469 if (moduleEntry.compilationResult.didSucceed()) {
470 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
476 public ModuleCompilationOptionsAdvisor getAdvisor() {
480 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
481 this.advisor = advisor;