1 package org.simantics.scl.compiler.module.repository;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.HashSet;
9 import java.util.concurrent.ConcurrentHashMap;
11 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
12 import org.simantics.scl.compiler.compilation.CompilationContext;
13 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
14 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
15 import org.simantics.scl.compiler.environment.EmptyEnvironment;
16 import org.simantics.scl.compiler.environment.Environment;
17 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
18 import org.simantics.scl.compiler.environment.NamespaceSpec;
19 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
20 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
21 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
22 import org.simantics.scl.compiler.errors.CompilationError;
23 import org.simantics.scl.compiler.errors.DoesNotExist;
24 import org.simantics.scl.compiler.errors.Failable;
25 import org.simantics.scl.compiler.errors.Failure;
26 import org.simantics.scl.compiler.errors.Locations;
27 import org.simantics.scl.compiler.errors.Success;
28 import org.simantics.scl.compiler.module.ImportDeclaration;
29 import org.simantics.scl.compiler.module.Module;
30 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
31 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
32 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
33 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
34 import org.simantics.scl.compiler.runtime.RuntimeModule;
35 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
36 import org.simantics.scl.compiler.source.ModuleSource;
37 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
38 import org.simantics.scl.compiler.top.ModuleInitializer;
39 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
40 import org.simantics.scl.compiler.top.ValueNotFound;
41 import org.simantics.scl.compiler.types.Types;
43 import gnu.trove.map.hash.THashMap;
44 import gnu.trove.map.hash.TObjectLongHashMap;
45 import gnu.trove.procedure.TObjectObjectProcedure;
46 import gnu.trove.set.hash.THashSet;
49 * Manages compilation and caching of SCL modules.
51 * @author Hannu Niemistö
53 public class ModuleRepository {
54 private final ModuleRepository parentRepository;
55 private final ModuleSourceRepository sourceRepository;
56 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
57 public Set<RuntimeEnvironment> runtimeEnvironments = new HashSet<RuntimeEnvironment>();
59 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
61 private ModuleCompilationOptionsAdvisor advisor = null;
63 private static void beginModuleCompilation(String moduleName) {
64 THashSet<String> set = PENDING_MODULES.get();
66 set = new THashSet<String>();
67 PENDING_MODULES.set(set);
69 if(!set.add(moduleName))
70 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
73 private static void finishModuleCompilation(String moduleName) {
74 PENDING_MODULES.get().remove(moduleName);
77 private class ModuleEntry extends UpdateListener implements Observable {
78 final String moduleName;
79 THashSet<UpdateListener> listeners = new THashSet<UpdateListener>(); // listeners == null is used as a marker that this entry is disposed
80 // should be handled only inside synchronized code
83 Failable<Module> compilationResult;
84 Failable<RuntimeModule> runtimeModule; // created lazily
86 public ModuleEntry(String moduleName) {
87 this.moduleName = moduleName;
90 synchronized void addListener(UpdateListener listener) {
91 if(listener == null || listeners == null)
93 listeners.add(listener);
94 listener.addObservable(this);
97 public synchronized void removeListener(UpdateListener listener) {
98 if (listeners == null)
100 listeners.remove(listener);
104 public void notifyAboutUpdate() {
105 // There is a chance that another observable calls notifyAboutUpdate() before stopListening has been completed,
106 // but notifyAboutUpdate(ArrayList<UpdateListener>) lets only one thread to do the notification of dependencies
107 // by clearing listeners field.
109 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
110 notifyAboutUpdate(externalListeners);
111 for(UpdateListener listener : externalListeners)
112 listener.notifyAboutUpdate();
115 void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
116 THashSet<UpdateListener> listenersCopy;
118 listenersCopy = listeners;
119 if (listenersCopy == null)
123 if(moduleCache.get(moduleName) == this) {
124 moduleCache.remove(moduleName);
125 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
126 System.out.println("Invalidate " + moduleName);
127 for(UpdateListener l : listenersCopy)
128 System.out.println(" " + l);
130 for(UpdateListener l : listenersCopy)
131 if(!l.stopListening())
133 else if(l instanceof ModuleEntry)
134 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
136 externalListeners.add(l);
140 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
141 source = sourceRepository.getModuleSource(moduleName, this);
144 compilationResult = DoesNotExist.getInstance();
146 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
147 System.out.println("Compile " + source);
148 beginModuleCompilation(moduleName);
149 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
150 finishModuleCompilation(moduleName);
153 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
154 if(oldEntry != null) {
155 oldEntry.addListener(listener);
159 addListener(listener);
163 @SuppressWarnings({ "rawtypes", "unchecked" })
164 public synchronized Failable<RuntimeModule> getRuntimeModule() {
165 if(runtimeModule == null) {
166 if(compilationResult.didSucceed()) {
167 Module module = compilationResult.getResult();
168 RuntimeModuleMap parentModules = new RuntimeModuleMap();
169 if(!moduleName.equals(Types.BUILTIN)) {
170 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
172 Collection<ImportDeclaration> dependencies = module.getDependencies();
173 THashMap<String, ModuleEntry> moduleEntries;
175 moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null, false);
176 } catch (ImportFailureException e) {
177 throw new InternalCompilerError(e);
179 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
180 parentModules.add(m);
182 /*for(ImportDeclaration importAst : module.getDependencies()) {
183 RuntimeModule parentModule =
184 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
186 if(parentModule != null)
187 parentModules.add(parentModule);
189 RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
190 ModuleInitializer initializer = module.getModuleInitializer();
191 if(initializer != null)
193 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
194 } catch (Exception e) {
195 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
198 runtimeModule = new Success<RuntimeModule>(rm);
201 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
203 return runtimeModule;
206 public synchronized void dispose() {
210 compilationResult = null;
211 if (runtimeModule != null) {
212 if (runtimeModule.didSucceed())
213 runtimeModule.getResult().dispose();
215 runtimeModule = null;
219 public String toString() {
220 return "ModuleEntry@" + moduleName + "@" + hashCode();
224 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
225 this.parentRepository = parentRepository;
226 this.sourceRepository = sourceRepository;
229 public ModuleRepository(ModuleSourceRepository sourceRepository) {
230 this(null, sourceRepository);
233 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
234 return getModuleEntry(moduleName, listener).compilationResult;
237 public Failable<Module> getModule(String moduleName) {
238 return getModule(moduleName, null);
241 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
242 return getModuleEntry(moduleName, listener).getRuntimeModule();
245 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
246 return getRuntimeModule(moduleName, null);
249 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
250 /* It is deliberate that the following code does not try to prevent
251 * simultaneous compilation of the same module. This is because in
252 * some situations only certain thread trying compilation can succeed
255 ModuleEntry entry = moduleCache.get(moduleName);
257 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
259 entry.addListener(listener);
261 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
262 return parentRepository.getModuleEntry(moduleName, listener);
267 private THashMap<String, ModuleEntry> getModuleEntries(
268 CompilationContext compilationContext,
269 ImportDeclaration[] imports,
270 UpdateListener listener,
271 boolean robustly) throws ImportFailureException {
272 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
273 Collection<ImportFailure> failures = null;
275 TObjectLongHashMap<String> originalImports = new TObjectLongHashMap<String>();
276 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
277 for(ImportDeclaration import_ : imports) {
279 originalImports.put(import_.moduleName, import_.location);
281 while(!stack.isEmpty()) {
282 ImportDeclaration import_ = stack.remove(stack.size()-1);
283 if(!result.containsKey(import_.moduleName)) {
284 boolean originalImport = originalImports.contains(import_.moduleName);
285 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
286 Failable<Module> compilationResult = entry.compilationResult;
287 if(compilationResult.didSucceed()) {
288 result.put(import_.moduleName, entry);
289 stack.addAll(compilationResult.getResult().getDependencies());
291 String deprecation = compilationResult.getResult().getDeprecation();
292 if(deprecation != null && compilationContext != null) {
293 long location = originalImport ? originalImports.get(import_.moduleName) : Locations.NO_LOCATION;
294 compilationContext.errorLog.logWarning(location, "Deprecated module " + import_.moduleName + (deprecation.isEmpty() ? "." : ": " + deprecation));
300 failures = new ArrayList<ImportFailure>(2);
301 failures.add(new ImportFailure(import_.location, import_.moduleName,
302 compilationResult == DoesNotExist.INSTANCE
303 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
304 : ((Failure)compilationResult).errors));
309 if(failures != null && !robustly)
310 throw new ImportFailureException(failures);
315 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
316 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
317 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
319 public boolean execute(String a, ModuleEntry b) {
320 result.put(a, b.compilationResult.getResult());
327 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
328 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
329 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
331 public boolean execute(String a, ModuleEntry b) {
332 result.put(a, b.getRuntimeModule().getResult());
339 public Environment createEnvironment(
340 ImportDeclaration[] imports,
341 UpdateListener listener) throws ImportFailureException {
342 return createEnvironment(null, imports, listener);
345 public Environment createEnvironment(
346 CompilationContext compilationContext,
347 ImportDeclaration[] imports,
348 UpdateListener listener) throws ImportFailureException {
349 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, false);
350 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
351 return createEnvironment(moduleMap, imports);
354 public Environment createEnvironmentRobustly(
355 CompilationContext compilationContext,
356 ImportDeclaration[] imports,
357 UpdateListener listener) {
359 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, true);
360 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
361 return createEnvironment(moduleMap, imports);
362 } catch(ImportFailureException e) {
363 // Should not happen because of robust flag
364 return EmptyEnvironment.INSTANCE;
368 public Environment createEnvironment(
369 EnvironmentSpecification specification,
370 UpdateListener listener) throws ImportFailureException {
371 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
374 public RuntimeEnvironment createRuntimeEnvironment(
375 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
376 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
379 public RuntimeEnvironment createRuntimeEnvironment(EnvironmentSpecification environmentSpecification) throws ImportFailureException {
380 return createRuntimeEnvironment(environmentSpecification, getClass().getClassLoader());
383 public RuntimeEnvironment createRuntimeEnvironment(
384 EnvironmentSpecification environmentSpecification,
385 ClassLoader parentClassLoader,
386 UpdateListener listener) throws ImportFailureException {
387 return createRuntimeEnvironment(
388 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
393 public RuntimeEnvironment createRuntimeEnvironment(
394 ImportDeclaration[] imports,
395 ClassLoader parentClassLoader,
396 UpdateListener listener) throws ImportFailureException {
397 THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener, false);
398 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
399 Environment environment = createEnvironment(moduleMap, imports);
400 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
401 RuntimeEnvironmentImpl result = new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
402 runtimeEnvironments.add(result);
406 private static Environment createEnvironment(
407 THashMap<String, Module> moduleMap,
408 ImportDeclaration[] imports) {
409 NamespaceSpec spec = new NamespaceSpec();
410 for(ImportDeclaration import_ : imports)
411 if(import_.localName != null)
412 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
413 NamespaceFilters.createFromSpec(import_.spec));
415 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
418 private static void addToNamespace(THashMap<String, Module> moduleMap,
419 NamespaceSpec namespace, String moduleName, String localName,
420 NamespaceFilter filter) {
421 if(localName.isEmpty())
422 addToNamespace(moduleMap, namespace, moduleName, filter);
424 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
427 private static void addToNamespace(THashMap<String, Module> moduleMap,
428 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
429 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
430 if(moduleImport == null) {
431 Module module = moduleMap.get(moduleName);
432 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
433 for(ImportDeclaration import_ : module.getDependencies())
434 if(import_.localName != null) {
435 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
436 if(import_.localName.equals(""))
437 localFilter = NamespaceFilters.intersection(filter, localFilter);
438 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
441 else if(!filter.isSubsetOf(moduleImport.filter)) {
442 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
443 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
444 // We have to recheck only modules imported to this namespace
445 if("".equals(import_.localName)) {
446 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
447 localFilter = NamespaceFilters.intersection(filter, localFilter);
448 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
453 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
454 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
455 if(module.didSucceed())
456 return module.getResult().getValue(valueName);
457 else if(module == DoesNotExist.INSTANCE)
458 throw new ValueNotFound("Didn't find module " + moduleName);
460 throw new ValueNotFound(((Failure)module).toString());
463 public Object getValue(String fullValueName) throws ValueNotFound {
464 int p = fullValueName.lastIndexOf('/');
466 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
467 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
470 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
471 Failable<Module> module = getModule(moduleName);
472 if(module.didSucceed()) {
473 SCLValue value = module.getResult().getValue(valueName);
475 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
478 else if(module == DoesNotExist.INSTANCE)
479 throw new ValueNotFound("Didn't find module " + moduleName);
481 throw new ValueNotFound(((Failure)module).toString());
484 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
485 int p = fullValueName.lastIndexOf('/');
487 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
488 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
491 public ModuleSourceRepository getSourceRepository() {
492 return sourceRepository;
495 public String getDocumentation(String documentationName) {
496 String documentation = sourceRepository.getDocumentation(documentationName);
497 if(documentation == null && parentRepository != null)
498 return parentRepository.getDocumentation(documentationName);
499 return documentation;
503 * Flush clears module repository cache completely. It should not be called in
504 * normal operation, but may be useful during testing for clearing repositories
505 * that are statically defined.
507 public void flush() {
508 if (parentRepository != null)
509 parentRepository.flush();
510 if (moduleCache != null)
511 for (ModuleEntry entry : moduleCache.values())
513 moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
517 * Gets the map of all modules that have been currently compiled successfully.
518 * Not that the method does not return all possible modules in the source repository.
520 public Map<String, Module> getModules() {
521 Map<String, Module> result = new HashMap<>(moduleCache.size());
522 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
523 ModuleEntry moduleEntry = entry.getValue();
524 if (moduleEntry.compilationResult.didSucceed()) {
525 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
531 public ModuleCompilationOptionsAdvisor getAdvisor() {
535 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
536 this.advisor = advisor;
539 public Set<RuntimeEnvironment> getRuntimeEnvironments() {
540 return runtimeEnvironments;