1 package org.simantics.district.selection;
3 import java.awt.geom.Path2D;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.List;
13 import java.util.stream.Collectors;
15 import org.simantics.Simantics;
16 import org.simantics.db.ReadGraph;
17 import org.simantics.db.RequestProcessor;
18 import org.simantics.db.Resource;
19 import org.simantics.db.common.request.ResourceRead;
20 import org.simantics.db.common.utils.ListUtils;
21 import org.simantics.db.exception.DatabaseException;
22 import org.simantics.db.layer0.QueryIndexUtils;
23 import org.simantics.db.layer0.request.ActiveModels;
24 import org.simantics.db.layer0.request.ActiveRuns;
25 import org.simantics.db.layer0.request.Configuration;
26 import org.simantics.db.layer0.request.PossibleActiveModel;
27 import org.simantics.db.layer0.variable.RVI;
28 import org.simantics.db.layer0.variable.Variable;
29 import org.simantics.db.layer0.variable.Variables;
30 import org.simantics.db.request.Read;
31 import org.simantics.diagram.stubs.DiagramResource;
32 import org.simantics.district.network.ontology.DistrictNetworkResource;
33 import org.simantics.district.region.ontology.DiagramRegionsResource;
34 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
35 import org.simantics.layer0.Layer0;
36 import org.simantics.modeling.ModelingResources;
37 import org.simantics.structural.stubs.StructuralResource2;
38 import org.simantics.utils.datastructures.Pair;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 public class ElementSelector {
52 static Logger LOG = LoggerFactory.getLogger(ElementSelector.class);
54 static ElementSelectionResource ES;
56 static StructuralResource2 STR;
57 static ModelingResources MOD;
58 static DiagramResource DIA;
59 static DistrictNetworkResource DN;
61 private static Logger LOGGER = LoggerFactory.getLogger(ElementSelector.class);
63 ElementSelector(ReadGraph graph, Resource resource) throws DatabaseException {
66 L0 = Layer0.getInstance(graph);
67 ES = ElementSelectionResource.getInstance(graph);
68 STR = StructuralResource2.getInstance(graph);
69 MOD = ModelingResources.getInstance(graph);
70 DIA = DiagramResource.getInstance(graph);
71 DN = DistrictNetworkResource.getInstance(graph);
73 this.resource = resource;
75 this.name = graph.getRelatedValue(resource, L0.HasLabel);
76 this.expression = getExpression(graph, resource);
77 } catch (DatabaseException e) {
78 LOG.error("Error reading element selector", e);
84 * Instantiate an element selector object for an ES.Selection resource.
86 public static ElementSelector getSelector(RequestProcessor graph, Resource resource) throws DatabaseException {
87 return graph.syncRequest(new ElementSelectorQuery(resource));
91 * Get an SQL-like textual description of an ES.Selection resource.
93 public static String getExpression(RequestProcessor graph, Resource resource) throws DatabaseException {
94 return graph.syncRequest(new SelectionExpressionRequest(resource));
98 * Get a Java object representation of an ES.Condition resource.
100 public static Condition getCondition(RequestProcessor graph, Resource condition) throws DatabaseException {
101 return graph.syncRequest(new SelectionConditionRequest(condition));
105 * Get the name of the element selector.
107 public String getName() {
112 * Get a textual SQL-like description of the element selector.
114 public String getExpression() {
119 * Get the resource that this selector represents.
121 public Resource getResource() {
126 * Get the generator component. Use {@link #buildSelection(ReadGraph)} to make it available first.
128 public Generator getGenerator() {
133 * Get the selector component. Use {@link #buildSelection(ReadGraph)} to make it available first.
135 public Selector getSelector() {
140 * Get the condition component. Use {@link #buildSelection(ReadGraph)} to make it available first.
142 public Condition getCondition() {
149 * @throws DatabaseException
151 public void buildSelection(ReadGraph graph) throws DatabaseException {
152 Resource selector = graph.getSingleObject(resource, ES.Selection_HasSelector);
153 Resource generator = graph.getSingleObject(resource, ES.Selection_HasGenerator);
154 Resource condition = graph.getPossibleObject(resource, ES.Selection_HasCondition);
156 this.selector = buildSelector(graph, selector);
157 this.generator = buildGenerator(graph, generator);
158 this.condition = buildCondition(graph, condition);
161 public static Map<Resource, String> findDiagrams() {
163 return Simantics.getSession().syncRequest(new Read<Map<Resource, String>>() {
165 public Map<Resource, String> perform(ReadGraph graph) throws DatabaseException {
166 Map<Resource, String> result = new HashMap<>();
167 Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
168 List<Resource> composites = QueryIndexUtils.searchByType(graph, model, StructuralResource2.getInstance(graph).Composite);
169 for (Resource r : composites) {
171 Resource diagram = graph.getPossibleObject(r, ModelingResources.getInstance(graph).CompositeToDiagram);
172 if (diagram == null) continue;
174 // Filter out user component diagrams
175 Resource parent = graph.getPossibleObject(r, Layer0.getInstance(graph).PartOf);
176 if (parent == null || graph.isInheritedFrom(parent, StructuralResource2.getInstance(graph).Component))
179 result.put(r, graph.getRelatedValue(r, Layer0.getInstance(graph).HasName));
185 } catch (DatabaseException e) {
186 LOGGER.error("Query for model diagrams failed", e);
187 return Collections.emptyMap();
191 public static Variable getVariableForElement(ReadGraph graph, Resource element) throws DatabaseException {
192 Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
193 if (component != null) {
194 Variable var = Variables.getVariable(graph, component);
195 RVI realRvi = var.getRVI(graph);
197 for (Resource activeModel : graph.syncRequest(new ActiveModels(Simantics.getProjectResource()))) {
198 for (Variable run : graph.syncRequest(new ActiveRuns(activeModel))) {
199 Variable v = realRvi.resolvePossible(graph, run);
204 Variable configuration = Variables.getPossibleConfigurationContext(graph, activeModel);
205 if (configuration != null) {
206 Variable v = realRvi.resolvePossible(graph, configuration);
217 public static Double getPropertyValue(ReadGraph graph, Resource element, String propertyName) {
219 Variable v = getVariableForElement(graph, element);
221 Number value = v.getPossiblePropertyValue(graph, propertyName);
223 return value.doubleValue();
226 // No property found - try possible mapped element property as well
227 Resource mappedElement = graph.getPossibleObject(element, DN.MappedComponent);
228 if (mappedElement != null)
229 return getPropertyValue(graph, mappedElement, propertyName);
231 catch (DatabaseException e) {
237 public SelectionResult selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
238 if (selector == null) {
239 buildSelection(graph);
242 return gather(graph, model);
245 private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
246 if (!graph.isInstanceOf(resource, ES.Generator))
247 throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
249 if (graph.isInstanceOf(resource, ES.Generator_Model)) {
250 return new ModelGenerator();
252 else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) {
253 return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
255 else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) {
256 return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
259 throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
263 private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
264 if (!graph.isInstanceOf(resource, ES.Selector))
265 throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
268 if (graph.isInstanceOf(resource, ES.Selector_All)) {
271 else if (graph.isInstanceOf(resource, ES.PropertySelector)) {
272 String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
273 Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
274 boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
275 s = new PropertySelector(isSmallest, propertyName, resultCount);
278 throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
283 private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException {
284 if (resource == null)
287 if (!graph.isInstanceOf(resource, ES.Condition))
288 throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
291 if (graph.isInstanceOf(resource, ES.PropertyCondition)) {
292 String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
293 Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
294 Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
295 cond = new PropertyCondition(resource, propertyName, lowerLimit, upperLimit);
297 else if (graph.isInstanceOf(resource, ES.RegionCondition)) {
298 DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
299 Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion);
300 double[] region = graph.getRelatedValue(regionResource, DR.Region_area);
301 cond = new RegionCondition(resource, regionResource, region);
303 else if (graph.isInstanceOf(resource, ES.RouteCondition)) {
304 Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute);
305 Set<Resource> routePoints = new HashSet<>(ListUtils.toList(graph, routeResource));
306 cond = new RouteCondition(resource, routeResource, routePoints);
308 else if (graph.isInstanceOf(resource, ES.AggregateCondition)) {
309 Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
310 List<Condition> conditions = new ArrayList<>(conditionResources.size());
311 for (Resource c : conditionResources) {
312 conditions.add(buildCondition(graph, c));
315 if (graph.isInstanceOf(resource, ES.Conjunction))
316 type = AggregateCondition.Type.CONJUNCTION;
317 else if (graph.isInstanceOf(resource, ES.Negation))
318 type = AggregateCondition.Type.NEGATION;
319 else if (graph.isInstanceOf(resource, ES.Disjunction))
320 type = AggregateCondition.Type.DISJUNCTION;
322 throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
324 cond = new AggregateCondition(resource, type, conditions);
327 throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
333 private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
334 if (graph.isInstanceOf(r, ES.Selection)) {
335 String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) +
336 " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
338 Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
339 return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
341 else if (graph.isInstanceOf(r, ES.Condition)) {
342 if (graph.isInstanceOf(r, ES.PropertyCondition)) {
343 return buildPropertyConditionExpression(graph, r);
345 else if (graph.isInstanceOf(r, ES.RegionCondition)) {
346 Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
347 String name = graph.getRelatedValue(region, L0.HasLabel);
348 return "in region " + name;
350 else if (graph.isInstanceOf(r, ES.RouteCondition)) {
351 Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
352 String name = graph.getRelatedValue(route, L0.HasLabel);
353 return "in route " + name;
355 else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
356 String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
357 List<String> exps = new ArrayList<>();
358 Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
359 for (Resource c : objects) {
360 String exp = getExpression(graph, c);
361 exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
363 String result = String.join(op, exps);
364 if (graph.isInstanceOf(r, ES.Negation))
365 result = "not {" + result + "}";
369 throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
372 else if (graph.isInstanceOf(r, ES.Selector)) {
373 if (graph.isInstanceOf(r, ES.Selector_All)) {
376 else if (graph.isInstanceOf(r, ES.PropertySelector)) {
378 if (graph.isInstanceOf(r, ES.Selector_NLowest))
380 else if (graph.isInstanceOf(r, ES.Selector_NHighest))
383 throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
385 String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
386 Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
387 return op + " " + count + " of " + name;
390 throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
393 else if (graph.isInstanceOf(r, ES.Generator)) {
394 if (graph.isInstanceOf(r, ES.Generator_Model)) {
397 else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
398 return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
400 else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
401 return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
404 throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
408 throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
412 private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
413 String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
414 Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
415 Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
416 if (lowerLimit == null && upperLimit == null) {
417 return "has property " + propertyName;
420 StringBuilder result = new StringBuilder();
421 if (lowerLimit != null) {
422 result.append(lowerLimit);
423 result.append(" < ");
425 result.append(propertyName);
426 if (upperLimit != null) {
427 result.append(" < ");
428 result.append(upperLimit);
430 return result.toString();
434 private static Collection<Resource> elementsOfDiagram(ReadGraph graph, Resource diagram) throws DatabaseException {
435 if (graph.isInstanceOf(diagram, STR.Composite)) {
436 // Resource is a composite - get diagram
437 return elementsOfDiagram(graph, graph.getSingleObject(diagram, MOD.CompositeToDiagram));
440 Collection<Resource> elements = graph.getObjects(diagram, L0.ConsistsOf);
441 Collection<Resource> result = new ArrayList<>();
442 for (Resource r : elements)
443 if (graph.isInstanceOf(r, DIA.Element))
448 private Collection<Resource> filterElementsFrom(ReadGraph graph, Collection<Resource> elements) throws DatabaseException {
449 if (condition == null) return elements;
451 ArrayList<Resource> result = new ArrayList<>();
452 for (Resource r : elements) {
453 if (condition.match(graph, r))
460 private SelectionResult gather(ReadGraph graph, Resource model) throws DatabaseException {
461 return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
466 public static abstract class Generator {
467 abstract Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException;
470 public static class ModelGenerator extends Generator {
472 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
473 Resource conf = graph.syncRequest(new Configuration(model));
475 HashMap<Resource, Resource> fromMapped = new HashMap<Resource, Resource>();
477 // Iterate over diagrams
478 // Each model element is represented by the corresponding mapping element, if present
479 for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) {
480 if (!graph.isInstanceOf(comp, STR.Composite)) continue;
482 Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram);
483 if (diagram != null) {
484 for (Resource elem : elementsOfDiagram(graph, diagram)) {
485 Resource mapped = graph.getPossibleObject(elem, DN.MappedComponent);
486 if (mapped != null) {
487 fromMapped.put(mapped, elem);
489 else if (!fromMapped.containsKey(elem)) {
490 fromMapped.put(elem, elem);
496 return new ArrayList<>(fromMapped.values());
500 public static class DiagramGenerator extends Generator {
501 public Resource diagram;
503 public DiagramGenerator(Resource diagram) {
504 this.diagram = diagram;
508 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
509 return elementsOfDiagram(graph, diagram);
513 public static class ExplicitGenerator extends Generator {
514 public ExplicitGenerator(Collection<Resource> elements) {
515 this.elements = elements;
518 public Collection<Resource> elements;
521 Collection<Resource> generate(ReadGraph graph, Resource model) {
528 public static class SelectionResult {
529 public final Collection<Resource> elements;
530 public final int tailCount;
531 public final int tailSize;
533 public SelectionResult(Collection<Resource> elements, int tailCount, int tailSize) {
534 this.elements = elements;
535 this.tailCount = tailCount;
536 this.tailSize = tailSize;
540 public static abstract class Selector {
541 abstract SelectionResult select(ReadGraph graph, Collection<Resource> elements);
544 public static class All extends Selector {
549 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
550 return new SelectionResult(elements, 0, 0);
554 public static class PropertySelector extends Selector {
555 public PropertySelector(boolean smallest, String propertyName, int resultCount) {
556 this.smallest = smallest;
557 this.propertyName = propertyName;
558 this.resultCount = resultCount;
561 public boolean smallest;
562 public String propertyName;
563 public int resultCount;
566 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
567 // Select sorting direction
568 Comparator<Pair<Resource, Double>> comparator = smallest ?
569 (p1, p2) -> Double.compare(p1.second, p2.second) :
570 (p1, p2) -> Double.compare(p1.second, p2.second);
572 // Get association list to property values
573 List<Pair<Resource, Double>> result2 = elements.stream()
574 .map(r -> Pair.make(r, getPropertyValue(graph, r, propertyName)))
575 .filter(t -> t.second != null)
577 .collect(Collectors.toList());
578 int count = Math.min(resultCount, result2.size());
580 // Count number of equal values at the end of the list
582 double tailValue = count > 0 ? result2.get(count-1).second : 0.0;
583 for (int i = count-1; i >= 0; i--) {
584 if (result2.get(i).second == tailValue)
590 // Count number of elements with value equal to the end of the list
591 int tailSize = tailCount;
592 for (int i = count; i < result2.size(); i++) {
593 if (result2.get(i).second == tailValue)
599 // Take first n items
600 if (count < result2.size()) {
601 result2 = result2.subList(0, resultCount);
604 // Map to list or resources
605 List<Resource> selection = result2.stream().map(p -> p.first).collect(Collectors.toList());
606 return new SelectionResult(selection, tailCount, tailSize);
612 public static abstract class Condition {
613 public Resource resource;
615 Condition(Resource r) {
619 abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
622 public static class PropertyCondition extends Condition {
623 public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) {
626 this.propertyName = propertyName;
627 this.lowerLimit = lowerLimit;
628 this.upperLimit = upperLimit;
631 public String propertyName;
632 public Double lowerLimit;
633 public Double upperLimit;
636 boolean match(ReadGraph graph, Resource r) {
637 Double value = getPropertyValue(graph, r, propertyName);
638 return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
642 public static class RegionCondition extends Condition {
643 public RegionCondition(Resource r, Resource regionResoruce, double[] region) {
645 this.region = region;
647 Path2D path = new Path2D.Double();
648 double startX = region[0];
649 double startY = region[1];
650 path.moveTo(startX, startY);
651 for (int i = 2; i < region.length; i+=2)
652 path.lineTo(region[i], region[i+1]);
656 this.regionResource = regionResoruce;
659 public Resource regionResource;
664 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
665 double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
666 double x = transform[4];
667 double y = transform[5];
668 return path.contains(x, y);
672 public static class RouteCondition extends Condition {
673 public RouteCondition(Resource r, Resource routeResource, Set<Resource> routePoints) {
675 this.routePoints = routePoints;
676 this.routeResource = routeResource;
679 public Resource routeResource;
680 Set<Resource> routePoints;
683 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
684 return routePoints.contains(r);
688 public static class AggregateCondition extends Condition {
689 public static enum Type { DISJUNCTION, CONJUNCTION, NEGATION };
691 public AggregateCondition(Resource r, Type type, List<Condition> conditions) {
694 this.conditions = conditions;
698 public List<Condition> conditions;
701 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
704 for (Condition c : conditions)
705 if (c.match(graph, r)) return true;
708 for (Condition c : conditions)
709 if (!c.match(graph, r)) return false;
712 for (Condition c : conditions)
713 if (c.match(graph, r)) return false;
717 throw new IllegalArgumentException("Unknown aggregate condition type " + type);
722 public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {
723 public ElementSelectorQuery(Resource resource) {
728 public ElementSelector perform(ReadGraph graph) throws DatabaseException {
729 return new ElementSelector(graph, resource);
733 public final static class SelectionExpressionRequest extends ResourceRead<String> {
734 public SelectionExpressionRequest(Resource condition) {
739 public String perform(ReadGraph graph) throws DatabaseException {
740 return ElementSelector.buildExpression(graph, resource);
744 public static final class SelectionConditionRequest extends ResourceRead<Condition> {
745 public SelectionConditionRequest(Resource resource) {
750 public Condition perform(ReadGraph graph) throws DatabaseException {
751 return buildCondition(graph, resource);