]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling/src/org/simantics/modeling/scl/issue/SCLExpressionIssueProvider.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / scl / issue / SCLExpressionIssueProvider.java
1 package org.simantics.modeling.scl.issue;
2
3 import java.util.ArrayList;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Objects;
7 import java.util.Set;
8 import java.util.TreeSet;
9 import java.util.concurrent.ConcurrentHashMap;
10
11 import org.eclipse.jface.viewers.StructuredSelection;
12 import org.eclipse.swt.widgets.Display;
13 import org.eclipse.swt.widgets.Shell;
14 import org.simantics.Simantics;
15 import org.simantics.db.Disposable;
16 import org.simantics.db.ReadGraph;
17 import org.simantics.db.Resource;
18 import org.simantics.db.common.procedure.adapter.DisposableListener;
19 import org.simantics.db.common.procedure.adapter.DisposableSyncListener;
20 import org.simantics.db.common.procedure.adapter.SyncListenerAdapter;
21 import org.simantics.db.common.request.TernaryRead;
22 import org.simantics.db.common.request.UnaryRead;
23 import org.simantics.db.common.request.UniqueRead;
24 import org.simantics.db.exception.DatabaseException;
25 import org.simantics.db.layer0.util.Layer0Utils;
26 import org.simantics.db.layer0.variable.Variable;
27 import org.simantics.db.layer0.variable.Variables;
28 import org.simantics.layer0.Layer0;
29 import org.simantics.modeling.ModelingUtils;
30 import org.simantics.scl.compiler.errors.CompilationError;
31 import org.simantics.scl.compiler.errors.Locations;
32 import org.simantics.scl.osgi.issues.SCLIssueProviderFactory;
33 import org.simantics.scl.osgi.issues.SCLIssueProviderFactory.SCLIssueProvider;
34 import org.simantics.scl.osgi.issues.SCLIssuesTableEntry;
35 import org.simantics.scl.runtime.SCLContext;
36 import org.simantics.scl.runtime.function.Function1;
37 import org.simantics.structural.stubs.StructuralResource2;
38 import org.simantics.ui.workbench.action.DefaultActions;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 public class SCLExpressionIssueProvider implements SCLIssueProvider {
43
44     public static class SCLExpressionIssueProviderFactory implements SCLIssueProviderFactory {
45
46         @Override
47         public SCLIssueProvider getSCLIssueProvider() {
48             return new SCLExpressionIssueProvider();
49         }
50
51     }
52
53     private static final Logger LOGGER = LoggerFactory.getLogger(SCLExpressionIssueProvider.class);
54     private boolean disposed = false;
55     private ComponentSyncListenerAdapter listener;
56
57     SCLExpressionIssueProvider() {
58     }
59
60     @Override
61     public void listenIssues(Runnable callback) {
62         listener = new ComponentSyncListenerAdapter(callback);
63         Simantics.getSession().asyncRequest(new ComponentRequest(), listener);
64     }
65
66     @Override
67     public List<SCLIssuesTableEntry> getIssues() {
68         return listener.getIssues();
69     }
70
71     @Override
72     public void dispose() {
73         listener.dispose();
74         disposed = true;
75     }
76
77     private static void openResource(Shell shell, Resource resource) {
78         DefaultActions.performDefaultAction(shell, new StructuredSelection(resource));
79     }
80
81     private static class ComponentRequest extends UniqueRead<Set<Resource>> {
82
83         @Override
84         public Set<Resource> perform(ReadGraph graph) throws DatabaseException {
85             Layer0 L0 = Layer0.getInstance(graph);
86             Set<Resource> indexRoots = new TreeSet<Resource>();
87             for(Resource ontology : Layer0Utils.listOntologies(graph)) {
88                 if (graph.isInstanceOf(ontology, L0.SharedOntology)) {
89                     indexRoots.add(ontology);
90                 }
91             }
92
93             for(Resource child : graph.getObjects(Simantics.getProjectResource(), L0.ConsistsOf)) {
94                 if (graph.isInstanceOf(child, L0.IndexRoot)) {
95                     indexRoots.add(child);
96                 }
97             }
98
99             StructuralResource2 STR = StructuralResource2.getInstance(graph);
100
101             Set<Resource> allComponents = new HashSet<>();
102             for (Resource ontology : indexRoots) {
103                 List<Resource> components = ModelingUtils.searchByTypeShallow(graph, ontology, STR.Component);
104                 allComponents.addAll(components);
105             }
106             return allComponents;
107         }
108     }
109     
110     private static class SCLValueRequest extends UnaryRead<Resource, Set<ResourceHolder>> {
111
112         public SCLValueRequest(Resource parameter) {
113             super(parameter);
114         }
115
116         @Override
117         public Set<ResourceHolder> perform(ReadGraph graph) throws DatabaseException {
118             Layer0 L0 = Layer0.getInstance(graph);
119             Set<ResourceHolder> results = new HashSet<>();
120             for (Resource predicate : graph.getPredicates(parameter)) {
121                 if (graph.isSubrelationOf(predicate, L0.HasProperty)) {
122                     for (Resource object : graph.getObjects(parameter, predicate)) {
123                         if (graph.isInstanceOf(object, L0.SCLValue)) {
124                             results.add(new ResourceHolder(parameter, predicate, object));
125                         }
126                     }
127                 }
128             }
129             return results;
130         }
131     }
132     
133     private static class SCLExpressionValidationRequest extends TernaryRead<Resource, Resource, Resource, SCLIssuesTableEntry> {
134
135         public SCLExpressionValidationRequest(Resource component, Resource predicate, Resource object) {
136             super(component, predicate, object);
137         }
138
139         @Override
140         public SCLIssuesTableEntry perform(ReadGraph graph) throws DatabaseException {
141             Resource type = graph.getPossibleType(parameter3, Layer0.getInstance(graph).SCLValue);
142             if (type == null) {
143                 return null;
144             }
145             if (!graph.hasStatement(parameter))
146                 return null;
147             
148             Variable componentVariable = Variables.getPossibleVariable(graph, parameter);
149             if (componentVariable == null) {
150                 // Resource might be deleted already and therefore no URI available for variable building
151                 return null;
152             }
153             Variable propertyVariable = componentVariable.getProperty(graph, parameter2);
154             
155             Variable typeVariable = Variables.getVariable(graph, type);
156
157             Function1<Variable, String> func = typeVariable.getPossiblePropertyValue(graph, "validator");
158             if (func == null) {
159                 // No validator available
160                 if (LOGGER.isTraceEnabled())
161                     LOGGER.trace("No validator available for " + typeVariable.getURI(graph));
162                 return null;
163             }
164
165             SCLContext sclContext = SCLContext.getCurrent();
166             Object oldGraph = sclContext.get("graph");
167             try {
168                 sclContext.put("graph", graph);
169                 String validatorValue = func.apply(propertyVariable);
170                 if (validatorValue != null && !validatorValue.isEmpty()) {
171                     return new SCLIssuesTableEntry(propertyVariable.getURI(graph), new CompilationError(Locations.NO_LOCATION, validatorValue.replace("\n", " "))) {
172                         @Override
173                         public void openLocation() {
174                             openResource(Display.getCurrent().getActiveShell(), parameter);
175                         }
176                     };
177                 }
178             } catch (Throwable t) {
179                 LOGGER.error("Failed to invoke type validator function " + func, t);
180             } finally {
181                 sclContext.put("graph", oldGraph);
182             }
183             return null;
184         }
185     }
186     
187     private static class ComponentSyncListenerAdapter extends SyncListenerAdapter<Set<Resource>> implements Disposable {
188
189         private ConcurrentHashMap<Resource, SCLValueDisposableSyncListener> currentlyListening = new ConcurrentHashMap<>();
190         private boolean disposed;
191         private Runnable callback;
192         
193         public ComponentSyncListenerAdapter(Runnable callback) {
194             this.callback = callback;
195         }
196         
197         @Override
198         public void execute(ReadGraph graph, Set<Resource> newComponents) throws DatabaseException {
199             if (currentlyListening.isEmpty() && newComponents.isEmpty()) {
200                 // we can stop here as nothing will change
201                 return;
202             }
203             
204             Set<Resource> removedComponents = new HashSet<>(currentlyListening.keySet());
205             removedComponents.removeAll(newComponents);
206             
207             Set<Resource> addedComponents = new HashSet<>(newComponents);
208             addedComponents.removeAll(currentlyListening.keySet());
209             
210             for (Resource removedComponent : removedComponents) {
211                 // stop listening
212                 DisposableSyncListener<?> listener = currentlyListening.remove(removedComponent);
213                 listener.dispose();
214             }
215             
216             for (Resource addedComponent : addedComponents) {
217                 SCLValueDisposableSyncListener listener = new SCLValueDisposableSyncListener(callback);
218                 currentlyListening.put(addedComponent, listener);
219                 graph.syncRequest(new SCLValueRequest(addedComponent), listener);
220             }
221         }
222         
223         public List<SCLIssuesTableEntry> getIssues() {
224             List<SCLIssuesTableEntry> issues = new ArrayList<>();
225             for (SCLValueDisposableSyncListener listener : currentlyListening.values()) {
226                 List<SCLIssuesTableEntry> listenerIssues = listener.getIssues();
227                 if (listenerIssues != null && !listenerIssues.isEmpty())
228                     issues.addAll(listenerIssues);
229             }
230             return issues;
231         }
232         
233         @Override
234         public void dispose() {
235             currentlyListening.values().forEach(l -> l.dispose());
236             currentlyListening.clear();
237             this.disposed = true;
238         }
239         
240         @Override
241         public boolean isDisposed() {
242             return disposed;
243         }
244     }
245     
246     private static class SCLValueDisposableSyncListener extends DisposableSyncListener<Set<ResourceHolder>> {
247
248         private Runnable callback;
249         private ConcurrentHashMap<ResourceHolder, SCLIssuesTableEntryDisposableListener> currentlyListeningSCLValues = new ConcurrentHashMap<>();
250         
251         public SCLValueDisposableSyncListener(Runnable callback) {
252             this.callback = callback;
253         }
254         
255         @Override
256         public void execute(ReadGraph graph, Set<ResourceHolder> newComponents) throws DatabaseException {
257             if (currentlyListeningSCLValues.isEmpty() && newComponents.isEmpty()) {
258                 // we can stop here as nothing will change
259                 return;
260             }
261             
262             
263             Set<ResourceHolder> removedComponents = new HashSet<>(currentlyListeningSCLValues.keySet());
264             removedComponents.removeAll(newComponents);
265             
266             Set<ResourceHolder> addedComponents = new HashSet<>(newComponents);
267             addedComponents.removeAll(currentlyListeningSCLValues.keySet());
268             
269             for (ResourceHolder removedComponent : removedComponents) {
270                 // stop listening
271                 DisposableListener<?> listener = currentlyListeningSCLValues.remove(removedComponent);
272                 listener.dispose();
273             }
274             
275             for (ResourceHolder sclValue : addedComponents) {
276                 SCLIssuesTableEntryDisposableListener listener = new SCLIssuesTableEntryDisposableListener(callback);
277                 currentlyListeningSCLValues.put(sclValue, listener);
278                 graph.syncRequest(new SCLExpressionValidationRequest(sclValue.component, sclValue.predicate, sclValue.object), listener);
279             }
280             if (callback != null) {
281                 callback.run();
282             }
283         }
284
285         public List<SCLIssuesTableEntry> getIssues() {
286             if (currentlyListeningSCLValues.isEmpty())
287                 return null;
288             List<SCLIssuesTableEntry> issues = new ArrayList<>();
289             for (SCLIssuesTableEntryDisposableListener listener : currentlyListeningSCLValues.values()) {
290                 if (listener.getResult() != null)
291                     issues.add(listener.getResult());
292             }
293             return issues;
294         }
295
296         @Override
297         public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
298             LOGGER.error("Could not listen", throwable);
299         }
300         
301         @Override
302         public void dispose() {
303             currentlyListeningSCLValues.values().forEach(l -> l.dispose());
304             currentlyListeningSCLValues.clear();
305             super.dispose();
306         }
307         
308     }
309     
310     private static class SCLIssuesTableEntryDisposableListener extends DisposableListener<SCLIssuesTableEntry> {
311
312         private SCLIssuesTableEntry result;
313         private Runnable callback;
314         
315         public SCLIssuesTableEntryDisposableListener(Runnable callback) {
316             this.callback = callback;
317         }
318         
319         @Override
320         public void execute(SCLIssuesTableEntry result) {
321             if (!Objects.equals(this.result, result)) {
322                 this.result = result;
323                 if (callback != null) {
324                     callback.run();
325                 }
326             }
327         }
328
329         @Override
330         public void exception(Throwable t) {
331             LOGGER.error("", t);
332         }
333         
334         public SCLIssuesTableEntry getResult() {
335             return result;
336         }
337     }
338     
339     private static class ResourceHolder {
340
341         Resource component;
342         Resource predicate;
343         Resource object;
344         
345         public ResourceHolder(Resource component, Resource predicate, Resource object) {
346             this.component = Objects.requireNonNull(component);
347             this.predicate = Objects.requireNonNull(predicate);
348             this.object = Objects.requireNonNull(object);
349         }
350
351         @Override
352         public int hashCode() {
353             final int prime = 31;
354             int result = 1;
355             result = prime * result + component.hashCode();
356             result = prime * result + object.hashCode();
357             result = prime * result + predicate.hashCode();
358             return result;
359         }
360
361         @Override
362         public boolean equals(Object obj) {
363             if (this == obj)
364                 return true;
365             if (obj == null)
366                 return false;
367             if (getClass() != obj.getClass())
368                 return false;
369             ResourceHolder other = (ResourceHolder) obj;
370             if (!component.equals(other.component))
371                 return false;
372             if (!object.equals(other.object))
373                 return false;
374             if (!predicate.equals(other.predicate))
375                 return false;
376             return true;
377         }
378
379     }
380 }