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.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.network.ontology.DistrictNetworkResource; 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; static DistrictNetworkResource DN; 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); DN = DistrictNetworkResource.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 selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException { if (selector == null) { buildSelection(graph); } Collection result = gather(graph, model); return result instanceof List ? (List) 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 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 conditionResources = graph.getObjects(resource, ES.HasSubcondition); List 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 exps = new ArrayList<>(); Collection 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 ""; } 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 elementsOfDiagram(ReadGraph graph, Resource diagram) throws DatabaseException { if (graph.isInstanceOf(diagram, STR.Composite)) { // Resource is a composite - get diagram return elementsOfDiagram(graph, graph.getSingleObject(diagram, MOD.CompositeToDiagram)); } Collection elements = graph.getObjects(diagram, L0.ConsistsOf); Collection 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 filterElementsFrom(ReadGraph graph, Collection elements) throws DatabaseException { if (condition == null) return elements; ArrayList result = new ArrayList<>(); for (Resource r : elements) { if (condition.match(graph, r)) result.add(r); } return result; } private Collection 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(); } // No property found - try possible mapped element property as well Resource mappedElement = graph.getPossibleObject(element, DN.MappedComponent); if (mappedElement != null) return getPropertyValue(graph, mappedElement, propertyName); } catch (DatabaseException e) { } return null; } // Generators static abstract class Generator { abstract Collection generate(ReadGraph graph, Resource model) throws DatabaseException; } static class ModelGenerator extends Generator { @Override Collection generate(ReadGraph graph, Resource model) throws DatabaseException { Resource conf = graph.syncRequest(new Configuration(model)); ArrayList 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 generate(ReadGraph graph, Resource model) throws DatabaseException { return elementsOfDiagram(graph, diagram); } } static class ExplicitGenerator extends Generator { public ExplicitGenerator(Collection elements) { this.elements = elements; } Collection elements; @Override Collection generate(ReadGraph graph, Resource model) { return elements; } } // Selectors static abstract class Selector { abstract Collection select(ReadGraph graph, Collection elements); } static class All extends Selector { public All() { } @Override Collection select(ReadGraph graph, Collection 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 select(ReadGraph graph, Collection elements) { List result = new ArrayList<>(elements); List result2 = Lists.map(new FunctionImpl1() { @Override public Tuple2 apply(Resource r) { return new Tuple2(r, getPropertyValue(graph, r, propertyName)); } }, result); result2 = Lists.filter(new FunctionImpl1() { @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)); if (resultCount < result2.size()) result2 = result2.subList(0, resultCount); result = Lists.map(new FunctionImpl1() { @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 routePoints) { super(); this.routePoints = routePoints; } Set 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 conditions) { super(); this.type = type; this.conditions = conditions; } Type type; List 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 { public ElementSelectorQuery(Resource resource) { super(resource); } @Override public ElementSelector perform(ReadGraph graph) throws DatabaseException { return new ElementSelector(graph, resource); } } }