1 package org.simantics.scl.compiler.module.repository;
\r
3 import java.util.ArrayList;
\r
4 import java.util.Collection;
\r
5 import java.util.HashMap;
\r
6 import java.util.Map;
\r
7 import java.util.WeakHashMap;
\r
8 import java.util.concurrent.ConcurrentHashMap;
\r
10 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
\r
11 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
\r
12 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
\r
13 import org.simantics.scl.compiler.environment.Environment;
\r
14 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
\r
15 import org.simantics.scl.compiler.environment.NamespaceSpec;
\r
16 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
\r
17 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
\r
18 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
\r
19 import org.simantics.scl.compiler.errors.CompilationError;
\r
20 import org.simantics.scl.compiler.errors.DoesNotExist;
\r
21 import org.simantics.scl.compiler.errors.Failable;
\r
22 import org.simantics.scl.compiler.errors.Failure;
\r
23 import org.simantics.scl.compiler.errors.Success;
\r
24 import org.simantics.scl.compiler.module.ImportDeclaration;
\r
25 import org.simantics.scl.compiler.module.Module;
\r
26 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
\r
27 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
\r
28 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
\r
29 import org.simantics.scl.compiler.runtime.RuntimeModule;
\r
30 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
\r
31 import org.simantics.scl.compiler.source.ModuleSource;
\r
32 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
\r
33 import org.simantics.scl.compiler.top.ModuleInitializer;
\r
34 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
\r
35 import org.simantics.scl.compiler.top.ValueNotFound;
\r
36 import org.simantics.scl.compiler.types.Types;
\r
38 import gnu.trove.map.hash.THashMap;
\r
39 import gnu.trove.procedure.TObjectObjectProcedure;
\r
40 import gnu.trove.set.hash.THashSet;
\r
43 * Manages compilation and caching of SCL modules.
\r
45 * @author Hannu Niemistö
\r
47 public class ModuleRepository {
\r
48 private final ModuleRepository parentRepository;
\r
49 private final ModuleSourceRepository sourceRepository;
\r
50 private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
\r
52 private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
\r
54 private ModuleCompilationOptionsAdvisor advisor = null;
\r
56 private static void beginModuleCompilation(String moduleName) {
\r
57 THashSet<String> set = PENDING_MODULES.get();
\r
59 set = new THashSet<String>();
\r
60 PENDING_MODULES.set(set);
\r
62 if(!set.add(moduleName))
\r
63 throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
\r
66 private static void finishModuleCompilation(String moduleName) {
\r
67 PENDING_MODULES.get().remove(moduleName);
\r
70 private class ModuleEntry implements UpdateListener {
\r
71 final String moduleName;
\r
72 WeakHashMap<UpdateListener,Object> listeners = new WeakHashMap<UpdateListener,Object>();
\r
74 ModuleSource source;
\r
75 Failable<Module> compilationResult;
\r
76 Failable<RuntimeModule> runtimeModule; // created lazily
\r
78 public ModuleEntry(String moduleName) {
\r
79 this.moduleName = moduleName;
\r
82 synchronized void addListener(UpdateListener listener) {
\r
83 if(listener != null)
\r
84 listeners.put(listener, null);
\r
88 public void notifyAboutUpdate() {
\r
89 if (listeners == null)
\r
91 ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
\r
92 notifyAboutUpdate(externalListeners);
\r
93 for(UpdateListener listener : externalListeners)
\r
94 listener.notifyAboutUpdate();
\r
97 synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
\r
98 if(moduleCache.get(moduleName) == this) {
\r
99 moduleCache.remove(moduleName);
\r
100 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
\r
101 System.out.println("Invalidate " + moduleName);
\r
102 for(UpdateListener l : listeners.keySet())
\r
103 System.out.println(" " + l);
\r
105 for(UpdateListener l : listeners.keySet())
\r
106 if(l instanceof ModuleEntry)
\r
107 ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
\r
109 externalListeners.add(l);
\r
113 private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
\r
114 source = sourceRepository.getModuleSource(moduleName, this);
\r
117 compilationResult = DoesNotExist.getInstance();
\r
119 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
\r
120 System.out.println("Compile " + source);
\r
121 beginModuleCompilation(moduleName);
\r
122 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
\r
123 finishModuleCompilation(moduleName);
\r
126 ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
\r
127 if(oldEntry != null) {
\r
128 oldEntry.addListener(listener);
\r
132 addListener(listener);
\r
136 @SuppressWarnings({ "rawtypes", "unchecked" })
\r
137 public synchronized Failable<RuntimeModule> getRuntimeModule() {
\r
138 if(runtimeModule == null) {
\r
139 if(compilationResult.didSucceed()) {
\r
140 Module module = compilationResult.getResult();
\r
141 RuntimeModuleMap parentModules = new RuntimeModuleMap();
\r
142 if(!moduleName.equals(Types.BUILTIN)) {
\r
143 parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
\r
145 Collection<ImportDeclaration> dependencies = module.getDependencies();
\r
146 THashMap<String, ModuleEntry> moduleEntries;
\r
148 moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);
\r
149 } catch (ImportFailureException e) {
\r
150 throw new InternalCompilerError(e);
\r
152 for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
\r
153 parentModules.add(m);
\r
155 /*for(ImportDeclaration importAst : module.getDependencies()) {
\r
156 RuntimeModule parentModule =
\r
157 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
\r
159 if(parentModule != null)
\r
160 parentModules.add(parentModule);
\r
162 RuntimeModule rm = new RuntimeModule(module, parentModules, source.getClassLoader());
\r
163 ModuleInitializer initializer = module.getModuleInitializer();
\r
164 if(initializer != null)
\r
166 initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
\r
167 } catch (Exception e) {
\r
168 compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
\r
169 e.printStackTrace();
\r
171 runtimeModule = new Success<RuntimeModule>(rm);
\r
174 runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
\r
176 return runtimeModule;
\r
179 public void dispose() {
\r
180 if (listeners != null)
\r
184 compilationResult = null;
\r
185 if (runtimeModule != null) {
\r
186 if (runtimeModule.didSucceed())
\r
187 runtimeModule.getResult().dispose();
\r
189 runtimeModule = null;
\r
193 public String toString() {
\r
194 return "ModuleEntry@" + moduleName + "@" + hashCode();
\r
198 public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
\r
199 this.parentRepository = parentRepository;
\r
200 this.sourceRepository = sourceRepository;
\r
203 public ModuleRepository(ModuleSourceRepository sourceRepository) {
\r
204 this(null, sourceRepository);
\r
207 public Failable<Module> getModule(String moduleName, UpdateListener listener) {
\r
208 return getModuleEntry(moduleName, listener).compilationResult;
\r
211 public Failable<Module> getModule(String moduleName) {
\r
212 return getModule(moduleName, null);
\r
215 public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
\r
216 return getModuleEntry(moduleName, listener).getRuntimeModule();
\r
219 public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
\r
220 return getRuntimeModule(moduleName, null);
\r
223 private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
\r
224 /* It is deliberate that the following code does not try to prevent
\r
225 * simultaneous compilation of the same module. This is because in
\r
226 * some situations only certain thread trying compilation can succeed
\r
229 ModuleEntry entry = moduleCache.get(moduleName);
\r
231 entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
\r
233 entry.addListener(listener);
\r
235 if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
\r
236 return parentRepository.getModuleEntry(moduleName, listener);
\r
241 private THashMap<String, ModuleEntry> getModuleEntries(
\r
242 ImportDeclaration[] imports,
\r
243 UpdateListener listener) throws ImportFailureException {
\r
244 THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
\r
245 Collection<ImportFailure> failures = null;
\r
247 ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
\r
248 for(ImportDeclaration import_ : imports)
\r
249 stack.add(import_);
\r
250 while(!stack.isEmpty()) {
\r
251 ImportDeclaration import_ = stack.remove(stack.size()-1);
\r
252 if(!result.containsKey(import_.moduleName)) {
\r
253 ModuleEntry entry = getModuleEntry(import_.moduleName, listener);
\r
254 Failable<Module> compilationResult = entry.compilationResult;
\r
255 if(compilationResult.didSucceed()) {
\r
256 result.put(import_.moduleName, entry);
\r
257 stack.addAll(compilationResult.getResult().getDependencies());
\r
260 if(failures == null)
\r
261 failures = new ArrayList<ImportFailure>(2);
\r
262 failures.add(new ImportFailure(import_.location, import_.moduleName,
\r
263 compilationResult == DoesNotExist.INSTANCE
\r
264 ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
\r
265 : ((Failure)compilationResult).errors));
\r
270 if(failures != null)
\r
271 throw new ImportFailureException(failures);
\r
276 private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
\r
277 final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
\r
278 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
\r
280 public boolean execute(String a, ModuleEntry b) {
\r
281 result.put(a, b.compilationResult.getResult());
\r
288 private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
\r
289 final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
\r
290 entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
\r
292 public boolean execute(String a, ModuleEntry b) {
\r
293 result.put(a, b.getRuntimeModule().getResult());
\r
300 public Environment createEnvironment(
\r
301 ImportDeclaration[] imports,
\r
302 UpdateListener listener) throws ImportFailureException {
\r
303 THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
\r
304 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
\r
305 return createEnvironment(moduleMap, imports);
\r
308 public Environment createEnvironment(
\r
309 EnvironmentSpecification specification,
\r
310 UpdateListener listener) throws ImportFailureException {
\r
311 return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
\r
314 public RuntimeEnvironment createRuntimeEnvironment(
\r
315 EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
\r
316 return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
\r
319 public RuntimeEnvironment createRuntimeEnvironment(
\r
320 EnvironmentSpecification environmentSpecification,
\r
321 ClassLoader parentClassLoader,
\r
322 UpdateListener listener) throws ImportFailureException {
\r
323 return createRuntimeEnvironment(
\r
324 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
\r
329 public RuntimeEnvironment createRuntimeEnvironment(
\r
330 ImportDeclaration[] imports,
\r
331 ClassLoader parentClassLoader,
\r
332 UpdateListener listener) throws ImportFailureException {
\r
333 THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
\r
334 THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
\r
335 Environment environment = createEnvironment(moduleMap, imports);
\r
336 THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
\r
337 return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
\r
340 private static Environment createEnvironment(THashMap<String, Module> moduleMap,
\r
341 ImportDeclaration[] imports) {
\r
342 NamespaceSpec spec = new NamespaceSpec();
\r
343 for(ImportDeclaration import_ : imports)
\r
344 if(import_.localName != null)
\r
345 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
\r
346 NamespaceFilters.createFromSpec(import_.spec));
\r
348 return new ConcreteEnvironment(moduleMap, spec.toNamespace());
\r
351 private static void addToNamespace(THashMap<String, Module> moduleMap,
\r
352 NamespaceSpec namespace, String moduleName, String localName,
\r
353 NamespaceFilter filter) {
\r
354 if(localName.isEmpty())
\r
355 addToNamespace(moduleMap, namespace, moduleName, filter);
\r
357 addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
\r
360 private static void addToNamespace(THashMap<String, Module> moduleMap,
\r
361 NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
\r
362 ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
\r
363 if(moduleImport == null) {
\r
364 Module module = moduleMap.get(moduleName);
\r
365 namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
\r
366 for(ImportDeclaration import_ : module.getDependencies())
\r
367 if(import_.localName != null) {
\r
368 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
\r
369 if(import_.localName.equals(""))
\r
370 localFilter = NamespaceFilters.intersection(filter, localFilter);
\r
371 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
\r
374 else if(!filter.isSubsetOf(moduleImport.filter)) {
\r
375 moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
\r
376 for(ImportDeclaration import_ : moduleImport.module.getDependencies())
\r
377 // We have to recheck only modules imported to this namespace
\r
378 if("".equals(import_.localName)) {
\r
379 NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
\r
380 localFilter = NamespaceFilters.intersection(filter, localFilter);
\r
381 addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
\r
386 public Object getValue(String moduleName, String valueName) throws ValueNotFound {
\r
387 Failable<RuntimeModule> module = getRuntimeModule(moduleName);
\r
388 if(module.didSucceed())
\r
389 return module.getResult().getValue(valueName);
\r
390 else if(module == DoesNotExist.INSTANCE)
\r
391 throw new ValueNotFound("Didn't find module " + moduleName);
\r
393 throw new ValueNotFound(((Failure)module).toString());
\r
396 public Object getValue(String fullValueName) throws ValueNotFound {
\r
397 int p = fullValueName.lastIndexOf('/');
\r
399 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
\r
400 return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
\r
403 public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
\r
404 Failable<Module> module = getModule(moduleName);
\r
405 if(module.didSucceed()) {
\r
406 SCLValue value = module.getResult().getValue(valueName);
\r
408 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
\r
411 else if(module == DoesNotExist.INSTANCE)
\r
412 throw new ValueNotFound("Didn't find module " + moduleName);
\r
414 throw new ValueNotFound(((Failure)module).toString());
\r
417 public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
\r
418 int p = fullValueName.lastIndexOf('/');
\r
420 throw new ValueNotFound(fullValueName + " is not a valid full value name.");
\r
421 return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
\r
424 public ModuleSourceRepository getSourceRepository() {
\r
425 return sourceRepository;
\r
428 public String getDocumentation(String documentationName) {
\r
429 String documentation = sourceRepository.getDocumentation(documentationName);
\r
430 if(documentation == null && parentRepository != null)
\r
431 return parentRepository.getDocumentation(documentationName);
\r
432 return documentation;
\r
435 public void flush() {
\r
436 if (parentRepository != null)
\r
437 parentRepository.flush();
\r
438 if (moduleCache != null) {
\r
439 for (ModuleEntry entry : moduleCache.values()) {
\r
442 moduleCache.clear();
\r
444 moduleCache = null;
\r
447 public Map<String, Module> getModules() {
\r
448 Map<String, Module> result = new HashMap<>(moduleCache.size());
\r
449 for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
\r
450 ModuleEntry moduleEntry = entry.getValue();
\r
451 if (moduleEntry.compilationResult.didSucceed()) {
\r
452 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
\r
458 public ModuleCompilationOptionsAdvisor getAdvisor() {
\r
462 public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
\r
463 this.advisor = advisor;
\r