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.Environment;
14 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
15 import org.simantics.scl.compiler.environment.NamespaceSpec;
16 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
17 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
18 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
19 import org.simantics.scl.compiler.errors.CompilationError;
20 import org.simantics.scl.compiler.errors.DoesNotExist;
21 import org.simantics.scl.compiler.errors.Failable;
22 import org.simantics.scl.compiler.errors.Failure;
23 import org.simantics.scl.compiler.errors.Success;
24 import org.simantics.scl.compiler.module.ImportDeclaration;
25 import org.simantics.scl.compiler.module.Module;
26 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
27 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
28 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
29 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
30 import org.simantics.scl.compiler.runtime.RuntimeModule;
31 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
32 import org.simantics.scl.compiler.source.ModuleSource;
33 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
34 import org.simantics.scl.compiler.top.ModuleInitializer;
35 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
36 import org.simantics.scl.compiler.top.ValueNotFound;
37 import org.simantics.scl.compiler.types.Types;
39 import gnu.trove.map.hash.THashMap;
40 import gnu.trove.procedure.TObjectObjectProcedure;
41 import gnu.trove.set.hash.THashSet;
44 * Manages compilation and caching of SCL modules.
46 * @author Hannu Niemistö
48 public class ModuleRepository {
49 private final ModuleRepository parentRepository;
50 private final ModuleSourceRepository sourceRepository;
51 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
53 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
55 private ModuleCompilationOptionsAdvisor advisor = null;
57 private static void beginModuleCompilation(String moduleName) {
58 THashSet<String> set = PENDING_MODULES.get();
60 set = new THashSet<String>();
61 PENDING_MODULES.set(set);
63 if(!set.add(moduleName))
64 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
67 private static void finishModuleCompilation(String moduleName) {
68 PENDING_MODULES.get().remove(moduleName);
71 private class ModuleEntry extends UpdateListener implements Observable {
72 final String moduleName;
73 THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
76 Failable<Module> compilationResult;
77 Failable<RuntimeModule> runtimeModule; // created lazily
79 public ModuleEntry(String moduleName) {
80 this.moduleName = moduleName;
83 synchronized void addListener(UpdateListener listener) {
84 if(listener == null || listeners == null)
86 listeners.add(listener);
87 listener.addObservable(this);
90 public synchronized void removeListener(UpdateListener listener) {
91 if (listeners == null)
93 listeners.remove(listener);
97 public void notifyAboutUpdate() {
98 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
99 notifyAboutUpdate(externalListeners);
100 for(UpdateListener listener : externalListeners)
101 listener.notifyAboutUpdate();
104 synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
106 if (listeners == null)
108 if(moduleCache.get(moduleName) == this) {
109 moduleCache.remove(moduleName);
110 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
111 System.out.println("Invalidate " + moduleName);
112 for(UpdateListener l : listeners)
113 System.out.println(" " + l);
115 THashSet<UpdateListener> listenersCopy = listeners;
117 for(UpdateListener l : listenersCopy)
119 for(UpdateListener l : listenersCopy)
120 if(l instanceof ModuleEntry)
121 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
123 externalListeners.add(l);
128 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
129 source = sourceRepository.getModuleSource(moduleName, this);
132 compilationResult = DoesNotExist.getInstance();
134 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
135 System.out.println("Compile " + source);
136 beginModuleCompilation(moduleName);
137 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
138 finishModuleCompilation(moduleName);
141 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
142 if(oldEntry != null) {
143 oldEntry.addListener(listener);
147 addListener(listener);
151 @SuppressWarnings({ "rawtypes", "unchecked" })
152 public synchronized Failable<RuntimeModule> getRuntimeModule() {
153 if(runtimeModule == null) {
154 if(compilationResult.didSucceed()) {
155 Module module = compilationResult.getResult();
156 RuntimeModuleMap parentModules = new RuntimeModuleMap();
157 if(!moduleName.equals(Types.BUILTIN)) {
158 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
160 Collection<ImportDeclaration> dependencies = module.getDependencies();
161 THashMap<String, ModuleEntry> moduleEntries;
163 moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);
164 } catch (ImportFailureException e) {
165 throw new InternalCompilerError(e);
167 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
168 parentModules.add(m);
170 /*for(ImportDeclaration importAst : module.getDependencies()) {
171 RuntimeModule parentModule =
172 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
174 if(parentModule != null)
175 parentModules.add(parentModule);
177 RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
178 ModuleInitializer initializer = module.getModuleInitializer();
179 if(initializer != null)
181 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
182 } catch (Exception e) {
183 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
186 runtimeModule = new Success<RuntimeModule>(rm);
189 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
191 return runtimeModule;
194 public synchronized void dispose() {
195 if (listeners != null)
200 compilationResult = null;
201 if (runtimeModule != null) {
202 if (runtimeModule.didSucceed())
203 runtimeModule.getResult().dispose();
205 runtimeModule = null;
209 public String toString() {
210 return "ModuleEntry@" + moduleName + "@" + hashCode();
214 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
215 this.parentRepository = parentRepository;
216 this.sourceRepository = sourceRepository;
219 public ModuleRepository(ModuleSourceRepository sourceRepository) {
220 this(null, sourceRepository);
223 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
224 return getModuleEntry(moduleName, listener).compilationResult;
227 public Failable<Module> getModule(String moduleName) {
228 return getModule(moduleName, null);
231 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
232 return getModuleEntry(moduleName, listener).getRuntimeModule();
235 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
236 return getRuntimeModule(moduleName, null);
239 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
240 /* It is deliberate that the following code does not try to prevent
241 * simultaneous compilation of the same module. This is because in
242 * some situations only certain thread trying compilation can succeed
245 ModuleEntry entry = moduleCache.get(moduleName);
247 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
249 entry.addListener(listener);
251 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
252 return parentRepository.getModuleEntry(moduleName, listener);
257 private THashMap<String, ModuleEntry> getModuleEntries(
258 CompilationContext compilationContext,
259 ImportDeclaration[] imports,
260 UpdateListener listener) throws ImportFailureException {
261 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
262 Collection<ImportFailure> failures = null;
264 THashSet<String> originalImports = new THashSet<String>();
265 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
266 for(ImportDeclaration import_ : imports) {
268 originalImports.add(import_.moduleName);
270 while(!stack.isEmpty()) {
271 ImportDeclaration import_ = stack.remove(stack.size()-1);
272 if(!result.containsKey(import_.moduleName)) {
273 boolean originalImport = originalImports.contains(import_.moduleName);
274 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
275 Failable<Module> compilationResult = entry.compilationResult;
276 if(compilationResult.didSucceed()) {
277 result.put(import_.moduleName, entry);
278 stack.addAll(compilationResult.getResult().getDependencies());
280 String deprecation = compilationResult.getResult().getDeprecation();
281 if(deprecation != null && compilationContext != null)
282 compilationContext.errorLog.logWarning(import_.location, "Deprecated module " + import_.moduleName + (deprecation.isEmpty() ? "." : ": " + deprecation));
287 failures = new ArrayList<ImportFailure>(2);
288 failures.add(new ImportFailure(import_.location, import_.moduleName,
289 compilationResult == DoesNotExist.INSTANCE
290 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
291 : ((Failure)compilationResult).errors));
297 throw new ImportFailureException(failures);
302 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
303 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
304 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
306 public boolean execute(String a, ModuleEntry b) {
307 result.put(a, b.compilationResult.getResult());
314 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
315 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
316 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
318 public boolean execute(String a, ModuleEntry b) {
319 result.put(a, b.getRuntimeModule().getResult());
326 public Environment createEnvironment(
327 ImportDeclaration[] imports,
328 UpdateListener listener) throws ImportFailureException {
329 return createEnvironment(null, imports, listener);
332 public Environment createEnvironment(
333 CompilationContext compilationContext,
334 ImportDeclaration[] imports,
335 UpdateListener listener) throws ImportFailureException {
336 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener);
337 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
338 return createEnvironment(moduleMap, imports);
341 public Environment createEnvironment(
342 EnvironmentSpecification specification,
343 UpdateListener listener) throws ImportFailureException {
344 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
347 public RuntimeEnvironment createRuntimeEnvironment(
348 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
349 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
352 public RuntimeEnvironment createRuntimeEnvironment(
353 EnvironmentSpecification environmentSpecification,
354 ClassLoader parentClassLoader,
355 UpdateListener listener) throws ImportFailureException {
356 return createRuntimeEnvironment(
357 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
362 public RuntimeEnvironment createRuntimeEnvironment(
363 ImportDeclaration[] imports,
364 ClassLoader parentClassLoader,
365 UpdateListener listener) throws ImportFailureException {
366 THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener);
367 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
368 Environment environment = createEnvironment(moduleMap, imports);
369 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
370 return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
373 private static Environment createEnvironment(
374 THashMap<String, Module> moduleMap,
375 ImportDeclaration[] imports) {
376 NamespaceSpec spec = new NamespaceSpec();
377 for(ImportDeclaration import_ : imports)
378 if(import_.localName != null)
379 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
380 NamespaceFilters.createFromSpec(import_.spec));
382 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
385 private static void addToNamespace(THashMap<String, Module> moduleMap,
386 NamespaceSpec namespace, String moduleName, String localName,
387 NamespaceFilter filter) {
388 if(localName.isEmpty())
389 addToNamespace(moduleMap, namespace, moduleName, filter);
391 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
394 private static void addToNamespace(THashMap<String, Module> moduleMap,
395 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
396 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
397 if(moduleImport == null) {
398 Module module = moduleMap.get(moduleName);
399 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
400 for(ImportDeclaration import_ : module.getDependencies())
401 if(import_.localName != null) {
402 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
403 if(import_.localName.equals(""))
404 localFilter = NamespaceFilters.intersection(filter, localFilter);
405 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
408 else if(!filter.isSubsetOf(moduleImport.filter)) {
409 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
410 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
411 // We have to recheck only modules imported to this namespace
412 if("".equals(import_.localName)) {
413 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
414 localFilter = NamespaceFilters.intersection(filter, localFilter);
415 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
420 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
421 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
422 if(module.didSucceed())
423 return module.getResult().getValue(valueName);
424 else if(module == DoesNotExist.INSTANCE)
425 throw new ValueNotFound("Didn't find module " + moduleName);
427 throw new ValueNotFound(((Failure)module).toString());
430 public Object getValue(String fullValueName) throws ValueNotFound {
431 int p = fullValueName.lastIndexOf('/');
433 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
434 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
437 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
438 Failable<Module> module = getModule(moduleName);
439 if(module.didSucceed()) {
440 SCLValue value = module.getResult().getValue(valueName);
442 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
445 else if(module == DoesNotExist.INSTANCE)
446 throw new ValueNotFound("Didn't find module " + moduleName);
448 throw new ValueNotFound(((Failure)module).toString());
451 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
452 int p = fullValueName.lastIndexOf('/');
454 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
455 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
458 public ModuleSourceRepository getSourceRepository() {
459 return sourceRepository;
462 public String getDocumentation(String documentationName) {
463 String documentation = sourceRepository.getDocumentation(documentationName);
464 if(documentation == null && parentRepository != null)
465 return parentRepository.getDocumentation(documentationName);
466 return documentation;
469 public void flush() {
470 if (parentRepository != null)
471 parentRepository.flush();
472 if (moduleCache != null) {
473 for (ModuleEntry entry : moduleCache.values()) {
481 public Map<String, Module> getModules() {
482 Map<String, Module> result = new HashMap<>(moduleCache.size());
483 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
484 ModuleEntry moduleEntry = entry.getValue();
485 if (moduleEntry.compilationResult.didSucceed()) {
486 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
492 public ModuleCompilationOptionsAdvisor getAdvisor() {
496 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
497 this.advisor = advisor;