1 package org.simantics.modeling.scl.issue;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashSet;
7 import java.util.Objects;
9 import java.util.TreeSet;
10 import java.util.concurrent.ConcurrentHashMap;
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;
43 public class SCLExpressionIssueProvider implements SCLIssueProvider {
45 public static class SCLExpressionIssueProviderFactory implements SCLIssueProviderFactory {
48 public SCLIssueProvider getSCLIssueProvider() {
49 if (Boolean.getBoolean("org.simantics.scl.issues"))
50 return new SCLExpressionIssueProvider();
52 return new DummyIssueProvider();
57 public static class DummyIssueProvider implements SCLIssueProvider {
59 public void listenIssues(Runnable callback) {
64 public List<SCLIssuesTableEntry> getIssues() {
65 return Collections.emptyList();
69 public void dispose() {
74 private static final Logger LOGGER = LoggerFactory.getLogger(SCLExpressionIssueProvider.class);
75 private boolean disposed = false;
76 private ComponentSyncListenerAdapter listener;
78 SCLExpressionIssueProvider() {
82 public void listenIssues(Runnable callback) {
83 listener = new ComponentSyncListenerAdapter(callback);
84 Simantics.getSession().asyncRequest(new ComponentRequest(), listener);
88 public List<SCLIssuesTableEntry> getIssues() {
89 return listener.getIssues();
93 public void dispose() {
98 private static void openResource(Shell shell, Resource resource) {
99 DefaultActions.performDefaultAction(shell, new StructuredSelection(resource));
102 private static class ComponentRequest extends UniqueRead<Set<Resource>> {
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);
114 for(Resource child : graph.getObjects(Simantics.getProjectResource(), L0.ConsistsOf)) {
115 if (graph.isInstanceOf(child, L0.IndexRoot)) {
116 indexRoots.add(child);
120 StructuralResource2 STR = StructuralResource2.getInstance(graph);
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);
127 return allComponents;
131 private static class SCLValueRequest extends UnaryRead<Resource, Set<ResourceHolder>> {
133 public SCLValueRequest(Resource parameter) {
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));
154 private static class SCLExpressionValidationRequest extends TernaryRead<Resource, Resource, Resource, SCLIssuesTableEntry> {
156 public SCLExpressionValidationRequest(Resource component, Resource predicate, Resource object) {
157 super(component, predicate, object);
161 public SCLIssuesTableEntry perform(ReadGraph graph) throws DatabaseException {
162 Resource type = graph.getPossibleType(parameter3, Layer0.getInstance(graph).SCLValue);
166 if (!graph.hasStatement(parameter))
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
174 Variable propertyVariable = componentVariable.getProperty(graph, parameter2);
176 Variable typeVariable = Variables.getVariable(graph, type);
178 Function1<Variable, String> func = typeVariable.getPossiblePropertyValue(graph, "validator");
180 // No validator available
181 if (LOGGER.isTraceEnabled())
182 LOGGER.trace("No validator available for " + typeVariable.getURI(graph));
186 SCLContext sclContext = SCLContext.getCurrent();
187 Object oldGraph = sclContext.get("graph");
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", " "))) {
194 public void openLocation() {
195 openResource(Display.getCurrent().getActiveShell(), parameter);
199 } catch (Throwable t) {
200 LOGGER.error("Failed to invoke type validator function " + func, t);
202 sclContext.put("graph", oldGraph);
208 private static class ComponentSyncListenerAdapter extends SyncListenerAdapter<Set<Resource>> implements Disposable {
210 private ConcurrentHashMap<Resource, SCLValueDisposableSyncListener> currentlyListening = new ConcurrentHashMap<>();
211 private boolean disposed;
212 private Runnable callback;
214 public ComponentSyncListenerAdapter(Runnable callback) {
215 this.callback = callback;
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
225 Set<Resource> removedComponents = new HashSet<>(currentlyListening.keySet());
226 removedComponents.removeAll(newComponents);
228 Set<Resource> addedComponents = new HashSet<>(newComponents);
229 addedComponents.removeAll(currentlyListening.keySet());
231 for (Resource removedComponent : removedComponents) {
233 DisposableSyncListener<?> listener = currentlyListening.remove(removedComponent);
237 for (Resource addedComponent : addedComponents) {
238 SCLValueDisposableSyncListener listener = new SCLValueDisposableSyncListener(callback);
239 currentlyListening.put(addedComponent, listener);
240 graph.syncRequest(new SCLValueRequest(addedComponent), listener);
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);
255 public void dispose() {
256 currentlyListening.values().forEach(l -> l.dispose());
257 currentlyListening.clear();
258 this.disposed = true;
262 public boolean isDisposed() {
267 private static class SCLValueDisposableSyncListener extends DisposableSyncListener<Set<ResourceHolder>> {
269 private Runnable callback;
270 private ConcurrentHashMap<ResourceHolder, SCLIssuesTableEntryDisposableListener> currentlyListeningSCLValues = new ConcurrentHashMap<>();
272 public SCLValueDisposableSyncListener(Runnable callback) {
273 this.callback = callback;
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
284 Set<ResourceHolder> removedComponents = new HashSet<>(currentlyListeningSCLValues.keySet());
285 removedComponents.removeAll(newComponents);
287 Set<ResourceHolder> addedComponents = new HashSet<>(newComponents);
288 addedComponents.removeAll(currentlyListeningSCLValues.keySet());
290 for (ResourceHolder removedComponent : removedComponents) {
292 DisposableListener<?> listener = currentlyListeningSCLValues.remove(removedComponent);
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);
301 if (callback != null) {
306 public List<SCLIssuesTableEntry> getIssues() {
307 if (currentlyListeningSCLValues.isEmpty())
309 List<SCLIssuesTableEntry> issues = new ArrayList<>();
310 for (SCLIssuesTableEntryDisposableListener listener : currentlyListeningSCLValues.values()) {
311 if (listener.getResult() != null)
312 issues.add(listener.getResult());
318 public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
319 LOGGER.error("Could not listen", throwable);
323 public void dispose() {
324 currentlyListeningSCLValues.values().forEach(l -> l.dispose());
325 currentlyListeningSCLValues.clear();
331 private static class SCLIssuesTableEntryDisposableListener extends DisposableListener<SCLIssuesTableEntry> {
333 private SCLIssuesTableEntry result;
334 private Runnable callback;
336 public SCLIssuesTableEntryDisposableListener(Runnable callback) {
337 this.callback = callback;
341 public void execute(SCLIssuesTableEntry result) {
342 if (!Objects.equals(this.result, result)) {
343 this.result = result;
344 if (callback != null) {
351 public void exception(Throwable t) {
355 public SCLIssuesTableEntry getResult() {
360 private static class ResourceHolder {
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);
373 public int hashCode() {
374 final int prime = 31;
376 result = prime * result + component.hashCode();
377 result = prime * result + object.hashCode();
378 result = prime * result + predicate.hashCode();
383 public boolean equals(Object obj) {
388 if (getClass() != obj.getClass())
390 ResourceHolder other = (ResourceHolder) obj;
391 if (!component.equals(other.component))
393 if (!object.equals(other.object))
395 if (!predicate.equals(other.predicate))