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.Locations;
24 import org.simantics.scl.compiler.errors.Success;
25 import org.simantics.scl.compiler.module.ImportDeclaration;
26 import org.simantics.scl.compiler.module.Module;
27 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
28 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
29 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
30 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
31 import org.simantics.scl.compiler.runtime.RuntimeModule;
32 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
33 import org.simantics.scl.compiler.source.ModuleSource;
34 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
35 import org.simantics.scl.compiler.top.ModuleInitializer;
36 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
37 import org.simantics.scl.compiler.top.ValueNotFound;
38 import org.simantics.scl.compiler.types.Types;
40 import gnu.trove.map.hash.THashMap;
41 import gnu.trove.map.hash.TObjectLongHashMap;
42 import gnu.trove.procedure.TObjectObjectProcedure;
43 import gnu.trove.set.hash.THashSet;
46 * Manages compilation and caching of SCL modules.
48 * @author Hannu Niemistö
50 public class ModuleRepository {
51 private final ModuleRepository parentRepository;
52 private final ModuleSourceRepository sourceRepository;
53 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
55 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
57 private ModuleCompilationOptionsAdvisor advisor = null;
59 private static void beginModuleCompilation(String moduleName) {
60 THashSet<String> set = PENDING_MODULES.get();
62 set = new THashSet<String>();
63 PENDING_MODULES.set(set);
65 if(!set.add(moduleName))
66 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
69 private static void finishModuleCompilation(String moduleName) {
70 PENDING_MODULES.get().remove(moduleName);
73 private class ModuleEntry extends UpdateListener implements Observable {
74 final String moduleName;
75 THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
78 Failable<Module> compilationResult;
79 Failable<RuntimeModule> runtimeModule; // created lazily
81 public ModuleEntry(String moduleName) {
82 this.moduleName = moduleName;
85 synchronized void addListener(UpdateListener listener) {
86 if(listener == null || listeners == null)
88 listeners.add(listener);
89 listener.addObservable(this);
92 public synchronized void removeListener(UpdateListener listener) {
93 if (listeners == null)
95 listeners.remove(listener);
99 public void notifyAboutUpdate() {
100 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
101 notifyAboutUpdate(externalListeners);
102 for(UpdateListener listener : externalListeners)
103 listener.notifyAboutUpdate();
106 synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
108 if (listeners == null)
110 if(moduleCache.get(moduleName) == this) {
111 moduleCache.remove(moduleName);
112 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
113 System.out.println("Invalidate " + moduleName);
114 for(UpdateListener l : listeners)
115 System.out.println(" " + l);
117 THashSet<UpdateListener> listenersCopy = listeners;
119 for(UpdateListener l : listenersCopy)
121 for(UpdateListener l : listenersCopy)
122 if(l instanceof ModuleEntry)
123 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
125 externalListeners.add(l);
130 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
131 source = sourceRepository.getModuleSource(moduleName, this);
134 compilationResult = DoesNotExist.getInstance();
136 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
137 System.out.println("Compile " + source);
138 beginModuleCompilation(moduleName);
139 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
140 finishModuleCompilation(moduleName);
143 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
144 if(oldEntry != null) {
145 oldEntry.addListener(listener);
149 addListener(listener);
153 @SuppressWarnings({ "rawtypes", "unchecked" })
154 public synchronized Failable<RuntimeModule> getRuntimeModule() {
155 if(runtimeModule == null) {
156 if(compilationResult.didSucceed()) {
157 Module module = compilationResult.getResult();
158 RuntimeModuleMap parentModules = new RuntimeModuleMap();
159 if(!moduleName.equals(Types.BUILTIN)) {
160 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
162 Collection<ImportDeclaration> dependencies = module.getDependencies();
163 THashMap<String, ModuleEntry> moduleEntries;
165 moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);
166 } catch (ImportFailureException e) {
167 throw new InternalCompilerError(e);
169 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
170 parentModules.add(m);
172 /*for(ImportDeclaration importAst : module.getDependencies()) {
173 RuntimeModule parentModule =
174 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
176 if(parentModule != null)
177 parentModules.add(parentModule);
179 RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
180 ModuleInitializer initializer = module.getModuleInitializer();
181 if(initializer != null)
183 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
184 } catch (Exception e) {
185 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
188 runtimeModule = new Success<RuntimeModule>(rm);
191 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
193 return runtimeModule;
196 public synchronized void dispose() {
197 if (listeners != null)
202 compilationResult = null;
203 if (runtimeModule != null) {
204 if (runtimeModule.didSucceed())
205 runtimeModule.getResult().dispose();
207 runtimeModule = null;
211 public String toString() {
212 return "ModuleEntry@" + moduleName + "@" + hashCode();
216 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
217 this.parentRepository = parentRepository;
218 this.sourceRepository = sourceRepository;
221 public ModuleRepository(ModuleSourceRepository sourceRepository) {
222 this(null, sourceRepository);
225 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
226 return getModuleEntry(moduleName, listener).compilationResult;
229 public Failable<Module> getModule(String moduleName) {
230 return getModule(moduleName, null);
233 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
234 return getModuleEntry(moduleName, listener).getRuntimeModule();
237 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
238 return getRuntimeModule(moduleName, null);
241 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
242 /* It is deliberate that the following code does not try to prevent
243 * simultaneous compilation of the same module. This is because in
244 * some situations only certain thread trying compilation can succeed
247 ModuleEntry entry = moduleCache.get(moduleName);
249 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
251 entry.addListener(listener);
253 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
254 return parentRepository.getModuleEntry(moduleName, listener);
259 private THashMap<String, ModuleEntry> getModuleEntries(
260 CompilationContext compilationContext,
261 ImportDeclaration[] imports,
262 UpdateListener listener) throws ImportFailureException {
263 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
264 Collection<ImportFailure> failures = null;
266 TObjectLongHashMap<String> originalImports = new TObjectLongHashMap<String>();
267 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
268 for(ImportDeclaration import_ : imports) {
270 originalImports.put(import_.moduleName, import_.location);
272 while(!stack.isEmpty()) {
273 ImportDeclaration import_ = stack.remove(stack.size()-1);
274 if(!result.containsKey(import_.moduleName)) {
275 boolean originalImport = originalImports.contains(import_.moduleName);
276 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
277 Failable<Module> compilationResult = entry.compilationResult;
278 if(compilationResult.didSucceed()) {
279 result.put(import_.moduleName, entry);
280 stack.addAll(compilationResult.getResult().getDependencies());
282 String deprecation = compilationResult.getResult().getDeprecation();
283 if(deprecation != null && compilationContext != null) {
284 long location = originalImport ? originalImports.get(import_.moduleName) : Locations.NO_LOCATION;
285 compilationContext.errorLog.logWarning(location, "Deprecated module " + import_.moduleName + (deprecation.isEmpty() ? "." : ": " + deprecation));
291 failures = new ArrayList<ImportFailure>(2);
292 failures.add(new ImportFailure(import_.location, import_.moduleName,
293 compilationResult == DoesNotExist.INSTANCE
294 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
295 : ((Failure)compilationResult).errors));
301 throw new ImportFailureException(failures);
306 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
307 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
308 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
310 public boolean execute(String a, ModuleEntry b) {
311 result.put(a, b.compilationResult.getResult());
318 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
319 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
320 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
322 public boolean execute(String a, ModuleEntry b) {
323 result.put(a, b.getRuntimeModule().getResult());
330 public Environment createEnvironment(
331 ImportDeclaration[] imports,
332 UpdateListener listener) throws ImportFailureException {
333 return createEnvironment(null, imports, listener);
336 public Environment createEnvironment(
337 CompilationContext compilationContext,
338 ImportDeclaration[] imports,
339 UpdateListener listener) throws ImportFailureException {
340 THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener);
341 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
342 return createEnvironment(moduleMap, imports);
345 public Environment createEnvironment(
346 EnvironmentSpecification specification,
347 UpdateListener listener) throws ImportFailureException {
348 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
351 public RuntimeEnvironment createRuntimeEnvironment(
352 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
353 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
356 public RuntimeEnvironment createRuntimeEnvironment(
357 EnvironmentSpecification environmentSpecification,
358 ClassLoader parentClassLoader,
359 UpdateListener listener) throws ImportFailureException {
360 return createRuntimeEnvironment(
361 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
366 public RuntimeEnvironment createRuntimeEnvironment(
367 ImportDeclaration[] imports,
368 ClassLoader parentClassLoader,
369 UpdateListener listener) throws ImportFailureException {
370 THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener);
371 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
372 Environment environment = createEnvironment(moduleMap, imports);
373 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
374 return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
377 private static Environment createEnvironment(
378 THashMap<String, Module> moduleMap,
379 ImportDeclaration[] imports) {
380 NamespaceSpec spec = new NamespaceSpec();
381 for(ImportDeclaration import_ : imports)
382 if(import_.localName != null)
383 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
384 NamespaceFilters.createFromSpec(import_.spec));
386 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
389 private static void addToNamespace(THashMap<String, Module> moduleMap,
390 NamespaceSpec namespace, String moduleName, String localName,
391 NamespaceFilter filter) {
392 if(localName.isEmpty())
393 addToNamespace(moduleMap, namespace, moduleName, filter);
395 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
398 private static void addToNamespace(THashMap<String, Module> moduleMap,
399 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
400 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
401 if(moduleImport == null) {
402 Module module = moduleMap.get(moduleName);
403 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
404 for(ImportDeclaration import_ : module.getDependencies())
405 if(import_.localName != null) {
406 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
407 if(import_.localName.equals(""))
408 localFilter = NamespaceFilters.intersection(filter, localFilter);
409 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
412 else if(!filter.isSubsetOf(moduleImport.filter)) {
413 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
414 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
415 // We have to recheck only modules imported to this namespace
416 if("".equals(import_.localName)) {
417 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
418 localFilter = NamespaceFilters.intersection(filter, localFilter);
419 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
424 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
425 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
426 if(module.didSucceed())
427 return module.getResult().getValue(valueName);
428 else if(module == DoesNotExist.INSTANCE)
429 throw new ValueNotFound("Didn't find module " + moduleName);
431 throw new ValueNotFound(((Failure)module).toString());
434 public Object getValue(String fullValueName) throws ValueNotFound {
435 int p = fullValueName.lastIndexOf('/');
437 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
438 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
441 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
442 Failable<Module> module = getModule(moduleName);
443 if(module.didSucceed()) {
444 SCLValue value = module.getResult().getValue(valueName);
446 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
449 else if(module == DoesNotExist.INSTANCE)
450 throw new ValueNotFound("Didn't find module " + moduleName);
452 throw new ValueNotFound(((Failure)module).toString());
455 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
456 int p = fullValueName.lastIndexOf('/');
458 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
459 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
462 public ModuleSourceRepository getSourceRepository() {
463 return sourceRepository;
466 public String getDocumentation(String documentationName) {
467 String documentation = sourceRepository.getDocumentation(documentationName);
468 if(documentation == null && parentRepository != null)
469 return parentRepository.getDocumentation(documentationName);
470 return documentation;
473 public void flush() {
474 if (parentRepository != null)
475 parentRepository.flush();
476 if (moduleCache != null) {
477 for (ModuleEntry entry : moduleCache.values()) {
485 public Map<String, Module> getModules() {
486 Map<String, Module> result = new HashMap<>(moduleCache.size());
487 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
488 ModuleEntry moduleEntry = entry.getValue();
489 if (moduleEntry.compilationResult.didSucceed()) {
490 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
496 public ModuleCompilationOptionsAdvisor getAdvisor() {
500 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
501 this.advisor = advisor;