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