]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling/src/org/simantics/modeling/scl/issue/SCLExpressionIssueProvider.java
Don't recompile all expressions if only one is modified
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / scl / issue / SCLExpressionIssueProvider.java
index bbbd6b01682994142522541ef62737a2bc5138b1..fbba1546ffbc2a0e81a75c95bea69c6e290b9584 100644 (file)
@@ -1,17 +1,25 @@
 package org.simantics.modeling.scl.issue;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 import org.simantics.Simantics;
+import org.simantics.db.Disposable;
 import org.simantics.db.ReadGraph;
 import org.simantics.db.Resource;
+import org.simantics.db.common.procedure.adapter.DisposableListener;
+import org.simantics.db.common.procedure.adapter.DisposableSyncListener;
 import org.simantics.db.common.procedure.adapter.SyncListenerAdapter;
+import org.simantics.db.common.request.TernaryRead;
+import org.simantics.db.common.request.UnaryRead;
 import org.simantics.db.common.request.UniqueRead;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.util.Layer0Utils;
@@ -43,123 +51,326 @@ public class SCLExpressionIssueProvider implements SCLIssueProvider {
     }
 
     private static final Logger LOGGER = LoggerFactory.getLogger(SCLExpressionIssueProvider.class);
-    private List<SCLIssuesTableEntry> currentIssues = new ArrayList<>();
     private boolean disposed = false;
+    private ComponentSyncListenerAdapter listener;
 
     SCLExpressionIssueProvider() {
     }
 
     @Override
     public void listenIssues(Runnable callback) {
-        Simantics.getSession().asyncRequest(new UniqueRead<List<SCLIssuesTableEntry>>() {
-
-            @Override
-            public List<SCLIssuesTableEntry> perform(ReadGraph graph) throws DatabaseException {
-                Layer0 L0 = Layer0.getInstance(graph);
-                Set<Resource> indexRoots = new TreeSet<Resource>();
-                for(Resource ontology : Layer0Utils.listOntologies(graph)) {
-                    if (graph.isInstanceOf(ontology, L0.SharedOntology)) {
-                        indexRoots.add(ontology);
-                    }
+        listener = new ComponentSyncListenerAdapter(callback);
+        Simantics.getSession().asyncRequest(new ComponentRequest(), listener);
+    }
+
+    @Override
+    public List<SCLIssuesTableEntry> getIssues() {
+        return listener.getIssues();
+    }
+
+    @Override
+    public void dispose() {
+        listener.dispose();
+        disposed = true;
+    }
+
+    private static void openResource(Shell shell, Resource resource) {
+        DefaultActions.performDefaultAction(shell, new StructuredSelection(resource));
+    }
+
+    private static class ComponentRequest extends UniqueRead<Set<Resource>> {
+
+        @Override
+        public Set<Resource> perform(ReadGraph graph) throws DatabaseException {
+            Layer0 L0 = Layer0.getInstance(graph);
+            Set<Resource> indexRoots = new TreeSet<Resource>();
+            for(Resource ontology : Layer0Utils.listOntologies(graph)) {
+                if (graph.isInstanceOf(ontology, L0.SharedOntology)) {
+                    indexRoots.add(ontology);
                 }
+            }
 
-                for(Resource child : graph.getObjects(Simantics.getProjectResource(), L0.ConsistsOf)) {
-                    if (graph.isInstanceOf(child, L0.IndexRoot)) {
-                        indexRoots.add(child);
-                    }
+            for(Resource child : graph.getObjects(Simantics.getProjectResource(), L0.ConsistsOf)) {
+                if (graph.isInstanceOf(child, L0.IndexRoot)) {
+                    indexRoots.add(child);
                 }
+            }
 
-                StructuralResource2 STR = StructuralResource2.getInstance(graph);
-
-                List<SCLIssuesTableEntry> results = new ArrayList<>();
-
-                for (Resource ontology : indexRoots) {
-                    List<Resource> components = ModelingUtils.searchByTypeShallow(graph, ontology, STR.Component);
-                    for (Resource component : components) {
-
-                        for (Resource predicate : graph.getPredicates(component)) {
-                            if (graph.isSubrelationOf(predicate, L0.HasProperty)) {
-                                for (Resource object : graph.getObjects(component, predicate)) {
-                                    if (graph.isInstanceOf(object, L0.SCLValue)) {
-                                        Resource type = graph.getPossibleType(object, L0.SCLValue);
-                                        Variable typeVariable = Variables.getVariable(graph, type);
-
-                                        Function1<Variable, String> func = typeVariable.getPossiblePropertyValue(graph, "validator");
-                                        if (func == null) {
-                                            // No validator available
-                                            if (LOGGER.isTraceEnabled())
-                                                LOGGER.trace("No validator available for " + typeVariable.getURI(graph));
-                                            continue;
-                                        }
-
-                                        Variable componentVariable = Variables.getVariable(graph, component);
-                                        Variable propertyVariable = componentVariable.getProperty(graph, predicate);
-
-                                        SCLContext sclContext = SCLContext.getCurrent();
-                                        Object oldGraph = sclContext.get("graph");
-                                        try {
-                                            sclContext.put("graph", graph);
-                                            String validatorValue = func.apply(propertyVariable);
-                                            if (validatorValue != null && !validatorValue.isEmpty()) {
-                                                results.add(new SCLIssuesTableEntry(propertyVariable.getURI(graph), new CompilationError(Locations.NO_LOCATION, validatorValue.replace("\n", " "))) {
-                                                    @Override
-                                                    public void openLocation() {
-                                                        openResource(Display.getCurrent().getActiveShell(), component);
-                                                    }
-                                                });
-                                            }
-                                        } catch (Throwable t) {
-                                            LOGGER.error("Failed to invoke type validator function " + func, t);
-                                        } finally {
-                                            sclContext.put("graph", oldGraph);
-                                        }
-                                    }
-                                }
-                            }
+            StructuralResource2 STR = StructuralResource2.getInstance(graph);
+
+            Set<Resource> allComponents = new HashSet<>();
+            for (Resource ontology : indexRoots) {
+                List<Resource> components = ModelingUtils.searchByTypeShallow(graph, ontology, STR.Component);
+                allComponents.addAll(components);
+            }
+            return allComponents;
+        }
+    }
+    
+    private static class SCLValueRequest extends UnaryRead<Resource, Set<ResourceHolder>> {
+
+        public SCLValueRequest(Resource parameter) {
+            super(parameter);
+        }
+
+        @Override
+        public Set<ResourceHolder> perform(ReadGraph graph) throws DatabaseException {
+            Layer0 L0 = Layer0.getInstance(graph);
+            Set<ResourceHolder> results = new HashSet<>();
+            for (Resource predicate : graph.getPredicates(parameter)) {
+                if (graph.isSubrelationOf(predicate, L0.HasProperty)) {
+                    for (Resource object : graph.getObjects(parameter, predicate)) {
+                        if (graph.isInstanceOf(object, L0.SCLValue)) {
+                            results.add(new ResourceHolder(parameter, predicate, object));
                         }
                     }
                 }
-                return results;
             }
-        }, new SyncListenerAdapter<List<SCLIssuesTableEntry>>() {
+            return results;
+        }
+    }
+    
+    private static class SCLExpressionValidationRequest extends TernaryRead<Resource, Resource, Resource, SCLIssuesTableEntry> {
 
-            @Override
-            public void execute(ReadGraph graph, List<SCLIssuesTableEntry> result) {
-                synchronized (currentIssues) {
-                    currentIssues.clear();
-                    currentIssues.addAll(result);
-                }
-                if (callback != null)
-                    callback.run();
+        public SCLExpressionValidationRequest(Resource component, Resource predicate, Resource object) {
+            super(component, predicate, object);
+        }
+
+        @Override
+        public SCLIssuesTableEntry perform(ReadGraph graph) throws DatabaseException {
+            Resource type = graph.getPossibleType(parameter3, Layer0.getInstance(graph).SCLValue);
+            if (type == null) {
+                return null;
             }
+            if (!graph.hasStatement(parameter))
+                return null;
+            
+            Variable componentVariable = Variables.getVariable(graph, parameter);
+            Variable propertyVariable = componentVariable.getProperty(graph, parameter2);
+            
+            Variable typeVariable = Variables.getVariable(graph, type);
 
-            @Override
-            public void exception(ReadGraph graph, Throwable t) {
-                LOGGER.error("Could not get SCL issues", t);
+            Function1<Variable, String> func = typeVariable.getPossiblePropertyValue(graph, "validator");
+            if (func == null) {
+                // No validator available
+                if (LOGGER.isTraceEnabled())
+                    LOGGER.trace("No validator available for " + typeVariable.getURI(graph));
+                return null;
             }
 
-            @Override
-            public boolean isDisposed() {
-                return disposed;
+            SCLContext sclContext = SCLContext.getCurrent();
+            Object oldGraph = sclContext.get("graph");
+            try {
+                sclContext.put("graph", graph);
+                String validatorValue = func.apply(propertyVariable);
+                if (validatorValue != null && !validatorValue.isEmpty()) {
+                    return new SCLIssuesTableEntry(propertyVariable.getURI(graph), new CompilationError(Locations.NO_LOCATION, validatorValue.replace("\n", " "))) {
+                        @Override
+                        public void openLocation() {
+                            openResource(Display.getCurrent().getActiveShell(), parameter);
+                        }
+                    };
+                }
+            } catch (Throwable t) {
+                LOGGER.error("Failed to invoke type validator function " + func, t);
+            } finally {
+                sclContext.put("graph", oldGraph);
             }
-        });
+            return null;
+        }
     }
+    
+    private static class ComponentSyncListenerAdapter extends SyncListenerAdapter<Set<Resource>> implements Disposable {
 
-    @Override
-    public List<SCLIssuesTableEntry> getIssues() {
-        synchronized (currentIssues) {
-            List<SCLIssuesTableEntry> results = new ArrayList<>(currentIssues);
-            return results;
+        private ConcurrentHashMap<Resource, SCLValueDisposableSyncListener> currentlyListening = new ConcurrentHashMap<>();
+        private boolean disposed;
+        private Runnable callback;
+        
+        public ComponentSyncListenerAdapter(Runnable callback) {
+            this.callback = callback;
+        }
+        
+        @Override
+        public void execute(ReadGraph graph, Set<Resource> newComponents) {
+            if (currentlyListening.isEmpty() && newComponents.isEmpty()) {
+                // we can stop here as nothing will change
+                return;
+            }
+            
+            Set<Resource> removedComponents = new HashSet<>(currentlyListening.keySet());
+            removedComponents.removeAll(newComponents);
+            
+            Set<Resource> addedComponents = new HashSet<>(newComponents);
+            addedComponents.removeAll(currentlyListening.keySet());
+            
+            for (Resource removedComponent : removedComponents) {
+                // stop listening
+                DisposableSyncListener<?> listener = currentlyListening.remove(removedComponent);
+                listener.dispose();
+            }
+            
+            for (Resource addedComponent : addedComponents) {
+                SCLValueDisposableSyncListener listener = new SCLValueDisposableSyncListener(callback);
+                currentlyListening.put(addedComponent, listener);
+                graph.asyncRequest(new SCLValueRequest(addedComponent), listener);
+            }
+        }
+        
+        public List<SCLIssuesTableEntry> getIssues() {
+            List<SCLIssuesTableEntry> issues = new ArrayList<>();
+            for (SCLValueDisposableSyncListener listener : currentlyListening.values()) {
+                List<SCLIssuesTableEntry> listenerIssues = listener.getIssues();
+                if (listenerIssues != null && !listenerIssues.isEmpty())
+                    issues.addAll(listenerIssues);
+            }
+            return issues;
+        }
+        
+        @Override
+        public void dispose() {
+            currentlyListening.values().forEach(l -> l.dispose());
+            currentlyListening.clear();
+            this.disposed = true;
+        }
+        
+        @Override
+        public boolean isDisposed() {
+            return disposed;
         }
     }
+    
+    private static class SCLValueDisposableSyncListener extends DisposableSyncListener<Set<ResourceHolder>> {
 
-    @Override
-    public void dispose() {
-        disposed = true;
+        private Runnable callback;
+        private ConcurrentHashMap<ResourceHolder, SCLIssuesTableEntryDisposableListener> currentlyListeningSCLValues = new ConcurrentHashMap<>();
+        
+        public SCLValueDisposableSyncListener(Runnable callback) {
+            this.callback = callback;
+        }
+        
+        @Override
+        public void execute(ReadGraph graph, Set<ResourceHolder> newComponents) throws DatabaseException {
+            if (currentlyListeningSCLValues.isEmpty() && newComponents.isEmpty()) {
+                // we can stop here as nothing will change
+                return;
+            }
+            
+            
+            Set<ResourceHolder> removedComponents = new HashSet<>(currentlyListeningSCLValues.keySet());
+            removedComponents.removeAll(newComponents);
+            
+            Set<ResourceHolder> addedComponents = new HashSet<>(newComponents);
+            addedComponents.removeAll(currentlyListeningSCLValues.keySet());
+            
+            for (ResourceHolder removedComponent : removedComponents) {
+                // stop listening
+                DisposableListener<?> listener = currentlyListeningSCLValues.remove(removedComponent);
+                listener.dispose();
+            }
+            
+            for (ResourceHolder sclValue : addedComponents) {
+                SCLIssuesTableEntryDisposableListener listener = new SCLIssuesTableEntryDisposableListener(callback);
+                currentlyListeningSCLValues.put(sclValue, listener);
+                graph.syncRequest(new SCLExpressionValidationRequest(sclValue.component, sclValue.predicate, sclValue.object), listener);
+            }
+            if (callback != null) {
+                callback.run();
+            }
+        }
+
+        public List<SCLIssuesTableEntry> getIssues() {
+            if (currentlyListeningSCLValues.isEmpty())
+                return null;
+            List<SCLIssuesTableEntry> issues = new ArrayList<>();
+            for (SCLIssuesTableEntryDisposableListener listener : currentlyListeningSCLValues.values()) {
+                if (listener.getResult() != null)
+                    issues.add(listener.getResult());
+            }
+            return issues;
+        }
+
+        @Override
+        public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
+            LOGGER.error("Could not listen", throwable);
+        }
+        
+        @Override
+        public void dispose() {
+            currentlyListeningSCLValues.values().forEach(l -> l.dispose());
+            currentlyListeningSCLValues.clear();
+            super.dispose();
+        }
+        
     }
+    
+    private static class SCLIssuesTableEntryDisposableListener extends DisposableListener<SCLIssuesTableEntry> {
 
-    private static void openResource(Shell shell, Resource resource) {
-        DefaultActions.performDefaultAction(shell, new StructuredSelection(resource));
+        private SCLIssuesTableEntry result;
+        private Runnable callback;
+        
+        public SCLIssuesTableEntryDisposableListener(Runnable callback) {
+            this.callback = callback;
+        }
+        
+        @Override
+        public void execute(SCLIssuesTableEntry result) {
+            if (!Objects.equals(this.result, result)) {
+                this.result = result;
+                if (callback != null) {
+                    callback.run();
+                }
+            }
+        }
+
+        @Override
+        public void exception(Throwable t) {
+            LOGGER.error("", t);
+        }
+        
+        public SCLIssuesTableEntry getResult() {
+            return result;
+        }
     }
+    
+    private static class ResourceHolder {
+
+        Resource component;
+        Resource predicate;
+        Resource object;
+        
+        public ResourceHolder(Resource component, Resource predicate, Resource object) {
+            this.component = Objects.requireNonNull(component);
+            this.predicate = Objects.requireNonNull(predicate);
+            this.object = Objects.requireNonNull(object);
+        }
 
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + component.hashCode();
+            result = prime * result + object.hashCode();
+            result = prime * result + predicate.hashCode();
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            ResourceHolder other = (ResourceHolder) obj;
+            if (!component.equals(other.component))
+                return false;
+            if (!object.equals(other.object))
+                return false;
+            if (!predicate.equals(other.predicate))
+                return false;
+            return true;
+        }
+
+    }
 }