]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java
Merge "Ensure GetElementClassRequest is not constructed without elementFactory"
[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;\r
2 \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
9 \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
37 \r
38 import gnu.trove.map.hash.THashMap;\r
39 import gnu.trove.procedure.TObjectObjectProcedure;\r
40 import gnu.trove.set.hash.THashSet;\r
41 \r
42 /**\r
43  * Manages compilation and caching of SCL modules.\r
44  * \r
45  * @author Hannu Niemistö\r
46  */\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
51     \r
52     private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();\r
53     \r
54     private ModuleCompilationOptionsAdvisor advisor = null;\r
55     \r
56     private static void beginModuleCompilation(String moduleName) {\r
57         THashSet<String> set = PENDING_MODULES.get();\r
58         if(set == null) {\r
59             set = new THashSet<String>();\r
60             PENDING_MODULES.set(set);\r
61         }\r
62         if(!set.add(moduleName))\r
63             throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");\r
64     }\r
65     \r
66     private static void finishModuleCompilation(String moduleName) {\r
67         PENDING_MODULES.get().remove(moduleName);\r
68     }\r
69     \r
70     private class ModuleEntry implements UpdateListener {\r
71         final String moduleName;\r
72         WeakHashMap<UpdateListener,Object> listeners = new WeakHashMap<UpdateListener,Object>();\r
73         \r
74         ModuleSource source;\r
75         Failable<Module> compilationResult;\r
76         Failable<RuntimeModule> runtimeModule; // created lazily\r
77 \r
78         public ModuleEntry(String moduleName) {\r
79             this.moduleName = moduleName;\r
80         }\r
81         \r
82         synchronized void addListener(UpdateListener listener) {\r
83             if(listener != null)\r
84                 listeners.put(listener, null);\r
85         }\r
86         \r
87         @Override\r
88         public void notifyAboutUpdate() {\r
89             if (listeners == null)\r
90                 return;\r
91             ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();\r
92             notifyAboutUpdate(externalListeners);\r
93             for(UpdateListener listener : externalListeners)\r
94                 listener.notifyAboutUpdate();\r
95         }\r
96 \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
104                 }\r
105                 for(UpdateListener l : listeners.keySet())\r
106                     if(l instanceof ModuleEntry)\r
107                         ((ModuleEntry)l).notifyAboutUpdate(externalListeners);\r
108                     else\r
109                         externalListeners.add(l);\r
110             }\r
111         }\r
112 \r
113         private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {\r
114             source = sourceRepository.getModuleSource(moduleName, this);\r
115             \r
116             if(source == null)\r
117                 compilationResult = DoesNotExist.getInstance();\r
118             else {\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
124             }\r
125         \r
126             ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);\r
127             if(oldEntry != null) {\r
128                 oldEntry.addListener(listener);\r
129                 return oldEntry;\r
130             }\r
131             \r
132             addListener(listener);\r
133             return this;\r
134         }\r
135         \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
144                                 .getResult());\r
145                         Collection<ImportDeclaration> dependencies = module.getDependencies();\r
146                         THashMap<String, ModuleEntry> moduleEntries;\r
147                         try {\r
148                             moduleEntries = getModuleEntries(dependencies.toArray(new ImportDeclaration[dependencies.size()]), null);\r
149                         } catch (ImportFailureException e) {\r
150                             throw new InternalCompilerError(e);\r
151                         }\r
152                         for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())\r
153                             parentModules.add(m);\r
154                     }\r
155                     /*for(ImportDeclaration importAst : module.getDependencies()) {\r
156                         RuntimeModule parentModule =\r
157                                 ModuleRepository.this.getRuntimeModule(importAst.moduleName)\r
158                                 .getResult();\r
159                         if(parentModule != null)\r
160                             parentModules.add(parentModule);\r
161                     }*/\r
162                     RuntimeModule rm = new RuntimeModule(module, parentModules, source.getClassLoader());\r
163                     ModuleInitializer initializer = module.getModuleInitializer();\r
164                     if(initializer != null)\r
165                         try {\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
170                         }\r
171                     runtimeModule = new Success<RuntimeModule>(rm); \r
172                 }\r
173                 else\r
174                     runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;\r
175             }\r
176             return runtimeModule;\r
177         }\r
178 \r
179         public void dispose() {\r
180             if (listeners != null)\r
181                 listeners.clear();\r
182             listeners = null;\r
183             source = null;\r
184             compilationResult = null;\r
185             if (runtimeModule != null) {\r
186                 if (runtimeModule.didSucceed())\r
187                     runtimeModule.getResult().dispose();\r
188             }\r
189             runtimeModule = null;\r
190         }\r
191         \r
192         @Override\r
193         public String toString() {\r
194             return "ModuleEntry@" + moduleName + "@" + hashCode();\r
195         }\r
196     }\r
197     \r
198     public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {\r
199         this.parentRepository = parentRepository;\r
200         this.sourceRepository = sourceRepository;\r
201     }\r
202 \r
203     public ModuleRepository(ModuleSourceRepository sourceRepository) {\r
204         this(null, sourceRepository);\r
205     }\r
206     \r
207     public Failable<Module> getModule(String moduleName, UpdateListener listener) {\r
208         return getModuleEntry(moduleName, listener).compilationResult;\r
209     }\r
210     \r
211     public Failable<Module> getModule(String moduleName) {\r
212         return getModule(moduleName, null);\r
213     }\r
214     \r
215     public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {\r
216         return getModuleEntry(moduleName, listener).getRuntimeModule();\r
217     }\r
218     \r
219     public Failable<RuntimeModule> getRuntimeModule(String moduleName) {\r
220         return getRuntimeModule(moduleName, null);\r
221     }\r
222     \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
227          * in it.\r
228          */\r
229         ModuleEntry entry = moduleCache.get(moduleName);\r
230         if(entry == null)\r
231             entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);\r
232         else\r
233             entry.addListener(listener);\r
234 \r
235         if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)\r
236             return parentRepository.getModuleEntry(moduleName, listener);\r
237         else\r
238             return entry;\r
239     }\r
240     \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
246         \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
258                 }\r
259                 else {\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
266                 }\r
267             }\r
268         }\r
269         \r
270         if(failures != null)\r
271             throw new ImportFailureException(failures);\r
272         \r
273         return result;\r
274     }\r
275 \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
279             @Override\r
280             public boolean execute(String a, ModuleEntry b) {\r
281                 result.put(a, b.compilationResult.getResult());\r
282                 return true;\r
283             }\r
284         });\r
285         return result;\r
286     }\r
287     \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
291             @Override\r
292             public boolean execute(String a, ModuleEntry b) {\r
293                 result.put(a, b.getRuntimeModule().getResult());\r
294                 return true;\r
295             }\r
296         });\r
297         return result;\r
298     }\r
299     \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
306     }\r
307     \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
312     }\r
313     \r
314     public RuntimeEnvironment createRuntimeEnvironment(\r
315             EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {\r
316         return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);\r
317     }\r
318     \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
325                 parentClassLoader,\r
326                 listener);\r
327     }\r
328     \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
338     }\r
339     \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
347         \r
348         return new ConcreteEnvironment(moduleMap, spec.toNamespace());\r
349     }\r
350     \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
356         else\r
357             addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);\r
358     }\r
359     \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
372                 }\r
373         }\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
382                 }\r
383         }\r
384     }\r
385 \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
392         else\r
393             throw new ValueNotFound(((Failure)module).toString());\r
394     }\r
395 \r
396     public Object getValue(String fullValueName) throws ValueNotFound {\r
397         int p = fullValueName.lastIndexOf('/');\r
398         if(p < 0)\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
401     }\r
402 \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
407             if(value == null)\r
408                 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");\r
409             return value;\r
410         }\r
411         else if(module == DoesNotExist.INSTANCE)\r
412             throw new ValueNotFound("Didn't find module " + moduleName);\r
413         else\r
414             throw new ValueNotFound(((Failure)module).toString());\r
415     }\r
416     \r
417     public SCLValue getValueRef(String fullValueName) throws ValueNotFound {\r
418         int p = fullValueName.lastIndexOf('/');\r
419         if(p < 0)\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
422     }\r
423     \r
424     public ModuleSourceRepository getSourceRepository() {\r
425         return sourceRepository;\r
426     }\r
427     \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
433     }\r
434     \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
440                 entry.dispose();\r
441             }\r
442             moduleCache.clear();\r
443         }\r
444         moduleCache = null;\r
445     }\r
446 \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
453             }\r
454         }\r
455         return result;\r
456     }\r
457 \r
458     public ModuleCompilationOptionsAdvisor getAdvisor() {\r
459         return advisor;\r
460     }\r
461 \r
462     public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {\r
463         this.advisor = advisor;\r
464     }\r
465 \r
466 }\r
467