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;
}
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;
+ }
+
+ }
}