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.compilation.CompilationContext;
11 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
12 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
13 import org.simantics.scl.compiler.environment.EmptyEnvironment;
14 import org.simantics.scl.compiler.environment.Environment;
15 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
16 import org.simantics.scl.compiler.environment.NamespaceSpec;
17 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
18 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
19 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
20 import org.simantics.scl.compiler.errors.CompilationError;
21 import org.simantics.scl.compiler.errors.DoesNotExist;
22 import org.simantics.scl.compiler.errors.Failable;
23 import org.simantics.scl.compiler.errors.Failure;
24 import org.simantics.scl.compiler.errors.Locations;
25 import org.simantics.scl.compiler.errors.Success;
26 import org.simantics.scl.compiler.module.ImportDeclaration;
27 import org.simantics.scl.compiler.module.Module;
28 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
29 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
30 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
31 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
32 import org.simantics.scl.compiler.runtime.RuntimeModule;
33 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
34 import org.simantics.scl.compiler.source.ModuleSource;
35 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
36 import org.simantics.scl.compiler.top.ModuleInitializer;
37 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
38 import org.simantics.scl.compiler.top.ValueNotFound;
39 import org.simantics.scl.compiler.types.Types;
41 import gnu.trove.map.hash.THashMap;
42 import gnu.trove.map.hash.TObjectLongHashMap;
43 import gnu.trove.procedure.TObjectObjectProcedure;
44 import gnu.trove.set.hash.THashSet;
47 * Manages compilation and caching of SCL modules.
49 * @author Hannu Niemistö
51 public class ModuleRepository {
52 private final ModuleRepository parentRepository;
53 private final ModuleSourceRepository sourceRepository;
54 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
56 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
58 private ModuleCompilationOptionsAdvisor advisor = null;
60 private static void beginModuleCompilation(String moduleName) {
61 THashSet<String> set = PENDING_MODULES.get();
63 set = new THashSet<String>();
64 PENDING_MODULES.set(set);
66 if(!set.add(moduleName))
67 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
70 private static void finishModuleCompilation(String moduleName) {
71 PENDING_MODULES.get().remove(moduleName);
74 private class ModuleEntry extends UpdateListener implements Observable {
75 final String moduleName;
76 THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
79 Failable<Module> compilationResult;
80 Failable<RuntimeModule> runtimeModule; // created lazily
82 public ModuleEntry(String moduleName) {
83 this.moduleName = moduleName;
86 synchronized void addListener(UpdateListener listener) {
87 if(listener == null || listeners == null)
89 listeners.add(listener);
90 listener.addObservable(this);
93 public synchronized void removeListener(UpdateListener listener) {
94 if (listeners == null)
96 listeners.remove(listener);
100 public void notifyAboutUpdate() {
101 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
102 notifyAboutUpdate(externalListeners);
103 for(UpdateListener listener : externalListeners)
104 listener.notifyAboutUpdate();
107 synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
109 if (listeners == null)
111 if(moduleCache.get(moduleName) == this) {
112 moduleCache.remove(moduleName);
113 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
114 System.out.println("Invalidate " + moduleName);
115 for(UpdateListener l : listeners)
116 System.out.println(" " + l);
118 THashSet<UpdateListener> listenersCopy = listeners;
120 for(UpdateListener l : listenersCopy)
122 for(UpdateListener l : listenersCopy)
123 if(l instanceof ModuleEntry)
124 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
126 externalListeners.add(l);
131 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
132 source = sourceRepository.getModuleSource(moduleName, this);
135 compilationResult = DoesNotExist.getInstance();
137 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
138 System.out.println("Compile " + source);
139 beginModuleCompilation(moduleName);
140 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
141 finishModuleCompilation(moduleName);
144 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
145 if(oldEntry != null) {
146 oldEntry.addListener(listener);
150 addListener(listener);
154 @SuppressWarnings({ "rawtypes", "unchecked" })
155 public synchronized Failable<RuntimeModule> getRuntimeModule() {
156 if(runtimeModule == null) {
157 if(compilationResult.didSucceed()) {
158 Module module = compilationResult.getResult();
159 RuntimeModuleMap parentModules = new RuntimeModuleMap();
160 if(!moduleName.equals(Types.BUILTIN)) {
161 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
163 Collection<ImportDeclaration> dependencies = module.getDependencies();
164 THashMap<String, ModuleEntry> moduleEntries;
166 moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null, false);
167 } catch (ImportFailureException e) {
168 throw new InternalCompilerError(e);
170 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
171 parentModules.add(m);
173 /*for(ImportDeclaration importAst : module.getDependencies()) {
174 RuntimeModule parentModule =
175 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
177 if(parentModule != null)
178 parentModules.add(parentModule);
180 RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
181 ModuleInitializer initializer = module.getModuleInitializer();
182 if(initializer != null)
184 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
185 } catch (Exception e) {
186 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
189 runtimeModule = new Success<RuntimeModule>(rm);
192 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
194 return runtimeModule;
197 public synchronized void dispose() {
198 if (listeners != null)
203 compilationResult = null;
204 if (runtimeModule != null) {
205 if (runtimeModule.didSucceed())
206 runtimeModule.getResult().dispose();
208 runtimeModule = null;
212 public String toString() {
213 return "ModuleEntry@" + moduleName + "@" + hashCode();
217 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
218 this.parentRepository = parentRepository;
219 this.sourceRepository = sourceRepository;
222 public ModuleRepository(ModuleSourceRepository sourceRepository) {
223 this(null, sourceRepository);
226 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
227 return getModuleEntry(moduleName, listener).compilationResult;
230 public Failable<Module> getModule(String moduleName) {
231 return getModule(moduleName, null);
234 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
235 return getModuleEntry(moduleName, listener).getRuntimeModule();
238 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
239 return getRuntimeModule(moduleName, null);
242 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
243 /* It is deliberate that the following code does not try to prevent
244 * simultaneous compilation of the same module. This is because in
245 * some situations only certain thread trying compilation can succeed
248 ModuleEntry entry = moduleCache.get(moduleName);
250 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
252 entry.addListener(listener);
254 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
255 return parentRepository.getModuleEntry(moduleName, listener);
260 private THashMap<String, ModuleEntry> getModuleEntries(
261 CompilationContext compilationContext,
262 ImportDeclaration[] imports,
263 UpdateListener listener,
264 boolean robustly) throws ImportFailureException {
265 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
266 Collection<ImportFailure> failures = null;
268 TObjectLongHashMap<String> originalImports = new TObjectLongHashMap<String>();
269 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
270 for(ImportDeclaration import_ : imports) {
272 originalImports.put(import_.moduleName, import_.location);
274 while(!stack.isEmpty()) {
275 ImportDeclaration import_ = stack.remove(stack.size()-1);
276 if(!result.containsKey(import_.moduleName)) {
277 boolean originalImport = originalImports.contains(import_.moduleName);
278 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
279 Failable<Module> compilationResult = entry.compilationResult;
280 if(compilationResult.didSucceed()) {
281 result.put(import_.moduleName, entry);
282 stack.addAll(compilationResult.getResult().getDependencies());
284 String deprecation = compilationResult.getResult().getDeprecation();
285 if(deprecation != null && compilationContext != null) {
286 long location = originalImport ? originalImports.get(import_.moduleName) : Locations.NO_LOCATION;
287 compilationContext.errorLog.logWarning(location, "Deprecated module " + import_.moduleName + (deprecation.isEmpty() ? "." : ": " + deprecation));
293 failures = new ArrayList<ImportFailure>(2);
294 failures.add(new ImportFailure(import_.location, import_.moduleName,
295 compilationResult == DoesNotExist.INSTANCE
296 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
297 : ((Failure)compilationResult).errors));
302 if(failures != null && !robustly)
303 throw new ImportFailureException(failures);
308 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
309 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
310 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
312 public boolean execute(String a, ModuleEntry b) {
313 result.put(a, b.compilationResult.getResult());
320 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
321 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
322 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
324 public boolean execute(String a, ModuleEntry b) {
325 result.put(a, b.getRuntimeModule().getResult());
332 public Environment createEnvironment(
333 ImportDeclaration[] imports,
334 UpdateListener listener) throws ImportFailureException {
335 return createEnvironment(null, imports, listener);
338 public Environment createEnvironment(
339 CompilationContext compilationContext,
340 ImportDeclaration[] imports,
341 UpdateListener listener) throws ImportFailureException {
342 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, false);
343 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
344 return createEnvironment(moduleMap, imports);
347 public Environment createEnvironmentRobustly(
348 CompilationContext compilationContext,
349 ImportDeclaration[] imports,
350 UpdateListener listener) {
352 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, true);
353 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
354 return createEnvironment(moduleMap, imports);
355 } catch(ImportFailureException e) {
356 // Should not happen because of robust flag
357 return EmptyEnvironment.INSTANCE;
361 public Environment createEnvironment(
362 EnvironmentSpecification specification,
363 UpdateListener listener) throws ImportFailureException {
364 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
367 public RuntimeEnvironment createRuntimeEnvironment(
368 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
369 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
372 public RuntimeEnvironment createRuntimeEnvironment(EnvironmentSpecification environmentSpecification) throws ImportFailureException {
373 return createRuntimeEnvironment(environmentSpecification, getClass().getClassLoader());
376 public RuntimeEnvironment createRuntimeEnvironment(
377 EnvironmentSpecification environmentSpecification,
378 ClassLoader parentClassLoader,
379 UpdateListener listener) throws ImportFailureException {
380 return createRuntimeEnvironment(
381 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
386 public RuntimeEnvironment createRuntimeEnvironment(
387 ImportDeclaration[] imports,
388 ClassLoader parentClassLoader,
389 UpdateListener listener) throws ImportFailureException {
390 THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener, false);
391 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
392 Environment environment = createEnvironment(moduleMap, imports);
393 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
394 return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
397 private static Environment createEnvironment(
398 THashMap<String, Module> moduleMap,
399 ImportDeclaration[] imports) {
400 NamespaceSpec spec = new NamespaceSpec();
401 for(ImportDeclaration import_ : imports)
402 if(import_.localName != null)
403 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
404 NamespaceFilters.createFromSpec(import_.spec));
406 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
409 private static void addToNamespace(THashMap<String, Module> moduleMap,
410 NamespaceSpec namespace, String moduleName, String localName,
411 NamespaceFilter filter) {
412 if(localName.isEmpty())
413 addToNamespace(moduleMap, namespace, moduleName, filter);
415 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
418 private static void addToNamespace(THashMap<String, Module> moduleMap,
419 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
420 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
421 if(moduleImport == null) {
422 Module module = moduleMap.get(moduleName);
423 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
424 for(ImportDeclaration import_ : module.getDependencies())
425 if(import_.localName != null) {
426 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
427 if(import_.localName.equals(""))
428 localFilter = NamespaceFilters.intersection(filter, localFilter);
429 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
432 else if(!filter.isSubsetOf(moduleImport.filter)) {
433 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
434 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
435 // We have to recheck only modules imported to this namespace
436 if("".equals(import_.localName)) {
437 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
438 localFilter = NamespaceFilters.intersection(filter, localFilter);
439 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
444 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
445 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
446 if(module.didSucceed())
447 return module.getResult().getValue(valueName);
448 else if(module == DoesNotExist.INSTANCE)
449 throw new ValueNotFound("Didn't find module " + moduleName);
451 throw new ValueNotFound(((Failure)module).toString());
454 public Object getValue(String fullValueName) throws ValueNotFound {
455 int p = fullValueName.lastIndexOf('/');
457 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
458 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
461 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
462 Failable<Module> module = getModule(moduleName);
463 if(module.didSucceed()) {
464 SCLValue value = module.getResult().getValue(valueName);
466 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
469 else if(module == DoesNotExist.INSTANCE)
470 throw new ValueNotFound("Didn't find module " + moduleName);
472 throw new ValueNotFound(((Failure)module).toString());
475 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
476 int p = fullValueName.lastIndexOf('/');
478 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
479 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
482 public ModuleSourceRepository getSourceRepository() {
483 return sourceRepository;
486 public String getDocumentation(String documentationName) {
487 String documentation = sourceRepository.getDocumentation(documentationName);
488 if(documentation == null && parentRepository != null)
489 return parentRepository.getDocumentation(documentationName);
490 return documentation;
493 public void flush() {
494 if (parentRepository != null)
495 parentRepository.flush();
496 if (moduleCache != null) {
497 for (ModuleEntry entry : moduleCache.values()) {
505 public Map<String, Module> getModules() {
506 Map<String, Module> result = new HashMap<>(moduleCache.size());
507 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
508 ModuleEntry moduleEntry = entry.getValue();
509 if (moduleEntry.compilationResult.didSucceed()) {
510 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
516 public ModuleCompilationOptionsAdvisor getAdvisor() {
520 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
521 this.advisor = advisor;