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