]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java
Added module header feature to SCL language.
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / module / repository / ModuleRepository.java
1 package org.simantics.scl.compiler.module.repository;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.concurrent.ConcurrentHashMap;
8
9 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
10 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
11 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
12 import org.simantics.scl.compiler.environment.Environment;
13 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
14 import org.simantics.scl.compiler.environment.NamespaceSpec;
15 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
16 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
17 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
18 import org.simantics.scl.compiler.errors.CompilationError;
19 import org.simantics.scl.compiler.errors.DoesNotExist;
20 import org.simantics.scl.compiler.errors.Failable;
21 import org.simantics.scl.compiler.errors.Failure;
22 import org.simantics.scl.compiler.errors.Success;
23 import org.simantics.scl.compiler.module.ImportDeclaration;
24 import org.simantics.scl.compiler.module.Module;
25 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
26 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
27 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
28 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
29 import org.simantics.scl.compiler.runtime.RuntimeModule;
30 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
31 import org.simantics.scl.compiler.source.ModuleSource;
32 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
33 import org.simantics.scl.compiler.top.ModuleInitializer;
34 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
35 import org.simantics.scl.compiler.top.ValueNotFound;
36 import org.simantics.scl.compiler.types.Types;
37
38 import gnu.trove.map.hash.THashMap;
39 import gnu.trove.procedure.TObjectObjectProcedure;
40 import gnu.trove.set.hash.THashSet;
41
42 /**
43  * Manages compilation and caching of SCL modules.
44  * 
45  * @author Hannu Niemistö
46  */
47 public class ModuleRepository {
48     private final ModuleRepository parentRepository;
49     private final ModuleSourceRepository sourceRepository;
50     private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
51     
52     private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
53     
54     private ModuleCompilationOptionsAdvisor advisor = null;
55     
56     private static void beginModuleCompilation(String moduleName) {
57         THashSet<String> set = PENDING_MODULES.get();
58         if(set == null) {
59             set = new THashSet<String>();
60             PENDING_MODULES.set(set);
61         }
62         if(!set.add(moduleName))
63             throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
64     }
65     
66     private static void finishModuleCompilation(String moduleName) {
67         PENDING_MODULES.get().remove(moduleName);
68     }
69     
70     private class ModuleEntry extends UpdateListener implements Observable {
71         final String moduleName;
72         THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
73         
74         ModuleSource source;
75         Failable<Module> compilationResult;
76         Failable<RuntimeModule> runtimeModule; // created lazily
77
78         public ModuleEntry(String moduleName) {
79             this.moduleName = moduleName;
80         }
81         
82         synchronized void addListener(UpdateListener listener) {
83             if(listener == null || listeners == null)
84                 return;
85             listeners.add(listener);
86             listener.addObservable(this);
87         }
88
89         public synchronized void removeListener(UpdateListener listener) {
90             if (listeners == null)
91                 return;
92             listeners.remove(listener);
93         }
94         
95         @Override
96         public void notifyAboutUpdate() {
97             ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
98             notifyAboutUpdate(externalListeners);
99             for(UpdateListener listener : externalListeners)
100                 listener.notifyAboutUpdate();
101         }
102
103         synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
104             stopListening();
105             if (listeners == null)
106                 return;
107             if(moduleCache.get(moduleName) == this) {
108                 moduleCache.remove(moduleName);
109                 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
110                     System.out.println("Invalidate " + moduleName);
111                     for(UpdateListener l : listeners)
112                         System.out.println("    " + l);
113                 }
114                 THashSet<UpdateListener> listenersCopy = listeners;
115                 listeners = null;
116                 for(UpdateListener l : listenersCopy)
117                     l.stopListening();
118                 for(UpdateListener l : listenersCopy)
119                     if(l instanceof ModuleEntry)
120                         ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
121                     else {
122                         externalListeners.add(l);
123                     }
124             }
125         }
126
127         private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
128             source = sourceRepository.getModuleSource(moduleName, this);
129             
130             if(source == null)
131                 compilationResult = DoesNotExist.getInstance();
132             else {
133                 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
134                     System.out.println("Compile " + source);
135                 beginModuleCompilation(moduleName);
136                 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
137                 finishModuleCompilation(moduleName);
138             }
139         
140             ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
141             if(oldEntry != null) {
142                 oldEntry.addListener(listener);
143                 return oldEntry;
144             }
145             
146             addListener(listener);
147             return this;
148         }
149         
150         @SuppressWarnings({ "rawtypes", "unchecked" })
151         public synchronized Failable<RuntimeModule> getRuntimeModule() {
152             if(runtimeModule == null) {
153                 if(compilationResult.didSucceed()) {
154                     Module module = compilationResult.getResult();
155                     RuntimeModuleMap parentModules = new RuntimeModuleMap();
156                     if(!moduleName.equals(Types.BUILTIN)) {
157                         parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
158                                 .getResult());
159                         Collection<ImportDeclaration> dependencies = module.getDependencies();
160                         THashMap<String, ModuleEntry> moduleEntries;
161                         try {
162                             moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);
163                         } catch (ImportFailureException e) {
164                             throw new InternalCompilerError(e);
165                         }
166                         for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
167                             parentModules.add(m);
168                     }
169                     /*for(ImportDeclaration importAst : module.getDependencies()) {
170                         RuntimeModule parentModule =
171                                 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
172                                 .getResult();
173                         if(parentModule != null)
174                             parentModules.add(parentModule);
175                     }*/
176                     RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
177                     ModuleInitializer initializer = module.getModuleInitializer();
178                     if(initializer != null)
179                         try {
180                             initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
181                         } catch (Exception e) {
182                             compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
183                             e.printStackTrace();
184                         }
185                     runtimeModule = new Success<RuntimeModule>(rm); 
186                 }
187                 else
188                     runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
189             }
190             return runtimeModule;
191         }
192
193         public synchronized void dispose() {
194             if (listeners != null)
195                 listeners.clear();
196             listeners = null;
197             stopListening();
198             source = null;
199             compilationResult = null;
200             if (runtimeModule != null) {
201                 if (runtimeModule.didSucceed())
202                     runtimeModule.getResult().dispose();
203             }
204             runtimeModule = null;
205         }
206         
207         @Override
208         public String toString() {
209             return "ModuleEntry@" + moduleName + "@" + hashCode();
210         }
211     }
212     
213     public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
214         this.parentRepository = parentRepository;
215         this.sourceRepository = sourceRepository;
216     }
217
218     public ModuleRepository(ModuleSourceRepository sourceRepository) {
219         this(null, sourceRepository);
220     }
221     
222     public Failable<Module> getModule(String moduleName, UpdateListener listener) {
223         return getModuleEntry(moduleName, listener).compilationResult;
224     }
225     
226     public Failable<Module> getModule(String moduleName) {
227         return getModule(moduleName, null);
228     }
229     
230     public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
231         return getModuleEntry(moduleName, listener).getRuntimeModule();
232     }
233     
234     public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
235         return getRuntimeModule(moduleName, null);
236     }
237
238     private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
239         /* It is deliberate that the following code does not try to prevent
240          * simultaneous compilation of the same module. This is because in
241          * some situations only certain thread trying compilation can succeed
242          * in it.
243          */
244         ModuleEntry entry = moduleCache.get(moduleName);
245         if(entry == null)
246             entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
247         else
248             entry.addListener(listener);
249
250         if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
251             return parentRepository.getModuleEntry(moduleName, listener);
252         else
253             return entry;
254     }
255     
256     private THashMap<String, ModuleEntry> getModuleEntries(
257             ImportDeclaration[] imports,
258             UpdateListener listener) throws ImportFailureException {
259         THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
260         Collection<ImportFailure> failures = null;
261         
262         THashSet<String> originalImports = new THashSet<String>(); 
263         ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
264         for(ImportDeclaration import_ : imports) {
265             stack.add(import_);
266             originalImports.add(import_.moduleName);
267         }
268         while(!stack.isEmpty()) {
269             ImportDeclaration import_ = stack.remove(stack.size()-1);
270             if(!result.containsKey(import_.moduleName)) {
271                 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImports.contains(import_.moduleName) ? listener : null);
272                 Failable<Module> compilationResult = entry.compilationResult;
273                 if(compilationResult.didSucceed()) {
274                     result.put(import_.moduleName, entry);
275                     stack.addAll(compilationResult.getResult().getDependencies());
276                 }
277                 else {
278                     if(failures == null)
279                         failures = new ArrayList<ImportFailure>(2);
280                     failures.add(new ImportFailure(import_.location, import_.moduleName,
281                             compilationResult == DoesNotExist.INSTANCE
282                                     ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
283                                     : ((Failure)compilationResult).errors));
284                 }
285             }
286         }
287         
288         if(failures != null)
289             throw new ImportFailureException(failures);
290         
291         return result;
292     }
293
294     private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
295         final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
296         entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
297             @Override
298             public boolean execute(String a, ModuleEntry b) {
299                 result.put(a, b.compilationResult.getResult());
300                 return true;
301             }
302         });
303         return result;
304     }
305     
306     private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
307         final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
308         entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
309             @Override
310             public boolean execute(String a, ModuleEntry b) {
311                 result.put(a, b.getRuntimeModule().getResult());
312                 return true;
313             }
314         });
315         return result;
316     }
317     
318     public Environment createEnvironment(
319             ImportDeclaration[] imports,
320             UpdateListener listener) throws ImportFailureException {
321         THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
322         THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
323         return createEnvironment(moduleMap, imports);
324     }
325     
326     public Environment createEnvironment(
327             EnvironmentSpecification specification,
328             UpdateListener listener) throws ImportFailureException {
329         return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
330     }
331     
332     public RuntimeEnvironment createRuntimeEnvironment(
333             EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
334         return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
335     }
336     
337     public RuntimeEnvironment createRuntimeEnvironment(
338             EnvironmentSpecification environmentSpecification,
339             ClassLoader parentClassLoader,
340             UpdateListener listener) throws ImportFailureException {
341         return createRuntimeEnvironment(
342                 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
343                 parentClassLoader,
344                 listener);
345     }
346     
347     public RuntimeEnvironment createRuntimeEnvironment(
348             ImportDeclaration[] imports,
349             ClassLoader parentClassLoader,
350             UpdateListener listener) throws ImportFailureException {
351         THashMap<String, ModuleEntry> entries = getModuleEntries(imports, listener);
352         THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
353         Environment environment = createEnvironment(moduleMap, imports);
354         THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
355         return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
356     }
357     
358     private static Environment createEnvironment(THashMap<String, Module> moduleMap, 
359             ImportDeclaration[] imports) {
360         NamespaceSpec spec = new NamespaceSpec();
361         for(ImportDeclaration import_ : imports)
362             if(import_.localName != null)
363                 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
364                         NamespaceFilters.createFromSpec(import_.spec));
365         
366         return new ConcreteEnvironment(moduleMap, spec.toNamespace());
367     }
368     
369     private static void addToNamespace(THashMap<String, Module> moduleMap, 
370             NamespaceSpec namespace, String moduleName, String localName,
371             NamespaceFilter filter) {
372         if(localName.isEmpty())
373             addToNamespace(moduleMap, namespace, moduleName, filter);
374         else
375             addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
376     }
377     
378     private static void addToNamespace(THashMap<String, Module> moduleMap, 
379             NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
380         ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
381         if(moduleImport == null) {
382             Module module = moduleMap.get(moduleName);
383             namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
384             for(ImportDeclaration import_ : module.getDependencies())
385                 if(import_.localName != null) {
386                     NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
387                     if(import_.localName.equals(""))
388                         localFilter = NamespaceFilters.intersection(filter, localFilter);
389                     addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
390                 }
391         }
392         else if(!filter.isSubsetOf(moduleImport.filter)) {
393             moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
394             for(ImportDeclaration import_ : moduleImport.module.getDependencies())
395                 // We have to recheck only modules imported to this namespace
396                 if("".equals(import_.localName)) {
397                     NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
398                     localFilter = NamespaceFilters.intersection(filter, localFilter);
399                     addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
400                 }
401         }
402     }
403
404     public Object getValue(String moduleName, String valueName) throws ValueNotFound {
405         Failable<RuntimeModule> module = getRuntimeModule(moduleName);
406         if(module.didSucceed())
407             return module.getResult().getValue(valueName);
408         else if(module == DoesNotExist.INSTANCE)
409             throw new ValueNotFound("Didn't find module " + moduleName);
410         else
411             throw new ValueNotFound(((Failure)module).toString());
412     }
413
414     public Object getValue(String fullValueName) throws ValueNotFound {
415         int p = fullValueName.lastIndexOf('/');
416         if(p < 0)
417             throw new ValueNotFound(fullValueName + " is not a valid full value name.");
418         return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
419     }
420
421     public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
422         Failable<Module> module = getModule(moduleName);
423         if(module.didSucceed()) {
424             SCLValue value = module.getResult().getValue(valueName);
425             if(value == null)
426                 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
427             return value;
428         }
429         else if(module == DoesNotExist.INSTANCE)
430             throw new ValueNotFound("Didn't find module " + moduleName);
431         else
432             throw new ValueNotFound(((Failure)module).toString());
433     }
434     
435     public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
436         int p = fullValueName.lastIndexOf('/');
437         if(p < 0)
438             throw new ValueNotFound(fullValueName + " is not a valid full value name.");
439         return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
440     }
441     
442     public ModuleSourceRepository getSourceRepository() {
443         return sourceRepository;
444     }
445     
446     public String getDocumentation(String documentationName) {
447         String documentation = sourceRepository.getDocumentation(documentationName);
448         if(documentation == null && parentRepository != null)
449             return parentRepository.getDocumentation(documentationName);
450         return documentation;
451     }
452     
453     public void flush() {
454         if (parentRepository != null)
455             parentRepository.flush();
456         if (moduleCache != null) {
457             for (ModuleEntry entry : moduleCache.values()) {
458                 entry.dispose();
459             }
460             moduleCache.clear();
461         }
462         moduleCache = null;
463     }
464
465     public Map<String, Module> getModules() {
466         Map<String, Module> result = new HashMap<>(moduleCache.size()); 
467         for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
468             ModuleEntry moduleEntry = entry.getValue();
469             if (moduleEntry.compilationResult.didSucceed()) {
470                 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
471             }
472         }
473         return result;
474     }
475
476     public ModuleCompilationOptionsAdvisor getAdvisor() {
477         return advisor;
478     }
479
480     public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
481         this.advisor = advisor;
482     }
483
484 }
485