+package org.simantics.district.selection;
+
+import java.awt.geom.Path2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.request.ResourceRead;
+import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.ServiceException;
+import org.simantics.db.layer0.request.ActiveModels;
+import org.simantics.db.layer0.request.ActiveRuns;
+import org.simantics.db.layer0.request.Configuration;
+import org.simantics.db.layer0.variable.RVI;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.district.region.ontology.DiagramRegionsResource;
+import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scl.runtime.Lists;
+import org.simantics.scl.runtime.function.FunctionImpl1;
+import org.simantics.scl.runtime.tuple.Tuple2;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ElementSelector {
+ String name;
+ String expression;
+ Integer count;
+ Resource resource;
+
+ Generator generator;
+ Selector selector;
+ Condition condition;
+
+ static Logger LOG = LoggerFactory.getLogger(ElementSelector.class);
+
+ static ElementSelectionResource ES;
+ static Layer0 L0;
+ static StructuralResource2 STR;
+ static ModelingResources MOD;
+ static DiagramResource DIA;
+
+ ElementSelector(ReadGraph graph, Resource resource) throws DatabaseException {
+ super();
+
+ L0 = Layer0.getInstance(graph);
+ ES = ElementSelectionResource.getInstance(graph);
+ STR = StructuralResource2.getInstance(graph);
+ MOD = ModelingResources.getInstance(graph);
+ DIA = DiagramResource.getInstance(graph);
+
+ this.resource = resource;
+ this.count = -1;
+ try {
+ this.name = graph.getRelatedValue(resource, L0.HasLabel);
+ this.expression = buildExpression(graph, resource);
+ } catch (DatabaseException e) {
+ LOG.error("Error reading element selector", e);
+ throw e;
+ }
+ }
+
+ public static ElementSelector getSelector(ReadGraph graph, Resource resource) throws DatabaseException {
+ return graph.syncRequest(new ElementSelectorQuery(resource));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public List<Resource> selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
+ if (selector == null) {
+ buildSelection(graph);
+ }
+
+ Collection<Resource> result = gather(graph, model);
+ return result instanceof List ? (List<Resource>) result : new ArrayList<>(result);
+ }
+
+ private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
+ if (!graph.isInstanceOf(resource, ES.Generator))
+ throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
+
+ if (graph.isInstanceOf(resource, ES.Generator_Model)) {
+ return new ModelGenerator();
+ }
+ else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) {
+ return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
+ }
+ else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) {
+ return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
+ }
+ else {
+ throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
+ }
+ }
+
+ private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
+ if (!graph.isInstanceOf(resource, ES.Selector))
+ throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
+
+ Selector s;
+ if (graph.isInstanceOf(resource, ES.Selector_All)) {
+ s = new All();
+ }
+ else if (graph.isInstanceOf(resource, ES.PropertySelector)) {
+ String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
+ Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
+ boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
+ s = new PropertySelector(isSmallest, propertyName, resultCount);
+ }
+ else {
+ throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
+ }
+ return s;
+ }
+
+ private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException {
+ if (resource == null)
+ return null;
+
+ if (!graph.isInstanceOf(resource, ES.Condition))
+ throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
+
+ Condition cond;
+ if (graph.isInstanceOf(resource, ES.PropertyCondition)) {
+ String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
+ Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
+ Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
+ cond = new PropertyCondition(propertyName, lowerLimit, upperLimit);
+ }
+ else if (graph.isInstanceOf(resource, ES.RegionCondition)) {
+ DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
+ double[] region = graph.getRelatedValue(graph.getSingleObject(resource, ES.RegionCondition_HasRegion), DR.Region_area);
+ cond = new RegionCondition(region);
+ }
+ else if (graph.isInstanceOf(resource, ES.RouteCondition)) {
+ Set<Resource> routePoints = new HashSet<>(ListUtils.toList(graph, graph.getSingleObject(resource, ES.RouteCondition_HasRoute)));
+ cond = new RouteCondition(routePoints);
+ }
+ else if (graph.isInstanceOf(resource, ES.AggregateCondition)) {
+ boolean isDisjunction = graph.isInstanceOf(resource, ES.Disjunction);
+ Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
+ List<Condition> conditions = new ArrayList<>(conditionResources.size());
+ for (Resource c : conditionResources) {
+ conditions.add(buildCondition(graph, c));
+ }
+ Type type;
+ if (graph.isInstanceOf(resource, ES.Conjunction))
+ type = AggregateCondition.Type.CONJUNCTION;
+ else if (graph.isInstanceOf(resource, ES.Negation))
+ type = AggregateCondition.Type.NEGATION;
+ else if (graph.isInstanceOf(resource, ES.Disjunction))
+ type = AggregateCondition.Type.DISJUNCTION;
+ else
+ throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
+
+ cond = new AggregateCondition(type, conditions);
+ }
+ else {
+ throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
+ }
+
+ cond.isInverse = graph.hasStatement(resource, ES.Condition_IsInverse, resource);
+ return cond;
+ }
+
+ private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
+ if (graph.isInstanceOf(r, ES.Selection)) {
+ String exp = "select " + buildExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) +
+ " from " + buildExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
+
+ Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
+ return cond != null ? exp + " where {" + buildExpression(graph, cond) + "}" : exp;
+ }
+ else if (graph.isInstanceOf(r, ES.Condition)) {
+ if (graph.isInstanceOf(r, ES.PropertyCondition)) {
+ return buildPropertyConditionExpression(graph, r);
+ }
+ else if (graph.isInstanceOf(r, ES.RegionCondition)) {
+ Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
+ String name = graph.getRelatedValue(region, L0.HasLabel);
+ return "in region " + name;
+ }
+ else if (graph.isInstanceOf(r, ES.RouteCondition)) {
+ Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
+ String name = graph.getRelatedValue(route, L0.HasLabel);
+ return "in route " + name;
+ }
+ else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
+ String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
+ List<String> exps = new ArrayList<>();
+ Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
+ for (Resource c : objects) {
+ String exp = buildExpression(graph, c);
+ exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
+ }
+ String result = String.join(op, exps);
+ if (graph.isInstanceOf(r, ES.Negation))
+ result = "not {" + result + "}";
+ return result;
+ }
+ else {
+ throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+ }
+ }
+ else if (graph.isInstanceOf(r, ES.Selector)) {
+ if (graph.isInstanceOf(r, ES.Selector_All)) {
+ return "all";
+ }
+ else if (graph.isInstanceOf(r, ES.PropertySelector)) {
+ String op;
+ if (graph.isInstanceOf(r, ES.Selector_NLowest))
+ op = "bottom";
+ else if (graph.isInstanceOf(r, ES.Selector_NHighest))
+ op = "top";
+ else
+ throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+
+ String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
+ Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
+ return op + " " + count + " of " + name;
+ }
+ else {
+ throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+ }
+ }
+ else if (graph.isInstanceOf(r, ES.Generator)) {
+ if (graph.isInstanceOf(r, ES.Generator_Model)) {
+ return "model";
+ }
+ else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
+ return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
+ }
+ else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
+ return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
+ }
+ else {
+ throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+ }
+ }
+ else {
+ throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+ }
+ }
+
+ private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
+ String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
+ Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
+ Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
+ if (lowerLimit == null && upperLimit == null) {
+ return "has property " + propertyName;
+ }
+ else {
+ StringBuilder result = new StringBuilder();
+ if (lowerLimit != null) {
+ result.append(lowerLimit);
+ result.append(" < ");
+ }
+ result.append(propertyName);
+ if (upperLimit != null) {
+ result.append(" < ");
+ result.append(upperLimit);
+ }
+ return result.toString();
+ }
+ }
+
+ private static Collection<Resource> elementsOfDiagram(ReadGraph graph, Resource diagram) throws ServiceException {
+ Collection<Resource> elements = graph.getObjects(diagram, L0.ConsistsOf);
+ Collection<Resource> result = new ArrayList<>();
+ for (Resource r : elements)
+ if (graph.isInstanceOf(r, DIA.Element))
+ result .add(r);
+ return result;
+ }
+
+ private void buildSelection(ReadGraph graph) throws DatabaseException {
+ Resource selector = graph.getSingleObject(resource, ES.Selection_HasSelector);
+ Resource generator = graph.getSingleObject(resource, ES.Selection_HasGenerator);
+ Resource condition = graph.getPossibleObject(resource, ES.Selection_HasCondition);
+
+ this.selector = buildSelector(graph, selector);
+ this.generator = buildGenerator(graph, generator);
+ this.condition = buildCondition(graph, condition);
+ }
+
+ private Collection<Resource> filterElementsFrom(ReadGraph graph, Collection<Resource> elements) throws DatabaseException {
+ if (condition == null) return elements;
+
+ ArrayList<Resource> result = new ArrayList<>();
+ for (Resource r : elements) {
+ if (condition.match(graph, r))
+ result.add(r);
+ }
+
+ return result;
+ }
+
+ private Collection<Resource> gather(ReadGraph graph, Resource model) throws DatabaseException {
+ return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
+ }
+
+ static Variable getVariableForElement(ReadGraph graph, Resource element) throws DatabaseException {
+ Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
+ if (component != null) {
+ Variable var = Variables.getVariable(graph, component);
+ RVI realRvi = var.getRVI(graph);
+
+ for (Resource activeModel : graph.syncRequest(new ActiveModels(Simantics.getProjectResource()))) {
+ for (Variable run : graph.syncRequest(new ActiveRuns(activeModel))) {
+ Variable v = realRvi.resolvePossible(graph, run);
+ if (v != null) {
+ return v;
+ }
+ }
+ Variable configuration = Variables.getPossibleConfigurationContext(graph, activeModel);
+ if (configuration != null) {
+ Variable v = realRvi.resolvePossible(graph, configuration);
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ static Double getPropertyValue(ReadGraph graph, Resource element, String propertyName) {
+ try {
+ Variable v = getVariableForElement(graph, element);
+ if (v != null) {
+ Number value = v.getPossiblePropertyValue(graph, propertyName);
+ if (value != null)
+ return value.doubleValue();
+ }
+ }
+ catch (DatabaseException e) {
+ }
+
+ return null;
+ }
+
+ // Generators
+
+ static abstract class Generator {
+ abstract Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException;
+ }
+
+ static class ModelGenerator extends Generator {
+ @Override
+ Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
+ Resource conf = graph.syncRequest(new Configuration(model));
+
+ ArrayList<Resource> result = new ArrayList<>();
+
+ // Iterate over diagrams
+ for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) {
+ if (!graph.isInstanceOf(comp, STR.Composite)) continue;
+
+ Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram);
+ if (diagram != null) result.addAll(elementsOfDiagram(graph, diagram));
+ }
+
+ return result;
+ }
+ }
+
+ static class DiagramGenerator extends Generator {
+ Resource diagram;
+
+ public DiagramGenerator(Resource diagram) {
+ this.diagram = diagram;
+ }
+
+ @Override
+ Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
+ return elementsOfDiagram(graph, diagram);
+ }
+ }
+
+ static class ExplicitGenerator extends Generator {
+ public ExplicitGenerator(Collection<Resource> elements) {
+ this.elements = elements;
+ }
+
+ Collection<Resource> elements;
+
+ @Override
+ Collection<Resource> generate(ReadGraph graph, Resource model) {
+ return elements;
+ }
+ }
+
+ // Selectors
+
+ static abstract class Selector {
+ abstract Collection<Resource> select(ReadGraph graph, Collection<Resource> elements);
+ }
+
+ static class All extends Selector {
+ public All() {
+ }
+
+ @Override
+ Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
+ return elements;
+ }
+ }
+
+ static class PropertySelector extends Selector {
+ public PropertySelector(boolean smallest, String propertyName, int resultCount) {
+ this.smallest = smallest;
+ this.propertyName = propertyName;
+ this.resultCount = resultCount;
+ }
+
+ boolean smallest;
+ String propertyName;
+ int resultCount;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
+ List<Resource> result = new ArrayList<>(elements);
+ List<Tuple2> result2 = Lists.map(new FunctionImpl1<Resource, Tuple2>() {
+ @Override
+ public Tuple2 apply(Resource r) {
+ return new Tuple2(r, getPropertyValue(graph, r, propertyName));
+ }
+ }, result);
+
+ result2 = Lists.filter(new FunctionImpl1<Tuple2, Boolean>() {
+ @Override
+ public Boolean apply(Tuple2 t) {
+ return t.c1 != null;
+ }
+ }, result2);
+
+ result2.sort((t1, t2) -> smallest ? Double.compare((Double) t1.c1, (Double) t2.c1) : Double.compare((Double) t2.c1, (Double) t1.c1));
+
+ result2 = result2.subList(0, resultCount);
+
+ result = Lists.map(new FunctionImpl1<Tuple2, Resource>() {
+ @Override
+ public Resource apply(Tuple2 p0) {
+ return (Resource) p0.c0;
+ }
+ }, result2);
+
+ return result;
+ }
+ }
+
+ // Conditions
+
+ static abstract class Condition {
+ boolean isInverse;
+ abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
+ }
+
+ static class PropertyCondition extends Condition {
+ public PropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) {
+ super();
+ this.propertyName = propertyName;
+ this.lowerLimit = lowerLimit;
+ this.upperLimit = upperLimit;
+ }
+
+ String propertyName;
+ Double lowerLimit;
+ Double upperLimit;
+
+ @Override
+ boolean match(ReadGraph graph, Resource r) {
+ Double value = getPropertyValue(graph, r, propertyName);
+ return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
+ }
+ }
+
+ static class RegionCondition extends Condition {
+ public RegionCondition(double[] region) {
+ super();
+ this.region = region;
+
+ Path2D path = new Path2D.Double();
+ double startX = region[0];
+ double startY = region[1];
+ path.moveTo(startX, startY);
+ for (int i = 2; i < region.length; i+=2)
+ path.lineTo(region[i], region[i+1]);
+ path.closePath();
+
+ this.path = path;
+ }
+
+ double[] region;
+ Path2D path;
+
+ @Override
+ boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+ double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
+ double x = transform[4];
+ double y = transform[5];
+ return path.contains(x, y);
+ }
+ }
+
+ static class RouteCondition extends Condition {
+ public RouteCondition(Set<Resource> routePoints) {
+ super();
+ this.routePoints = routePoints;
+ }
+
+ Set<Resource> routePoints;
+
+ @Override
+ boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+ return routePoints.contains(r);
+ }
+ }
+
+ static class DiagramCondition extends Condition {
+ public DiagramCondition(Resource diagram) {
+ super();
+ this.diagram = diagram;
+ }
+
+ Resource diagram;
+
+ @Override
+ boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+ return graph.getSingleObject(r, L0.PartOf).equals(diagram);
+ }
+ }
+
+ static class AggregateCondition extends Condition {
+ static enum Type { DISJUNCTION, CONJUNCTION, NEGATION };
+
+ public AggregateCondition(Type type, List<Condition> conditions) {
+ super();
+ this.type = type;
+ this.conditions = conditions;
+ }
+
+ Type type;
+ List<Condition> conditions;
+
+ @Override
+ boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+ switch (type) {
+ case DISJUNCTION:
+ for (Condition c : conditions)
+ if (c.match(graph, r)) return true;
+ return false;
+ case CONJUNCTION:
+ for (Condition c : conditions)
+ if (!c.match(graph, r)) return false;
+ return true;
+ case NEGATION:
+ for (Condition c : conditions)
+ if (c.match(graph, r)) return false;
+ return true;
+ default:
+ // Should not happen
+ throw new IllegalArgumentException("Unknown aggregate condition type " + type);
+ }
+ }
+ }
+
+ public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {
+ public ElementSelectorQuery(Resource resource) {
+ super(resource);
+ }
+
+ @Override
+ public ElementSelector perform(ReadGraph graph) throws DatabaseException {
+ return new ElementSelector(graph, resource);
+ }
+ }
+}