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