]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Fixed memory leaks of SCL module listening systems 74/274/2
authorHannu Niemistö <hannu.niemisto@semantum.fi>
Wed, 18 Jan 2017 15:55:14 +0000 (17:55 +0200)
committerHannu Niemistö <hannu.niemisto@semantum.fi>
Wed, 18 Jan 2017 15:55:54 +0000 (17:55 +0200)
The main addition in this change are backlinks from UpdateListeners to
Observables. They make it possible to implement
UpdateListener.stopListening that removes the listener from all
Observables. This method is called automatically before the listener is
notified about the change, but client code can also call it when it
becames disinterested about changes.

Because now the client code is reposible for signaling when the
listening stops, the listeners are no more store to WeakHashMap in
ModuleEntry.

This change also contains a change in the default imports of some graph
based modules (Prelude -> StandardLibrary).

refs #6943

Change-Id: I7cf8d12b610ab22a70300d9491b645fa34dce620

bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest.java
bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest2.java
bundles/org.simantics.modeling/src/org/simantics/modeling/ComponentTypeScriptRuntimeEnvironmentRequest.java
bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/UpdateListener.java
bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLAnnotationModel.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesContentProvider.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesView.java

index 6fbe36854ad99bd8934bd843a094be4742496e7e..cde4e02396a48515a31ca777e1265870e8903cc8 100644 (file)
@@ -1,8 +1,5 @@
 package org.simantics.db.layer0.util;
 
-import java.util.HashMap;
-import java.util.Map;
-
 import org.simantics.db.ReadGraph;
 import org.simantics.db.Resource;
 import org.simantics.db.common.request.ParametrizedPrimitiveRead;
@@ -33,7 +30,7 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
     protected void fillEnvironmentSpecification(EnvironmentSpecification environmentSpecification) {
     }
 
-    static class UpdateListenerImpl implements UpdateListener {
+    static class UpdateListenerImpl extends UpdateListener {
                
        final EnvironmentSpecification environmentSpecification;
        final Listener<RuntimeEnvironment> callback;
@@ -46,68 +43,64 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
         @Override
         public void notifyAboutUpdate() {
                if(callback.isDisposed()) {
+                   stopListening();
                        return;
                }
                getRuntimeEnvironment(environmentSpecification, callback, this);
         }
+    };     
 
-       final public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback, UpdateListenerImpl listener) {
-
-                       try {
-                               
-                       SCLContext context = SCLContext.getCurrent();
-                       
-                       RuntimeEnvironment env;
-                       Object graph = context.get("graph");
-                       if(graph == null)
-                    try {
-                        env = SimanticsInternal.getSession().syncRequest(new Read<RuntimeEnvironment>() {
-                            @Override
-                            public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException {
-                               
-                                SCLContext sclContext = SCLContext.getCurrent();
-                               Object oldGraph = sclContext.get("graph");
-                                try {
-                                       sclContext.put("graph", graph);
-                                    return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
-                                            environmentSpecification,
-                                            callback.getClass().getClassLoader(), listener);
-                                } catch (ImportFailureException e) {
-                                    throw new DatabaseException(e);
-                                } catch (Throwable t) {
-                                    throw new DatabaseException(t);
-                                } finally {
-                                    sclContext.put("graph", oldGraph);
-                                }
+    public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback, UpdateListenerImpl listener) {
+
+        try {
+            
+            SCLContext context = SCLContext.getCurrent();
+            
+            RuntimeEnvironment env;
+            Object graph = context.get("graph");
+            if(graph == null)
+                try {
+                    env = SimanticsInternal.getSession().syncRequest(new Read<RuntimeEnvironment>() {
+                        @Override
+                        public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException {
+                            
+                            SCLContext sclContext = SCLContext.getCurrent();
+                            Object oldGraph = sclContext.get("graph");
+                            try {
+                                sclContext.put("graph", graph);
+                                return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
+                                        environmentSpecification,
+                                        callback.getClass().getClassLoader(), listener);
+                            } catch (ImportFailureException e) {
+                                throw new DatabaseException(e);
+                            } catch (Throwable t) {
+                                throw new DatabaseException(t);
+                            } finally {
+                                sclContext.put("graph", oldGraph);
                             }
-                        });
-                    } catch (DatabaseException e) {
-                        callback.exception(e);
-                        return;
-                    }
-                else 
-                           env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
-                                   environmentSpecification,
-                                   callback.getClass().getClassLoader(), listener);
-                       callback.execute(env);
-                       } catch (ImportFailureException e) {
-                               callback.exception(new DatabaseException(e));
-                       }
-
-       }
-        
-    };     
+                        }
+                    });
+                } catch (DatabaseException e) {
+                    callback.exception(e);
+                    return;
+                }
+            else 
+                env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
+                        environmentSpecification,
+                        callback.getClass().getClassLoader(), listener);
+            callback.execute(env);
+        } catch (ImportFailureException e) {
+            callback.exception(new DatabaseException(e));
+        }
 
-    // This is needed to prevent garbage collection from collecting UpdateListenerImpls
-    // -ModuleRepository only makes a weak reference to the listener
-    final static Map<String, UpdateListenerImpl> map = new HashMap<String, UpdateListenerImpl>(); 
+    }
     
     @Override
     public RuntimeEnvironment perform(ReadGraph graph)
             throws DatabaseException {
         final EnvironmentSpecification environmentSpecification = EnvironmentSpecification.of(
                 "Builtin", "",
-                "Prelude", "",
+                "StandardLibrary", "",
                 "Simantics/All", "");
         fillEnvironmentSpecification(environmentSpecification);
         Resource mainModule = Layer0Utils.getPossibleChild(graph, parameter, "SCLMain");
@@ -120,7 +113,9 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
             mainModuleUri = graph.getURI(parameter) + "/#"; // Add something dummy to the model uri that cannot be in a real URI
         
             return graph.syncRequest(new ParametrizedPrimitiveRead<String, RuntimeEnvironment>(mainModuleUri) {
-               
+                
+                UpdateListenerImpl sclListener;
+                
                @Override
                public void register(ReadGraph graph, Listener<RuntimeEnvironment> procedure) {
 
@@ -129,12 +124,11 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
                        try {
 
                                if(procedure.isDisposed()) {
-                                       UpdateListenerImpl.getRuntimeEnvironment(environmentSpecification, procedure, null);
+                                       getRuntimeEnvironment(environmentSpecification, procedure, null);
                                } else {
-                                       UpdateListenerImpl impl = new UpdateListenerImpl(environmentSpecification, procedure);
-                                       impl.notifyAboutUpdate();
-                                               map.put(parameter, impl);
-                               }
+                                   sclListener = new UpdateListenerImpl(environmentSpecification, procedure);
+                                   sclListener.notifyAboutUpdate();
+                                       }
 
                        } finally {
                                context.put("graph", oldGraph);
@@ -144,7 +138,8 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
                 
                 @Override
                 public void unregistered() {
-                       map.remove(parameter);
+                       if(sclListener != null)
+                           sclListener.stopListening();
                 }
                 
             });
@@ -155,8 +150,4 @@ public class RuntimeEnvironmentRequest extends UnaryRead<Resource, RuntimeEnviro
         return 31*getClass().hashCode() + super.hashCode();
     }
 
-    public static void flush() {
-        map.clear();
-    }
-
 }
index 75308cc18866f229efcebcd1464ef5ca02d5e1cd..4bbd480020141b5e602c9e7f73fb1b8170410995 100644 (file)
@@ -36,131 +36,123 @@ public class RuntimeEnvironmentRequest2 extends BinaryRead<Resource, Resource, R
     protected void fillEnvironmentSpecification(EnvironmentSpecification environmentSpecification) {
     }
 
-    static class UpdateListenerImpl implements UpdateListener {
-          
-     final EnvironmentSpecification environmentSpecification;
-     final Listener<RuntimeEnvironment> callback;
-     
-     UpdateListenerImpl(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback) {
-      this.environmentSpecification = environmentSpecification;
-      this.callback = callback;
-     }
+    static class UpdateListenerImpl extends UpdateListener {
+
+        final EnvironmentSpecification environmentSpecification;
+        final Listener<RuntimeEnvironment> callback;
+
+        UpdateListenerImpl(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback) {
+            this.environmentSpecification = environmentSpecification;
+            this.callback = callback;
+        }
 
         @Override
         public void notifyAboutUpdate() {
-         if(callback.isDisposed()) {
-          return;
-         }
-         getRuntimeEnvironment(environmentSpecification, callback, this);
+            if(callback.isDisposed()) {
+                stopListening();
+                return;
+            }
+            getRuntimeEnvironment(environmentSpecification, callback, this);
+        }
+    };
+
+    final public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback, UpdateListenerImpl listener) {
+
+        try {
+
+            SCLContext context = SCLContext.getCurrent();
+
+            RuntimeEnvironment env;
+            Object graph = context.get("graph");
+            if(graph == null)
+                try {
+                    env = SimanticsInternal.getSession().syncRequest(new Read<RuntimeEnvironment>() {
+                        @Override
+                        public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException {
+
+                            SCLContext sclContext = SCLContext.getCurrent();
+                            Object oldGraph = sclContext.get("graph");
+                            try {
+                                sclContext.put("graph", graph);
+                                return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
+                                        environmentSpecification,
+                                        callback.getClass().getClassLoader(), listener);
+                            } catch (ImportFailureException e) {
+                                throw new DatabaseException(e);
+                            } catch (Throwable t) {
+                                throw new DatabaseException(t);
+                            } finally {
+                                sclContext.put("graph", oldGraph);
+                            }
+                        }
+                    });
+                } catch (DatabaseException e) {
+                    callback.exception(e);
+                    return;
+                }
+            else 
+                env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
+                        environmentSpecification,
+                        callback.getClass().getClassLoader(), listener);
+            callback.execute(env);
+        } catch (ImportFailureException e) {
+            callback.exception(new DatabaseException(e));
         }
 
-     final public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener<RuntimeEnvironment> callback, UpdateListenerImpl listener) {
+    }
 
-   try {
-    
-          SCLContext context = SCLContext.getCurrent();
-          
-          RuntimeEnvironment env;
-          Object graph = context.get("graph");
-          if(graph == null)
-                    try {
-                        env = SimanticsInternal.getSession().syncRequest(new Read<RuntimeEnvironment>() {
-                            @Override
-                            public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException {
-                             
-                                SCLContext sclContext = SCLContext.getCurrent();
-                             Object oldGraph = sclContext.get("graph");
-                                try {
-                                 sclContext.put("graph", graph);
-                                    return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
-                                            environmentSpecification,
-                                            callback.getClass().getClassLoader(), listener);
-                                } catch (ImportFailureException e) {
-                                    throw new DatabaseException(e);
-                                } catch (Throwable t) {
-                                    throw new DatabaseException(t);
-                                } finally {
-                                    sclContext.put("graph", oldGraph);
-                                }
-                            }
-                        });
-                    } catch (DatabaseException e) {
-                        callback.exception(e);
-                        return;
-                    }
-                else 
-              env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment(
-                      environmentSpecification,
-                      callback.getClass().getClassLoader(), listener);
-          callback.execute(env);
-   } catch (ImportFailureException e) {
-    callback.exception(new DatabaseException(e));
-   }
-
-     }
-        
-    };     
-
-    // This is needed to prevent garbage collection from collecting UpdateListenerImpls
-    // -ModuleRepository only makes a weak reference to the listener
-    final static Map<EnvironmentSpecification, UpdateListenerImpl> map = new HashMap<>(); 
-    
     @Override
     public RuntimeEnvironment perform(ReadGraph graph)
             throws DatabaseException {
         final EnvironmentSpecification environmentSpecification = EnvironmentSpecification.of(
                 "Builtin", "",
-                "Prelude", "",
+                "StandardLibrary", "",
                 "Simantics/All", "");
         fillEnvironmentSpecification(environmentSpecification);
-        
+
         Layer0 L0 = Layer0.getInstance(graph);
         Collection<Resource> sclModules = graph.syncRequest(new ObjectsWithType(parameter, L0.ConsistsOf, L0.SCLModule));
         for (Resource sclModule : sclModules)
             environmentSpecification.importModule(graph.getURI(sclModule), "");
-        
+
         Resource mainModule = Layer0Utils.getPossibleChild(graph, parameter2, "SCLMain");
         if(mainModule != null)
             environmentSpecification.importModule(graph.getURI(mainModule), "");
-        
+
         return graph.syncRequest(new ParametrizedPrimitiveRead<EnvironmentSpecification, RuntimeEnvironment>(environmentSpecification) {
-         
-         @Override
-         public void register(ReadGraph graph, Listener<RuntimeEnvironment> procedure) {
-
-          SCLContext context = SCLContext.getCurrent();
-          Object oldGraph = context.put("graph", graph);
-          try {
-
-           if(procedure.isDisposed()) {
-            UpdateListenerImpl.getRuntimeEnvironment(parameter, procedure, null);
-           } else {
-            UpdateListenerImpl impl = new UpdateListenerImpl(parameter, procedure);
-            impl.notifyAboutUpdate();
-            map.put(parameter, impl);
-           }
-
-          } finally {
-           context.put("graph", oldGraph);
-          }
-
-         }
-            
+            UpdateListenerImpl sclListener;
+            @Override
+            public void register(ReadGraph graph, Listener<RuntimeEnvironment> procedure) {
+
+                SCLContext context = SCLContext.getCurrent();
+                Object oldGraph = context.put("graph", graph);
+                try {
+
+                    if(procedure.isDisposed()) {
+                        getRuntimeEnvironment(parameter, procedure, null);
+                    } else {
+                        sclListener = new UpdateListenerImpl(parameter, procedure);
+                        sclListener.notifyAboutUpdate();
+                    }
+
+                } finally {
+                    context.put("graph", oldGraph);
+                }
+
+            }
+
             @Override
             public void unregistered() {
-             map.remove(parameter);
+                if(sclListener != null)
+                    sclListener.stopListening();
             }
-            
+
         });
     }
-    
+
     @Override
     public int hashCode() {
         return 31*getClass().hashCode() + super.hashCode();
     }
 
-    public static void flush() {
-        map.clear();
-    }
-
 }
\ No newline at end of file
index 7f2b54559ed35f0ecb7c077fe97c6131a8ddca82..89385e5be3f111439be954cac24278c62f256f30 100644 (file)
@@ -1,7 +1,5 @@
 package org.simantics.modeling;
 
-import java.util.ArrayList;
-
 import org.simantics.db.ReadGraph;
 import org.simantics.db.common.request.ParametrizedPrimitiveRead;
 import org.simantics.db.procedure.Listener;
@@ -13,9 +11,7 @@ import org.simantics.scl.osgi.SCLOsgi;
 import org.simantics.scl.runtime.SCLContext;
 
 public class ComponentTypeScriptRuntimeEnvironmentRequest extends ParametrizedPrimitiveRead<EnvironmentSpecification, RuntimeEnvironment> {
-
-    // This array list is only needed to keep strong references to update listeners preventing their garbage collection.
-    ArrayList<UpdateListener> listeners;
+    UpdateListener listener;
     
     public ComponentTypeScriptRuntimeEnvironmentRequest(EnvironmentSpecification parameter) {
         super(parameter);
@@ -23,20 +19,22 @@ public class ComponentTypeScriptRuntimeEnvironmentRequest extends ParametrizedPr
 
     @Override
     public void register(ReadGraph graph, Listener<RuntimeEnvironment> procedure) {
-        UpdateListener listener = null;
-        if(!procedure.isDisposed()) {
+        if(!procedure.isDisposed() && listener == null) {
             listener = new UpdateListener() {
                 @Override
                 public void notifyAboutUpdate() {
                     createRuntimeEnvironment(graph, procedure, this);
                 }
             };
-            if(listeners == null)
-                listeners = new ArrayList<UpdateListener>(2);
-            listeners.add(listener);
         }
         createRuntimeEnvironment(graph, procedure, listener);
     }
+
+    @Override
+    public void unregistered() {
+        if(listener != null)
+            listener.stopListening();
+    }
     
     private void createRuntimeEnvironment(ReadGraph graph, Listener<RuntimeEnvironment> procedure, UpdateListener listener) {
         SCLContext context = SCLContext.getCurrent();
index 10a217b9da073918575c19bacf98f2925619688e..07af09e557109046575ee8556abf20c5cca6ec84 100644 (file)
@@ -16,12 +16,13 @@ import org.simantics.db.request.Read;
 import org.simantics.layer0.Layer0;
 import org.simantics.modeling.ModelingUtils;
 import org.simantics.scl.compiler.module.repository.UpdateListener;
+import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
 import org.simantics.scl.compiler.source.ModuleSource;
 import org.simantics.scl.compiler.source.StringModuleSource;
 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
 import org.simantics.scl.runtime.SCLContext;
-import org.simantics.structural2.utils.StructuralUtils;
 import org.simantics.scl.runtime.tuple.Tuple0;
+import org.simantics.structural2.utils.StructuralUtils;
 
 import gnu.trove.procedure.TObjectProcedure;
 import gnu.trove.set.hash.THashSet;
@@ -54,15 +55,20 @@ public enum GraphModuleSourceRepository implements ModuleSourceRepository {
         }
     }
 
-    static class ModuleListener implements SyncListener<ModuleSource> {
+    static class ModuleListener implements SyncListener<ModuleSource>, Observable {
         UpdateListener listener;
         boolean alreadyExecutedOnce;
         final String moduleName;
         public ModuleListener(UpdateListener listener, String moduleName) {
             this.listener = listener;
+            this.listener.addObservable(this);
             this.moduleName = moduleName;
         }
         @Override
+        public void removeListener(UpdateListener listener) {
+            listener = null;
+        }
+        @Override
         public boolean isDisposed() {
             return listener == null;
         }
index b851810e375a3f306a6224f7c2d8aa2691780c5e..7170b992b41d87be51e0e615d4723985bb03478e 100644 (file)
@@ -4,7 +4,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
@@ -24,6 +23,7 @@ import org.simantics.scl.compiler.errors.Success;
 import org.simantics.scl.compiler.module.ImportDeclaration;
 import org.simantics.scl.compiler.module.Module;
 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
+import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
 import org.simantics.scl.compiler.runtime.RuntimeModule;
@@ -67,9 +67,9 @@ public class ModuleRepository {
         PENDING_MODULES.get().remove(moduleName);
     }
     
-    private class ModuleEntry implements UpdateListener {
+    private class ModuleEntry extends UpdateListener implements Observable {
         final String moduleName;
-        WeakHashMap<UpdateListener,Object> listeners = new WeakHashMap<UpdateListener,Object>();
+        THashSet<UpdateListener> listeners = new THashSet<UpdateListener>();
         
         ModuleSource source;
         Failable<Module> compilationResult;
@@ -80,14 +80,20 @@ public class ModuleRepository {
         }
         
         synchronized void addListener(UpdateListener listener) {
-            if(listener != null)
-                listeners.put(listener, null);
+            if(listener == null || listeners == null)
+                return;
+            listeners.add(listener);
+            listener.addObservable(this);
+        }
+
+        public synchronized void removeListener(UpdateListener listener) {
+            if (listeners == null)
+                return;
+            listeners.remove(listener);
         }
         
         @Override
         public void notifyAboutUpdate() {
-            if (listeners == null)
-                return;
             ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
             notifyAboutUpdate(externalListeners);
             for(UpdateListener listener : externalListeners)
@@ -95,18 +101,26 @@ public class ModuleRepository {
         }
 
         synchronized void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
+            stopListening();
+            if (listeners == null)
+                return;
             if(moduleCache.get(moduleName) == this) {
                 moduleCache.remove(moduleName);
                 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
                     System.out.println("Invalidate " + moduleName);
-                    for(UpdateListener l : listeners.keySet())
+                    for(UpdateListener l : listeners)
                         System.out.println("    " + l);
                 }
-                for(UpdateListener l : listeners.keySet())
+                THashSet<UpdateListener> listenersCopy = listeners;
+                listeners = null;
+                for(UpdateListener l : listenersCopy)
+                    l.stopListening();
+                for(UpdateListener l : listenersCopy)
                     if(l instanceof ModuleEntry)
                         ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
-                    else
+                    else {
                         externalListeners.add(l);
+                    }
             }
         }
 
@@ -176,10 +190,11 @@ public class ModuleRepository {
             return runtimeModule;
         }
 
-        public void dispose() {
+        public synchronized void dispose() {
             if (listeners != null)
                 listeners.clear();
             listeners = null;
+            stopListening();
             source = null;
             compilationResult = null;
             if (runtimeModule != null) {
@@ -219,7 +234,7 @@ public class ModuleRepository {
     public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
         return getRuntimeModule(moduleName, null);
     }
-    
+
     private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
         /* It is deliberate that the following code does not try to prevent
          * simultaneous compilation of the same module. This is because in
@@ -244,13 +259,16 @@ public class ModuleRepository {
         THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
         Collection<ImportFailure> failures = null;
         
+        THashSet<String> originalImports = new THashSet<String>(); 
         ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
-        for(ImportDeclaration import_ : imports)
+        for(ImportDeclaration import_ : imports) {
             stack.add(import_);
+            originalImports.add(import_.moduleName);
+        }
         while(!stack.isEmpty()) {
             ImportDeclaration import_ = stack.remove(stack.size()-1);
             if(!result.containsKey(import_.moduleName)) {
-                ModuleEntry entry = getModuleEntry(import_.moduleName, listener);
+                ModuleEntry entry = getModuleEntry(import_.moduleName, originalImports.contains(import_.moduleName) ? listener : null);
                 Failable<Module> compilationResult = entry.compilationResult;
                 if(compilationResult.didSucceed()) {
                     result.put(import_.moduleName, entry);
index 7320758e48f6a2c1f6f1208db93b90c1b4fb4c96..4e3ad119d77bc5a686577e00090bea4b177a6305 100644 (file)
@@ -1,5 +1,41 @@
 package org.simantics.scl.compiler.module.repository;
 
-public interface UpdateListener {
-    void notifyAboutUpdate();
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * Listener that is notified about changes in modules and their dependencies. It is possible
+ * to listen multiple different modules with one listener. When a change happens, the listener
+ * automatically stops listening any other changes. The idea is that the client then asks all modules
+ * again using the listener as a parameter.
+ */
+public abstract class UpdateListener {
+    private final THashSet<Observable> observables = new THashSet<Observable>();  
+    
+    public interface Observable {
+        void removeListener(UpdateListener listener);
+    }
+    
+    public abstract void notifyAboutUpdate();
+    
+    /**
+     * Registers an observable to the listener. The client code should never
+     * call this method. It is needed so that it is possible to implement
+     * {@link #stopListening}.
+     */
+    public void addObservable(Observable observable) {
+        synchronized(observables) {
+            observables.add(observable);
+        }
+    }
+
+    /**
+     * Stops listening changes. 
+     */
+    public void stopListening() {
+        synchronized(observables) {
+            for(Observable observable : observables)
+                observable.removeListener(this);
+            observables.clear();
+        }
+    }
 }
index 1f60de08688f8365c52ed78a55e378dff162d032..244c7463f0d5ade4ba574a373fd13ee9940a99b0 100644 (file)
@@ -10,7 +10,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
 import java.util.Arrays;
 
 import org.eclipse.core.runtime.FileLocator;
@@ -21,7 +20,9 @@ import org.simantics.scl.compiler.module.repository.UpdateListener;
 import org.simantics.scl.compiler.source.EncodedTextualModuleSource;
 import org.simantics.scl.compiler.types.Type;
 
-public class BundleModuleSource extends EncodedTextualModuleSource {
+import gnu.trove.set.hash.THashSet;
+
+public class BundleModuleSource extends EncodedTextualModuleSource implements UpdateListener.Observable {
 
     public static final ImportDeclaration[] DEFAULT_IMPORTS = new ImportDeclaration[] {
         new ImportDeclaration("Builtin", ""),
@@ -36,13 +37,21 @@ public class BundleModuleSource extends EncodedTextualModuleSource {
     public final URL url;
     
     private byte[] digest;
-    private ArrayList<UpdateListener> listeners;
+    private THashSet<UpdateListener> listeners;
     
     public BundleModuleSource(String moduleName, Bundle bundle, URL url) {
         super(moduleName);
         this.bundle = bundle;
         this.url = url;
     }
+    
+    @Override
+    public void removeListener(UpdateListener listener) {
+        if(listeners != null)
+            synchronized(listeners) {
+                listeners.remove(listener);
+            }
+    }
 
     @Override
     protected ImportDeclaration[] getBuiltinImports(UpdateListener listener) {
@@ -84,8 +93,9 @@ public class BundleModuleSource extends EncodedTextualModuleSource {
             digest = computeDigest();
         if(listener != null) {
             if(listeners == null)
-                listeners = new ArrayList<UpdateListener>(2);
+                listeners = new THashSet<UpdateListener>(4);
             listeners.add(listener);
+            listener.addObservable(this);
         }
         return url.openStream();
     }
@@ -108,7 +118,7 @@ public class BundleModuleSource extends EncodedTextualModuleSource {
             byte[] newDigest = computeDigest();
             if(!Arrays.equals(digest, newDigest)) {
                 digest = newDigest;
-                ArrayList<UpdateListener> oldListeners = listeners;
+                THashSet<UpdateListener> oldListeners = listeners;
                 listeners = null;
                 for(UpdateListener listener : oldListeners)
                     listener.notifyAboutUpdate();
index 47b203d613e1b871e8a3762f008c11906fb3298b..e432a179aa2a9258b440271210ae34f29417197e 100644 (file)
@@ -70,6 +70,7 @@ public class SCLAnnotationModel extends AnnotationModel {
     @Override
     public void disconnect(IDocument document) {
         connected = false;
+        updateListener.stopListening();
         super.disconnect(document);
     }
 }
index eb5c25e4ac0ac3f402b7c78c466db586065a05fc..7a5daffdbee0716670932e1358a3f227cf2f8dc4 100644 (file)
@@ -16,6 +16,7 @@ import org.simantics.scl.compiler.module.repository.ModuleRepository;
 import org.simantics.scl.compiler.module.repository.UpdateListener;
 
 import gnu.trove.map.hash.THashMap;
+import gnu.trove.procedure.TObjectObjectProcedure;
 import gnu.trove.procedure.TObjectProcedure;
 
 public class SCLIssuesContentProvider implements IStructuredContentProvider {
@@ -39,16 +40,19 @@ public class SCLIssuesContentProvider implements IStructuredContentProvider {
     }
     
     private UpdateListener getUpdateListener(String moduleName) {
-        UpdateListener listener = updateListeners.get(moduleName);
-        if(listener == null) {
-            listener = new UpdateListener() {
-                @Override
-                public void notifyAboutUpdate() {
-                    if(!disposed)
-                        listenModule(moduleName);
-                }
-            };
-            updateListeners.put(moduleName, listener);
+        UpdateListener listener;
+        synchronized(updateListeners) {
+            listener = updateListeners.get(moduleName);
+            if(listener == null) {
+                listener = new UpdateListener() {
+                    @Override
+                    public void notifyAboutUpdate() {
+                        if(!disposed)
+                            listenModule(moduleName);
+                    }
+                };
+                updateListeners.put(moduleName, listener);
+            }
         }
         return listener;
     }
@@ -106,7 +110,20 @@ public class SCLIssuesContentProvider implements IStructuredContentProvider {
 
     @Override
     public void dispose() {
+        if(this.disposed)
+            return;
         this.disposed = true;
+        if(repository != null)
+            synchronized(updateListeners) {
+                updateListeners.forEachEntry(new TObjectObjectProcedure<String, UpdateListener>() {
+                    @Override
+                    public boolean execute(String moduleName, UpdateListener listener) {
+                        listener.stopListening();
+                        return true;
+                    }
+                });
+                updateListeners.clear();
+            }
     }
 
     @Override
index adc7def7902191c30158494ab8ba45e4e06ee556..112a5f4712e96c7aa054370964f9b232ea979f05 100644 (file)
@@ -40,6 +40,8 @@ public class SCLIssuesView extends ViewPart {
 
     ImageRegistry imageRegistry;
     
+    SCLIssuesContentProvider issuesContentProvider = new SCLIssuesContentProvider();
+    
     public SCLIssuesView() {
         super();
         imageRegistry = Activator.getInstance().getImageRegistry();
@@ -66,7 +68,7 @@ public class SCLIssuesView extends ViewPart {
         tableViewer = new TableViewer(parent,
                 SWT.FULL_SELECTION | SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL);
         ColumnViewerToolTipSupport.enableFor(tableViewer);
-        tableViewer.setContentProvider(new SCLIssuesContentProvider());
+        tableViewer.setContentProvider(issuesContentProvider);
 
         Table table = tableViewer.getTable();
         table.setHeaderVisible(true);
@@ -165,5 +167,11 @@ public class SCLIssuesView extends ViewPart {
     public void setFocus() {
         tableViewer.getControl().setFocus();
     }
+    
+    @Override
+    public void dispose() {
+        super.dispose();
+        issuesContentProvider.dispose();
+    }
 
 }