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.HashMap;
8 import java.util.HashSet;
13 import org.simantics.Simantics;
14 import org.simantics.db.ReadGraph;
15 import org.simantics.db.RequestProcessor;
16 import org.simantics.db.Resource;
17 import org.simantics.db.common.request.ResourceRead;
18 import org.simantics.db.common.utils.ListUtils;
19 import org.simantics.db.exception.DatabaseException;
20 import org.simantics.db.layer0.QueryIndexUtils;
21 import org.simantics.db.layer0.request.ActiveModels;
22 import org.simantics.db.layer0.request.ActiveRuns;
23 import org.simantics.db.layer0.request.Configuration;
24 import org.simantics.db.layer0.request.PossibleActiveModel;
25 import org.simantics.db.layer0.variable.RVI;
26 import org.simantics.db.layer0.variable.Variable;
27 import org.simantics.db.layer0.variable.Variables;
28 import org.simantics.db.request.Read;
29 import org.simantics.diagram.stubs.DiagramResource;
30 import org.simantics.district.network.ontology.DistrictNetworkResource;
31 import org.simantics.district.region.ontology.DiagramRegionsResource;
32 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
33 import org.simantics.layer0.Layer0;
34 import org.simantics.modeling.ModelingResources;
35 import org.simantics.scl.runtime.Lists;
36 import org.simantics.scl.runtime.function.FunctionImpl1;
37 import org.simantics.scl.runtime.tuple.Tuple2;
38 import org.simantics.structural.stubs.StructuralResource2;
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 List<Resource> selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
238 if (selector == null) {
239 buildSelection(graph);
242 Collection<Resource> result = gather(graph, model);
243 return result instanceof List ? (List<Resource>) result : new ArrayList<>(result);
246 private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
247 if (!graph.isInstanceOf(resource, ES.Generator))
248 throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
250 if (graph.isInstanceOf(resource, ES.Generator_Model)) {
251 return new ModelGenerator();
253 else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) {
254 return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
256 else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) {
257 return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
260 throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
264 private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
265 if (!graph.isInstanceOf(resource, ES.Selector))
266 throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
269 if (graph.isInstanceOf(resource, ES.Selector_All)) {
272 else if (graph.isInstanceOf(resource, ES.PropertySelector)) {
273 String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
274 Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
275 boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
276 s = new PropertySelector(isSmallest, propertyName, resultCount);
279 throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
284 private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException {
285 if (resource == null)
288 if (!graph.isInstanceOf(resource, ES.Condition))
289 throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
292 if (graph.isInstanceOf(resource, ES.PropertyCondition)) {
293 String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
294 Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
295 Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
296 cond = new PropertyCondition(resource, propertyName, lowerLimit, upperLimit);
298 else if (graph.isInstanceOf(resource, ES.RegionCondition)) {
299 DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
300 Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion);
301 double[] region = graph.getRelatedValue(regionResource, DR.Region_area);
302 cond = new RegionCondition(resource, regionResource, region);
304 else if (graph.isInstanceOf(resource, ES.RouteCondition)) {
305 Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute);
306 Set<Resource> routePoints = new HashSet<>(ListUtils.toList(graph, routeResource));
307 cond = new RouteCondition(resource, routeResource, routePoints);
309 else if (graph.isInstanceOf(resource, ES.AggregateCondition)) {
310 Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
311 List<Condition> conditions = new ArrayList<>(conditionResources.size());
312 for (Resource c : conditionResources) {
313 conditions.add(buildCondition(graph, c));
316 if (graph.isInstanceOf(resource, ES.Conjunction))
317 type = AggregateCondition.Type.CONJUNCTION;
318 else if (graph.isInstanceOf(resource, ES.Negation))
319 type = AggregateCondition.Type.NEGATION;
320 else if (graph.isInstanceOf(resource, ES.Disjunction))
321 type = AggregateCondition.Type.DISJUNCTION;
323 throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
325 cond = new AggregateCondition(resource, type, conditions);
328 throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
334 private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
335 if (graph.isInstanceOf(r, ES.Selection)) {
336 String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) +
337 " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
339 Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
340 return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
342 else if (graph.isInstanceOf(r, ES.Condition)) {
343 if (graph.isInstanceOf(r, ES.PropertyCondition)) {
344 return buildPropertyConditionExpression(graph, r);
346 else if (graph.isInstanceOf(r, ES.RegionCondition)) {
347 Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
348 String name = graph.getRelatedValue(region, L0.HasLabel);
349 return "in region " + name;
351 else if (graph.isInstanceOf(r, ES.RouteCondition)) {
352 Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
353 String name = graph.getRelatedValue(route, L0.HasLabel);
354 return "in route " + name;
356 else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
357 String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
358 List<String> exps = new ArrayList<>();
359 Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
360 for (Resource c : objects) {
361 String exp = getExpression(graph, c);
362 exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
364 String result = String.join(op, exps);
365 if (graph.isInstanceOf(r, ES.Negation))
366 result = "not {" + result + "}";
370 throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
373 else if (graph.isInstanceOf(r, ES.Selector)) {
374 if (graph.isInstanceOf(r, ES.Selector_All)) {
377 else if (graph.isInstanceOf(r, ES.PropertySelector)) {
379 if (graph.isInstanceOf(r, ES.Selector_NLowest))
381 else if (graph.isInstanceOf(r, ES.Selector_NHighest))
384 throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
386 String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
387 Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
388 return op + " " + count + " of " + name;
391 throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
394 else if (graph.isInstanceOf(r, ES.Generator)) {
395 if (graph.isInstanceOf(r, ES.Generator_Model)) {
398 else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
399 return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
401 else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
402 return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
405 throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
409 throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
413 private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
414 String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
415 Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
416 Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
417 if (lowerLimit == null && upperLimit == null) {
418 return "has property " + propertyName;
421 StringBuilder result = new StringBuilder();
422 if (lowerLimit != null) {
423 result.append(lowerLimit);
424 result.append(" < ");
426 result.append(propertyName);
427 if (upperLimit != null) {
428 result.append(" < ");
429 result.append(upperLimit);
431 return result.toString();
435 private static Collection<Resource> elementsOfDiagram(ReadGraph graph, Resource diagram) throws DatabaseException {
436 if (graph.isInstanceOf(diagram, STR.Composite)) {
437 // Resource is a composite - get diagram
438 return elementsOfDiagram(graph, graph.getSingleObject(diagram, MOD.CompositeToDiagram));
441 Collection<Resource> elements = graph.getObjects(diagram, L0.ConsistsOf);
442 Collection<Resource> result = new ArrayList<>();
443 for (Resource r : elements)
444 if (graph.isInstanceOf(r, DIA.Element))
449 private Collection<Resource> filterElementsFrom(ReadGraph graph, Collection<Resource> elements) throws DatabaseException {
450 if (condition == null) return elements;
452 ArrayList<Resource> result = new ArrayList<>();
453 for (Resource r : elements) {
454 if (condition.match(graph, r))
461 private Collection<Resource> gather(ReadGraph graph, Resource model) throws DatabaseException {
462 return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
467 public static abstract class Generator {
468 abstract Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException;
471 public static class ModelGenerator extends Generator {
473 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
474 Resource conf = graph.syncRequest(new Configuration(model));
476 ArrayList<Resource> result = new ArrayList<>();
478 // Iterate over diagrams
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) result.addAll(elementsOfDiagram(graph, diagram));
490 public static class DiagramGenerator extends Generator {
491 public Resource diagram;
493 public DiagramGenerator(Resource diagram) {
494 this.diagram = diagram;
498 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
499 return elementsOfDiagram(graph, diagram);
503 public static class ExplicitGenerator extends Generator {
504 public ExplicitGenerator(Collection<Resource> elements) {
505 this.elements = elements;
508 public Collection<Resource> elements;
511 Collection<Resource> generate(ReadGraph graph, Resource model) {
518 public static abstract class Selector {
519 abstract Collection<Resource> select(ReadGraph graph, Collection<Resource> elements);
522 public static class All extends Selector {
527 Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
532 public static class PropertySelector extends Selector {
533 public PropertySelector(boolean smallest, String propertyName, int resultCount) {
534 this.smallest = smallest;
535 this.propertyName = propertyName;
536 this.resultCount = resultCount;
539 public boolean smallest;
540 public String propertyName;
541 public int resultCount;
543 @SuppressWarnings("unchecked")
545 Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
546 List<Tuple2> result2 = Lists.map(new FunctionImpl1<Resource, Tuple2>() {
548 public Tuple2 apply(Resource r) {
549 return new Tuple2(r, getPropertyValue(graph, r, propertyName));
551 }, new ArrayList<>(elements));
553 result2 = Lists.filter(new FunctionImpl1<Tuple2, Boolean>() {
555 public Boolean apply(Tuple2 t) {
560 result2.sort((t1, t2) -> smallest ? Double.compare((Double) t1.c1, (Double) t2.c1) : Double.compare((Double) t2.c1, (Double) t1.c1));
562 if (resultCount < result2.size()) {
563 double limitValue = (double) result2.get(resultCount-1).c1;
565 // Expand selection to contain all items with the same value as the nth one
566 int count = resultCount;
567 while (count < result2.size() && (double)result2.get(count).c1 == limitValue) count++;
568 result2 = result2.subList(0, count);
571 return (List<Resource>) Lists.map(new FunctionImpl1<Tuple2, Resource>() {
573 public Resource apply(Tuple2 p0) {
574 return (Resource) p0.c0;
582 public static abstract class Condition {
583 public Resource resource;
585 Condition(Resource r) {
589 abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
592 public static class PropertyCondition extends Condition {
593 public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) {
596 this.propertyName = propertyName;
597 this.lowerLimit = lowerLimit;
598 this.upperLimit = upperLimit;
601 public String propertyName;
602 public Double lowerLimit;
603 public Double upperLimit;
606 boolean match(ReadGraph graph, Resource r) {
607 Double value = getPropertyValue(graph, r, propertyName);
608 return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
612 public static class RegionCondition extends Condition {
613 public RegionCondition(Resource r, Resource regionResoruce, double[] region) {
615 this.region = region;
617 Path2D path = new Path2D.Double();
618 double startX = region[0];
619 double startY = region[1];
620 path.moveTo(startX, startY);
621 for (int i = 2; i < region.length; i+=2)
622 path.lineTo(region[i], region[i+1]);
626 this.regionResource = regionResoruce;
629 public Resource regionResource;
634 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
635 double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
636 double x = transform[4];
637 double y = transform[5];
638 return path.contains(x, y);
642 public static class RouteCondition extends Condition {
643 public RouteCondition(Resource r, Resource routeResource, Set<Resource> routePoints) {
645 this.routePoints = routePoints;
646 this.routeResource = routeResource;
649 public Resource routeResource;
650 Set<Resource> routePoints;
653 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
654 return routePoints.contains(r);
658 public static class AggregateCondition extends Condition {
659 public static enum Type { DISJUNCTION, CONJUNCTION, NEGATION };
661 public AggregateCondition(Resource r, Type type, List<Condition> conditions) {
664 this.conditions = conditions;
668 public List<Condition> conditions;
671 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
674 for (Condition c : conditions)
675 if (c.match(graph, r)) return true;
678 for (Condition c : conditions)
679 if (!c.match(graph, r)) return false;
682 for (Condition c : conditions)
683 if (c.match(graph, r)) return false;
687 throw new IllegalArgumentException("Unknown aggregate condition type " + type);
692 public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {
693 public ElementSelectorQuery(Resource resource) {
698 public ElementSelector perform(ReadGraph graph) throws DatabaseException {
699 return new ElementSelector(graph, resource);
703 public final static class SelectionExpressionRequest extends ResourceRead<String> {
704 public SelectionExpressionRequest(Resource condition) {
709 public String perform(ReadGraph graph) throws DatabaseException {
710 return ElementSelector.buildExpression(graph, resource);
714 public static final class SelectionConditionRequest extends ResourceRead<Condition> {
715 public SelectionConditionRequest(Resource resource) {
720 public Condition perform(ReadGraph graph) throws DatabaseException {
721 return buildCondition(graph, resource);