package org.simantics.district.selection; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; 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.QueryIndexUtils; 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.request.PossibleActiveModel; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.request.Read; 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.structural.stubs.StructuralResource2; import org.simantics.utils.datastructures.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ElementSelector { String name; String expression; 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; private static Logger LOGGER = LoggerFactory.getLogger(ElementSelector.class); 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; try { this.name = graph.getRelatedValue(resource, L0.HasLabel); this.expression = getExpression(graph, resource); } catch (DatabaseException e) { LOG.error("Error reading element selector", e); throw e; } } /** * Instantiate an element selector object for an ES.Selection resource. */ public static ElementSelector getSelector(RequestProcessor graph, Resource resource) throws DatabaseException { return graph.syncRequest(new ElementSelectorQuery(resource)); } /** * Get an SQL-like textual description of an ES.Selection resource. */ public static String getExpression(RequestProcessor graph, Resource resource) throws DatabaseException { return graph.syncRequest(new SelectionExpressionRequest(resource)); } /** * Get a Java object representation of an ES.Condition resource. */ public static Condition getCondition(RequestProcessor graph, Resource condition) throws DatabaseException { return graph.syncRequest(new SelectionConditionRequest(condition)); } /** * Get the name of the element selector. */ public String getName() { return name; } /** * Get a textual SQL-like description of the element selector. */ public String getExpression() { return expression; } /** * Get the resource that this selector represents. */ public Resource getResource() { return resource; } /** * Get the generator component. Use {@link #buildSelection(ReadGraph)} to make it available first. */ public Generator getGenerator() { return generator; } /** * Get the selector component. Use {@link #buildSelection(ReadGraph)} to make it available first. */ public Selector getSelector() { return selector; } /** * Get the condition component. Use {@link #buildSelection(ReadGraph)} to make it available first. */ public Condition getCondition() { return condition; } /** * * @param graph * @throws DatabaseException */ public 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); } public static Map findDiagrams() { try { return Simantics.getSession().syncRequest(new Read>() { @Override public Map perform(ReadGraph graph) throws DatabaseException { Map result = new HashMap<>(); Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); List composites = QueryIndexUtils.searchByType(graph, model, StructuralResource2.getInstance(graph).Composite); for (Resource r : composites) { // Get diagram Resource diagram = graph.getPossibleObject(r, ModelingResources.getInstance(graph).CompositeToDiagram); if (diagram == null) continue; // Filter out user component diagrams Resource parent = graph.getPossibleObject(r, Layer0.getInstance(graph).PartOf); if (parent == null || graph.isInheritedFrom(parent, StructuralResource2.getInstance(graph).Component)) continue; result.put(r, graph.getRelatedValue(r, Layer0.getInstance(graph).HasName)); } return result; } }); } catch (DatabaseException e) { LOGGER.error("Query for model diagrams failed", e); return Collections.emptyMap(); } } public 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; } public 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; } public SelectionResult selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException { if (selector == null) { buildSelection(graph); } return gather(graph, model); } 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(resource, propertyName, lowerLimit, upperLimit); } else if (graph.isInstanceOf(resource, ES.RegionCondition)) { DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion); double[] region = graph.getRelatedValue(regionResource, DR.Region_area); cond = new RegionCondition(resource, regionResource, region); } else if (graph.isInstanceOf(resource, ES.RouteCondition)) { Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute); Set routePoints = new HashSet<>(ListUtils.toList(graph, routeResource)); cond = new RouteCondition(resource, routeResource, routePoints); } else if (graph.isInstanceOf(resource, ES.AggregateCondition)) { 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(resource, type, conditions); } else { throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource))); } return cond; } private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException { if (graph.isInstanceOf(r, ES.Selection)) { String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator)); Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition); return cond != null ? exp + " where {" + getExpression(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 = getExpression(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 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 SelectionResult gather(ReadGraph graph, Resource model) throws DatabaseException { return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model))); } // Generators public static abstract class Generator { abstract Collection generate(ReadGraph graph, Resource model) throws DatabaseException; } public static class ModelGenerator extends Generator { @Override Collection generate(ReadGraph graph, Resource model) throws DatabaseException { Resource conf = graph.syncRequest(new Configuration(model)); HashMap fromMapped = new HashMap(); // Iterate over diagrams // Each model element is represented by the corresponding mapping element, if present 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) { for (Resource elem : elementsOfDiagram(graph, diagram)) { Resource mapped = graph.getPossibleObject(elem, DN.MappedComponent); if (mapped != null) { fromMapped.put(mapped, elem); } else if (!fromMapped.containsKey(elem)) { fromMapped.put(elem, elem); } } } } return new ArrayList<>(fromMapped.values()); } } public static class DiagramGenerator extends Generator { public Resource diagram; public DiagramGenerator(Resource diagram) { this.diagram = diagram; } @Override Collection generate(ReadGraph graph, Resource model) throws DatabaseException { return elementsOfDiagram(graph, diagram); } } public static class ExplicitGenerator extends Generator { public ExplicitGenerator(Collection elements) { this.elements = elements; } public Collection elements; @Override Collection generate(ReadGraph graph, Resource model) { return elements; } } // Selectors public static class SelectionResult { public final Collection elements; public final int tailCount; public final int tailSize; public SelectionResult(Collection elements, int tailCount, int tailSize) { this.elements = elements; this.tailCount = tailCount; this.tailSize = tailSize; } } public static abstract class Selector { abstract SelectionResult select(ReadGraph graph, Collection elements); } public static class All extends Selector { public All() { } @Override SelectionResult select(ReadGraph graph, Collection elements) { return new SelectionResult(elements, 0, 0); } } public static class PropertySelector extends Selector { public PropertySelector(boolean smallest, String propertyName, int resultCount) { this.smallest = smallest; this.propertyName = propertyName; this.resultCount = resultCount; } public boolean smallest; public String propertyName; public int resultCount; @Override SelectionResult select(ReadGraph graph, Collection elements) { // Select sorting direction Comparator> comparator = smallest ? (p1, p2) -> Double.compare(p1.second, p2.second) : (p1, p2) -> Double.compare(p1.second, p2.second); // Get association list to property values List> result2 = elements.stream() .map(r -> Pair.make(r, getPropertyValue(graph, r, propertyName))) .filter(t -> t.second != null) .sorted(comparator) .collect(Collectors.toList()); int count = Math.min(resultCount, result2.size()); // Count number of equal values at the end of the list int tailCount = 0; double tailValue = count > 0 ? result2.get(count-1).second : 0.0; for (int i = count-1; i >= 0; i--) { if (result2.get(i).second == tailValue) tailCount++; else break; } // Count number of elements with value equal to the end of the list int tailSize = tailCount; for (int i = count; i < result2.size(); i++) { if (result2.get(i).second == tailValue) tailSize++; else break; } // Take first n items if (count < result2.size()) { result2 = result2.subList(0, resultCount); } // Map to list or resources List selection = result2.stream().map(p -> p.first).collect(Collectors.toList()); return new SelectionResult(selection, tailCount, tailSize); } } // Conditions public static abstract class Condition { public Resource resource; Condition(Resource r) { resource = r; } abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException; } public static class PropertyCondition extends Condition { public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) { super(r); this.propertyName = propertyName; this.lowerLimit = lowerLimit; this.upperLimit = upperLimit; } public String propertyName; public Double lowerLimit; public 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); } } public static class RegionCondition extends Condition { public RegionCondition(Resource r, Resource regionResoruce, double[] region) { super(r); 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; this.regionResource = regionResoruce; } public Resource regionResource; 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); } } public static class RouteCondition extends Condition { public RouteCondition(Resource r, Resource routeResource, Set routePoints) { super(r); this.routePoints = routePoints; this.routeResource = routeResource; } public Resource routeResource; Set routePoints; @Override boolean match(ReadGraph graph, Resource r) throws DatabaseException { return routePoints.contains(r); } } public static class AggregateCondition extends Condition { public static enum Type { DISJUNCTION, CONJUNCTION, NEGATION }; public AggregateCondition(Resource r, Type type, List conditions) { super(r); this.type = type; this.conditions = conditions; } public Type type; public 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); } } public final static class SelectionExpressionRequest extends ResourceRead { public SelectionExpressionRequest(Resource condition) { super(condition); } @Override public String perform(ReadGraph graph) throws DatabaseException { return ElementSelector.buildExpression(graph, resource); } } public static final class SelectionConditionRequest extends ResourceRead { public SelectionConditionRequest(Resource resource) { super(resource); } @Override public Condition perform(ReadGraph graph) throws DatabaseException { return buildCondition(graph, resource); } } }