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