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