]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java
6de06ff690ea23a03304e119e412309301e581c1
[simantics/district.git] / org.simantics.district.selection / src / org / simantics / district / selection / ElementSelector.java
1 package org.simantics.district.selection;
2
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;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.stream.Collectors;
14
15 import org.simantics.Simantics;
16 import org.simantics.databoard.Bindings;
17 import org.simantics.db.ReadGraph;
18 import org.simantics.db.RequestProcessor;
19 import org.simantics.db.Resource;
20 import org.simantics.db.WriteGraph;
21 import org.simantics.db.common.request.ResourceRead;
22 import org.simantics.db.common.utils.ListUtils;
23 import org.simantics.db.exception.AssumptionException;
24 import org.simantics.db.exception.DatabaseException;
25 import org.simantics.db.exception.DoesNotContainValueException;
26 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
27 import org.simantics.db.exception.NoSingleResultException;
28 import org.simantics.db.exception.ServiceException;
29 import org.simantics.db.exception.ValidationException;
30 import org.simantics.db.layer0.QueryIndexUtils;
31 import org.simantics.db.layer0.request.ActiveModels;
32 import org.simantics.db.layer0.request.ActiveRuns;
33 import org.simantics.db.layer0.request.Configuration;
34 import org.simantics.db.layer0.request.PossibleActiveModel;
35 import org.simantics.db.layer0.variable.RVI;
36 import org.simantics.db.layer0.variable.Variable;
37 import org.simantics.db.layer0.variable.Variables;
38 import org.simantics.db.request.Read;
39 import org.simantics.diagram.stubs.DiagramResource;
40 import org.simantics.district.network.ontology.DistrictNetworkResource;
41 import org.simantics.district.region.ontology.DiagramRegionsResource;
42 import org.simantics.layer0.Layer0;
43 import org.simantics.modeling.ModelingResources;
44 import org.simantics.structural.stubs.StructuralResource2;
45 import org.simantics.utils.datastructures.Pair;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public class ElementSelector {
50         String name;
51         String expression;
52         
53         Resource resource;
54         
55         Generator generator;
56         Selector selector;
57         Condition condition;
58         
59         private float[] color;
60         
61         static Logger LOG = LoggerFactory.getLogger(ElementSelector.class);
62         
63         static ElementSelectionResource ES;
64         static Layer0 L0;
65         static StructuralResource2 STR;
66         static ModelingResources MOD;
67         static DiagramResource DIA;
68         static DistrictNetworkResource DN;
69         
70         private static Logger LOGGER = LoggerFactory.getLogger(ElementSelector.class);
71         
72         ElementSelector(ReadGraph graph, Resource resource) throws DatabaseException {
73                 super();
74                 
75                 L0 = Layer0.getInstance(graph);
76                 ES = ElementSelectionResource.getInstance(graph);
77                 STR = StructuralResource2.getInstance(graph);
78                 MOD = ModelingResources.getInstance(graph);
79                 DIA = DiagramResource.getInstance(graph);
80                 DN = DistrictNetworkResource.getInstance(graph);
81                 
82                 this.resource = resource;
83                 try {
84                         this.name = graph.getRelatedValue(resource, L0.HasLabel);
85                         this.expression = getExpression(graph, resource);
86                         
87                         float[] color = graph.getPossibleRelatedValue(resource, ES.Selection_HasHighlightColor);
88                         this.color = color;
89                 } catch (DatabaseException e) {
90                         LOG.error("Error reading element selector", e);
91                         throw e;
92                 }
93         }
94         
95         /**
96          * Instantiate an element selector object for an ES.Selection resource.
97          */
98         public static ElementSelector getSelector(RequestProcessor graph, Resource resource) throws DatabaseException {
99                 return graph.syncRequest(new ElementSelectorQuery(resource));
100         }
101         
102         /**
103          * Get an SQL-like textual description of an ES.Selection resource.
104          */
105         public static String getExpression(RequestProcessor graph, Resource resource) throws DatabaseException {
106                 return graph.syncRequest(new SelectionExpressionRequest(resource));
107         }
108
109         /**
110          * Get a Java object representation of an ES.Condition resource.
111          */
112         public static Condition getCondition(RequestProcessor graph, Resource condition) throws DatabaseException {
113                 return graph.syncRequest(new SelectionConditionRequest(condition));
114         }
115
116         /**
117          * Get the name of the element selector.
118          */
119         public String getName() {
120                 return name;
121         }
122
123         /**
124          * Get a textual SQL-like description of the element selector.
125          */
126         public String getExpression() {
127                 return expression;
128         }
129
130         /**
131          * Get the resource that this selector represents. 
132          */
133         public Resource getResource() {
134                 return resource;
135         }
136
137         /**
138          * Get the generator component. Use {@link #buildSelection(ReadGraph)} to make it available first.
139          */
140         public Generator getGenerator() {
141                 return generator;
142         }
143
144         /**
145          * Get the selector component. Use {@link #buildSelection(ReadGraph)} to make it available first.
146          */
147         public Selector getSelector() {
148                 return selector;
149         }
150
151         /**
152          * Get the condition component. Use {@link #buildSelection(ReadGraph)} to make it available first.
153          */
154         public Condition getCondition() {
155                 return condition;
156         }
157         
158         /**
159          * Get the selection highlight color as a four element BGRA array.
160          */
161         public float[] getColor() {
162                 return color;
163         }
164
165         /**
166          * 
167          * @param graph
168          * @throws DatabaseException
169          */
170         public void buildSelection(ReadGraph graph) throws DatabaseException {
171                 Resource selector = graph.getSingleObject(resource, ES.Selection_HasSelector);
172                 Resource generator = graph.getSingleObject(resource, ES.Selection_HasGenerator);
173                 Resource condition = graph.getPossibleObject(resource, ES.Selection_HasCondition);
174                 
175                 this.selector = buildSelector(graph, selector);
176                 this.generator = buildGenerator(graph, generator);
177                 this.condition = buildCondition(graph, condition);
178         }
179
180         public static Map<Resource, String> findDiagrams() {
181                 try {
182                         return Simantics.getSession().syncRequest(new Read<Map<Resource, String>>() {
183                                 @Override
184                                 public Map<Resource, String> perform(ReadGraph graph) throws DatabaseException {
185                                         Layer0 L0 = Layer0.getInstance(graph);
186                                         StructuralResource2 STR = StructuralResource2.getInstance(graph);
187                                         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
188                                         ModelingResources MOD = ModelingResources.getInstance(graph);
189                                         
190                                         Map<Resource, String> result = new HashMap<>();
191                                         Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
192                                         List<Resource> composites = QueryIndexUtils.searchByType(graph, model, STR.Composite);
193                                         for (Resource r : composites) {
194                                                 // Get diagram
195                                                 Resource diagram = graph.getPossibleObject(r, MOD.CompositeToDiagram);
196                                                 if (diagram == null || !graph.isInstanceOf(diagram, DN.Diagram))
197                                                         continue;
198                                                 
199                                                 // Filter out user component diagrams
200                                                 Resource parent = graph.getPossibleObject(r, L0.PartOf);
201                                                 if (parent == null || graph.isInheritedFrom(parent, STR.Component))
202                                                         continue;
203                                                 
204                                                 result.put(r, graph.getRelatedValue(r, L0.HasName));
205                                         }
206                                         
207                                         return result;
208                                 }
209                         });
210                 } catch (DatabaseException e) {
211                         LOGGER.error("Query for model diagrams failed", e);
212                         return Collections.emptyMap();
213                 }
214         }
215
216         public static Variable getVariableForElement(ReadGraph graph, Resource element) throws DatabaseException {
217                 Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
218                 if (component != null) {
219                         Variable var = Variables.getVariable(graph, component);
220                         RVI realRvi = var.getRVI(graph);
221                         
222                         for (Resource activeModel : graph.syncRequest(new ActiveModels(Simantics.getProjectResource()))) {
223                                 for (Variable run : graph.syncRequest(new ActiveRuns(activeModel))) {
224                                         Variable v = realRvi.resolvePossible(graph, run);
225                                         if (v != null) {
226                                                 return v;
227                                         }
228                                 }
229                                 Variable configuration = Variables.getPossibleConfigurationContext(graph, activeModel);
230                                 if (configuration != null) {
231                                         Variable v = realRvi.resolvePossible(graph, configuration);
232                                         if (v != null) {
233                                                 return v;
234                                         }
235                                 }
236                         }
237                 }
238                 
239                 return null;
240         }
241
242         public static Double getPropertyValue(ReadGraph graph, Resource element, String propertyName) {
243                 try {
244                         Variable v = getVariableForElement(graph, element);
245                         if (v != null) {
246                                 Number value = v.getPossiblePropertyValue(graph, propertyName);
247                                 if (value != null)
248                                         return value.doubleValue();
249                         }
250                         
251                         // No property found - try possible mapped element property as well
252                         Resource mappedElement = graph.getPossibleObject(element, DN.MappedComponent);
253                         if (mappedElement != null)
254                                 return getPropertyValue(graph, mappedElement, propertyName);
255                 }
256                 catch (DatabaseException e) {
257                 }
258                 
259                 return null;
260         }
261
262         public SelectionResult selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
263                 if (selector == null) {
264                         buildSelection(graph);
265                 }
266                 
267                 return gather(graph, model);
268         }
269
270         private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
271                 if (!graph.isInstanceOf(resource, ES.Generator))
272                         throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
273                 
274                 if (graph.isInstanceOf(resource, ES.Generator_Model))
275                         return new ModelGenerator();
276                 else if (graph.isInstanceOf(resource, ES.Generator_Diagram))
277                         return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
278                 else if (graph.isInstanceOf(resource, ES.Generator_Explicit))
279                         return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
280                 else
281                         throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
282         }
283
284         private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
285                 if (!graph.isInstanceOf(resource, ES.Selector))
286                         throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
287                 
288                 Selector s;
289                 if (graph.isInstanceOf(resource, ES.Selector_All))
290                         s = new All();
291                 else if (graph.isInstanceOf(resource, ES.PropertySelector))
292                         s = new PropertySelector(graph, resource);
293                 else
294                         throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
295                 
296                 Resource mapping = graph.getPossibleObject(resource, ES.Selector_HasMapping);
297                 s.componentType = mapping;
298                 
299                 return s;
300         }
301
302         private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException {
303                 if (resource == null)
304                         return null;
305                 
306                 if (!graph.isInstanceOf(resource, ES.Condition))
307                         throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
308                 
309                 Condition cond;
310                 if (graph.isInstanceOf(resource, ES.PropertyCondition))
311                         cond = new PropertyCondition(graph, resource);
312                 else if (graph.isInstanceOf(resource, ES.RegionCondition))
313                         cond = new RegionCondition(graph, resource);
314                 else if (graph.isInstanceOf(resource, ES.RouteCondition))
315                         cond = new RouteCondition(graph, resource);
316                 else if (graph.isInstanceOf(resource, ES.AggregateCondition))
317                         cond = new AggregateCondition(graph, resource);
318                 else
319                         throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
320                 
321                 return cond;
322         }
323
324         private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
325                 if (graph.isInstanceOf(r, ES.Selection))
326                         return buildSelectionExpression(graph, r);
327                 else if (graph.isInstanceOf(r, ES.Condition))
328                         return buildConditionExpression(graph, r);
329                 else if (graph.isInstanceOf(r, ES.Selector))
330                         return buildSelectorExpression(graph, r);
331                 else if (graph.isInstanceOf(r, ES.Generator))
332                         return buildGeneratorExpression(graph, r);
333                 else
334                         throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
335         }
336
337         private static String buildSelectionExpression(ReadGraph graph, Resource r) throws DatabaseException,
338                         NoSingleResultException, ManyObjectsForFunctionalRelationException, ServiceException {
339                 String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + 
340                              " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
341                 
342                 Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
343                 return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
344         }
345
346         private static String buildGeneratorExpression(ReadGraph graph, Resource r)
347                         throws ServiceException, NoSingleResultException, DoesNotContainValueException,
348                         ManyObjectsForFunctionalRelationException, DatabaseException, AssumptionException, ValidationException {
349                 if (graph.isInstanceOf(r, ES.Generator_Model)) {
350                         return "model";
351                 }
352                 else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
353                         return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
354                 }
355                 else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
356                         return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
357                 }
358                 else {
359                         throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
360                 }
361         }
362
363         private static String buildSelectorExpression(ReadGraph graph, Resource r)
364                         throws ServiceException, NoSingleResultException, DoesNotContainValueException, DatabaseException,
365                         AssumptionException, ValidationException, ManyObjectsForFunctionalRelationException {
366                 String exp;
367                 if (graph.isInstanceOf(r, ES.Selector_All)) {
368                         exp = "all";
369                 }
370                 else if (graph.isInstanceOf(r, ES.PropertySelector)) {
371                         Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
372                         exp = count.toString();
373                 }
374                 else {
375                         throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
376                 }
377                 
378                 Resource mapping = graph.getPossibleObject(r, ES.Selector_HasMapping);
379                 if (mapping != null) {
380                         String name = graph.getRelatedValue2(mapping, L0.HasName);
381                         exp = exp + " " + name;
382                 } else {
383                         exp = exp + " elements";
384                 }
385                 
386                 if (graph.isInstanceOf(r, ES.PropertySelector)) {
387                         String op;
388                         if (graph.isInstanceOf(r, ES.Selector_NLowest))
389                                 op = "lowest";
390                         else if (graph.isInstanceOf(r, ES.Selector_NHighest))
391                                 op = "highest";
392                         else
393                                 throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
394                         
395                         String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
396                         exp = exp + " with " + op + " " + name;
397                 }
398                 
399                 return exp;
400         }
401
402         private static String buildConditionExpression(ReadGraph graph, Resource r) throws ServiceException,
403                         DatabaseException, NoSingleResultException, ManyObjectsForFunctionalRelationException,
404                         DoesNotContainValueException, AssumptionException, ValidationException {
405                 String result;
406                 boolean isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r);
407                 if (graph.isInstanceOf(r, ES.PropertyCondition)) {
408                         result = buildPropertyConditionExpression(graph, r);
409                 }
410                 else if (graph.isInstanceOf(r, ES.RegionCondition)) {
411                         result = buildRegionConditionExpression(graph, r);
412                 }
413                 else if (graph.isInstanceOf(r, ES.RouteCondition)) {
414                         result = buildRouteConditionExpression(graph, r);
415                 }
416                 else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
417                         // This handles isInverse internally
418                         return buildAggregateConditionExpression(graph, r, isInverse);
419                 }
420                 else {
421                         throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
422                 }
423                 
424                 if (isInverse)
425                         result = "not {" + result + "}";
426                 
427                 return result;
428         }
429
430         private static String buildAggregateConditionExpression(ReadGraph graph, Resource r, boolean isInverse)
431                         throws ServiceException, DatabaseException {
432                 String result;
433                 String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
434                 List<String> exps = new ArrayList<>();
435                 Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
436                 for (Resource c : objects) {
437                         String exp = getExpression(graph, c);
438                         exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
439                 }
440                 result = String.join(op, exps);
441                 if (graph.isInstanceOf(r, ES.Negation) ^ isInverse)
442                         result = "not {" + result + "}";
443                 return result;
444         }
445
446         private static String buildRouteConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException,
447                         ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException {
448                 String result;
449                 Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
450                 String name = graph.getRelatedValue(route, L0.HasLabel);
451                 result = "in route " + name;
452                 return result;
453         }
454
455         private static String buildRegionConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException,
456                         ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException {
457                 String result;
458                 Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
459                 String name = graph.getRelatedValue(region, L0.HasLabel);
460                 result = "in region " + name;
461                 return result;
462         }
463
464         private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
465                 String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
466                 Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
467                 Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
468                 if (upperLimit == null) {
469                         if (lowerLimit == null) {
470                                 return "has property " + propertyName;
471                         } else {
472                                 return propertyName + " \u2265 " + lowerLimit;
473                         }
474                 } else {
475                         StringBuilder result = new StringBuilder();
476                         if (lowerLimit != null) {
477                                 result.append(lowerLimit);
478                                 result.append(" \u2264 ");
479                         }
480                         result.append(propertyName);
481                         result.append(" \u2264 ");
482                         result.append(upperLimit);
483                         return result.toString();
484                 }
485         }
486
487         private static Collection<Resource> elementsOfDiagram(ReadGraph graph, Resource diagram) throws DatabaseException {
488                 if (graph.isInstanceOf(diagram, STR.Composite)) {
489                         // Resource is a composite - get diagram
490                         return elementsOfDiagram(graph, graph.getSingleObject(diagram, MOD.CompositeToDiagram));
491                 }
492                 
493                 Collection<Resource> elements = graph.getObjects(diagram, L0.ConsistsOf);
494                 Collection<Resource> result = new ArrayList<>();
495                 for (Resource r : elements)
496                         if (graph.isInstanceOf(r, DIA.Element))
497                                 result .add(r);
498                 return result;
499         }
500
501         private Collection<Resource> filterElementsFrom(ReadGraph graph, Collection<Resource> elements) throws DatabaseException {
502                 if (condition == null) return elements;
503                 
504                 ArrayList<Resource> result = new ArrayList<>();
505                 for (Resource r : elements) {
506                         if (condition.match(graph, r))
507                                 result.add(r);
508                 }
509                 
510                 return result;
511         }
512         
513         private SelectionResult gather(ReadGraph graph, Resource model) throws DatabaseException {
514                 return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
515         }
516         
517         // Generators
518         
519         public static abstract class Generator {
520                 abstract Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException;
521         }
522         
523         public static class ModelGenerator extends Generator {
524                 @Override
525                 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
526                         Resource conf = graph.syncRequest(new Configuration(model));
527                         
528                         HashMap<Resource, Resource> fromMapped = new HashMap<Resource, Resource>();
529                         
530                         // Iterate over diagrams
531                         // Each model element is represented by the corresponding mapping element, if present
532                         for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) {
533                                 if (!graph.isInstanceOf(comp, STR.Composite)) continue;
534                                 
535                                 Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram);
536                                 if (diagram != null) {
537                                         for (Resource elem : elementsOfDiagram(graph, diagram)) {
538                                                 Resource mapped = graph.getPossibleObject(elem, DN.MappedComponent);
539                                                 if (mapped != null) {
540                                                         fromMapped.put(mapped, elem);
541                                                 }
542                                                 else if (!fromMapped.containsKey(elem)) {
543                                                         fromMapped.put(elem, elem);
544                                                 }
545                                         }
546                                 }
547                         }
548                         
549                         return new ArrayList<>(fromMapped.values());
550                 }
551         }
552         
553         public static class DiagramGenerator extends Generator {
554                 public Resource diagram;
555                 
556                 public DiagramGenerator(Resource diagram) {
557                         this.diagram = diagram;
558                 }
559
560                 @Override
561                 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
562                         return elementsOfDiagram(graph, diagram);
563                 }
564         }
565         
566         public static class ExplicitGenerator extends Generator {
567                 public ExplicitGenerator(Collection<Resource> elements) {
568                         this.elements = elements;
569                 }
570
571                 public Collection<Resource> elements;
572
573                 @Override
574                 Collection<Resource> generate(ReadGraph graph, Resource model) {
575                         return elements;
576                 }
577         }
578         
579         // Selectors
580         
581         public static class SelectionResult {
582                 public final Collection<Resource> elements;
583                 public final int tailCount;
584                 public final int tailSize;
585                 
586                 public SelectionResult(Collection<Resource> elements, int tailCount, int tailSize) {
587                         this.elements = elements;
588                         this.tailCount = tailCount;
589                         this.tailSize = tailSize;
590                 }
591         }
592
593         public static abstract class Selector {
594                 public Resource componentType = null;
595                 
596                 abstract SelectionResult select(ReadGraph graph, Collection<Resource> elements);
597                 
598                 Collection<Resource> filterElements(ReadGraph graph, Collection<Resource> elements) {
599                         if (componentType == null)
600                                 return elements;
601                         
602                         Collection<Resource> selected = new HashSet<>(elements.size());
603                         for (Resource r : elements) {
604                                 try {
605                                         if (graph.hasStatement(r, DN.HasMapping, componentType))
606                                                 selected.add(r);
607                                 } catch (DatabaseException e) {
608                                         // Just leave it out of the result
609                                 }
610                         }
611                         
612                         return selected;
613                 }
614         }
615         
616         public static class All extends Selector {
617                 public All() {
618                 }
619
620                 @Override
621                 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
622                         Collection<Resource> selected = filterElements(graph, elements);
623                         return new SelectionResult(selected, 0, 0);
624                 }
625         }
626         
627         public static class PropertySelector extends Selector {
628                 public PropertySelector(boolean smallest, String propertyName, int resultCount) {
629                         this.smallest = smallest;
630                         this.propertyName = propertyName;
631                         this.resultCount = resultCount;
632                 }
633                 
634                 public PropertySelector(ReadGraph graph, Resource resource) throws DatabaseException {
635                         this.propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
636                         this.resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
637                         this.smallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
638                 }
639
640                 public boolean smallest;
641                 public String propertyName;
642                 public int resultCount;
643                 
644                 @Override
645                 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
646                         elements = filterElements(graph, elements);
647                         
648                         // Select sorting direction
649                         Comparator<Pair<Resource, Double>> comparator = smallest ?
650                                         (p1, p2) -> Double.compare(p1.second, p2.second) :
651                                         (p1, p2) -> Double.compare(p2.second, p1.second);
652                         
653                         // Get association list to property values
654                         List<Pair<Resource, Double>> result2 = elements.stream()
655                                         .map(r -> Pair.make(r, getPropertyValue(graph, r, propertyName)))
656                                         .filter(t -> t.second != null)
657                                         .sorted(comparator)
658                                         .collect(Collectors.toList());
659                         int count = Math.min(resultCount, result2.size());
660                         
661                         // Count number of equal values at the end of the list
662                         int tailCount = 0;
663                         double tailValue = count > 0 ? result2.get(count-1).second : 0.0;
664                         for (int i = count-1; i >= 0; i--) {
665                                 if (result2.get(i).second == tailValue)
666                                         tailCount++;
667                                 else
668                                         break;
669                         }
670                         
671                         // Count number of elements with value equal to the end of the list
672                         int tailSize = tailCount;
673                         for (int i = count; i < result2.size(); i++) {
674                                 if (result2.get(i).second == tailValue)
675                                         tailSize++;
676                                 else
677                                         break;
678                         }
679                         
680                         // Take first n items
681                         if (count < result2.size()) {
682                                 result2 = result2.subList(0, resultCount);
683                         }
684                         
685                         // Map to list or resources
686                         List<Resource> selection = result2.stream().map(p -> p.first).collect(Collectors.toList());
687                         return new SelectionResult(selection, tailCount, tailSize);
688                 }
689         }
690         
691         // Conditions
692         
693         public static abstract class Condition {
694                 public Resource resource;
695                 public boolean isInverse;
696                 
697                 Condition(Resource r) {
698                         resource = r;
699                         isInverse = false;
700                 }
701                 
702                 Condition(ReadGraph graph, Resource r) throws DatabaseException {
703                         this(r);
704                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
705                         isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r);
706                 }
707                 
708                 public abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
709                 public Resource update(WriteGraph graph) throws DatabaseException {
710                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
711                         
712                         assert(resource != null);
713                         if (isInverse)
714                                 graph.claim(resource, ES.Condition_IsInverse, resource);
715                         else
716                                 graph.deny(resource, ES.Condition_IsInverse, resource);
717                         return resource;
718                 }
719         }
720         
721         public static class PropertyCondition extends Condition {
722                 public PropertyCondition(ReadGraph graph, Resource r) throws DatabaseException {
723                         super(graph, r);
724                         
725                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
726                         this.propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
727                         this.lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
728                         this.upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
729                 }
730                 
731                 public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) {
732                         super(r);
733                         
734                         this.propertyName = propertyName;
735                         this.lowerLimit = lowerLimit;
736                         this.upperLimit = upperLimit;
737                 }
738                 
739                 public String propertyName;
740                 public Double lowerLimit;
741                 public Double upperLimit;
742                 
743                 @Override
744                 public boolean match(ReadGraph graph, Resource r) {
745                         Double value = getPropertyValue(graph, r, propertyName);
746                         boolean result = value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
747                         return result ^ isInverse;
748                 }
749                 
750                 @Override
751                 public Resource update(WriteGraph graph) throws DatabaseException {
752                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
753                         Layer0 L0 = Layer0.getInstance(graph);
754                         
755                         if (resource == null) {
756                                 resource = graph.newResource();
757                                 graph.claim(resource, L0.InstanceOf, ES.PropertyCondition);
758                         }
759                         
760                         super.update(graph);
761                         
762                         graph.claimLiteral(resource, ES.PropertyCondition_HasPropertyName, propertyName);
763                         if (lowerLimit != null)
764                                 graph.claimLiteral(resource, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit);
765                         else
766                                 graph.deny(resource, ES.PropertyCondition_HasLowerLimit);
767                         
768                         if (upperLimit != null)
769                                 graph.claimLiteral(resource, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit);
770                         else
771                                 graph.deny(resource, ES.PropertyCondition_HasUpperLimit);
772                         return resource;
773                 }
774         }
775         
776         public static class RegionCondition extends Condition {
777                 public RegionCondition(Resource r, Resource regionResource, double[] region) {
778                         super(r);
779                         this.region = region;
780                         this.path = createPathForRegion(region);
781                         this.regionResource = regionResource;
782                 }
783
784                 public RegionCondition(ReadGraph graph, Resource r) throws DatabaseException {
785                         super(graph, r);
786                         
787                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
788                         DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
789                         this.regionResource = graph.getPossibleObject(resource, ES.RegionCondition_HasRegion);
790                         this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area) : null;
791                         this.path = createPathForRegion(region);
792                 }
793
794                 public static Path2D createPathForRegion(double[] region) {
795                         Path2D path = new Path2D.Double();
796                         if (region != null) {
797                                 double startX = region[0];
798                                 double startY = region[1];
799                                 path.moveTo(startX, startY);
800                                 for (int i = 2; i < region.length; i+=2)
801                                         path.lineTo(region[i], region[i+1]);
802                                 path.closePath();
803                         }
804
805                         return path;
806                 }
807
808                 public Resource regionResource;
809                 double[] region;
810                 Path2D path;
811
812                 @Override
813                 public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
814                         double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
815                         double x = transform[4];
816                         double y = transform[5];
817                         return path.contains(x, y) ^ isInverse;
818                 }
819                 
820                 @Override
821                 public Resource update(WriteGraph graph) throws DatabaseException {
822                         Layer0 L0 = Layer0.getInstance(graph);
823                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
824                         DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
825                         
826                         if (resource == null) {
827                                 resource = graph.newResource();
828                                 graph.claim(resource, L0.InstanceOf, ES.RegionCondition);
829                         }
830                         
831                         super.update(graph);
832                         
833                         graph.claim(resource, ES.RegionCondition_HasRegion, regionResource);
834                         
835                         // Re-read region data to match DB
836                         this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area, Bindings.DOUBLE_ARRAY) : null;
837                         this.path = createPathForRegion(region);
838                         
839                         return resource;
840                 }
841         }
842         
843         public static class RouteCondition extends Condition {
844                 public RouteCondition(Resource r, Resource routeResource, Set<Resource> routePoints) {
845                         super(r);
846                         this.routePoints = routePoints;
847                         this.routeResource = routeResource;
848                 }
849
850                 public RouteCondition(ReadGraph graph, Resource r) throws DatabaseException {
851                         super(graph, r);
852                         this.routeResource = graph.getPossibleObject(resource, ES.RouteCondition_HasRoute);
853                         this.routePoints = getRoutePoints(graph, routeResource);
854                 }
855
856                 public static Set<Resource> getRoutePoints(ReadGraph graph, Resource routeResource) throws DatabaseException {
857                         return routeResource != null ?
858                                         new HashSet<>(ListUtils.toList(graph, routeResource)) :
859                                         Collections.emptySet();
860                 }
861
862                 public Resource routeResource;
863                 Set<Resource> routePoints;
864
865                 @Override
866                 public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
867                         return routePoints.contains(r) ^ isInverse;
868                 }
869                 
870                 @Override
871                 public Resource update(WriteGraph graph) throws DatabaseException {
872                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
873                         Layer0 L0 = Layer0.getInstance(graph);
874                         
875                         if (resource == null) {
876                                 resource = graph.newResource();
877                                 graph.claim(resource, L0.InstanceOf, ES.RouteCondition);
878                         }
879                         
880                         super.update(graph);
881                         
882                         if (routeResource != null)
883                                 graph.claim(resource, ES.RouteCondition_HasRoute, routeResource);
884                         
885                         this.routePoints = getRoutePoints(graph, routeResource);
886                         
887                         return resource;
888                 }
889         }
890         
891         public static class AggregateCondition extends Condition {
892                 public static enum Type { DISJUNCTION, CONJUNCTION, NEGATION };
893                 
894                 public AggregateCondition(Resource r, Type type, List<Condition> conditions) {
895                         super(r);
896                         this.type = type;
897                         this.conditions = conditions;
898                 }
899                 
900                 public AggregateCondition(ReadGraph graph, Resource r) throws DatabaseException {
901                         super(graph, r);
902                         Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
903                         conditions = new ArrayList<>(conditionResources.size());
904                         for (Resource c : conditionResources) {
905                                 conditions.add(buildCondition(graph, c));
906                         }
907                         if (graph.isInstanceOf(resource, ES.Conjunction))
908                                 this.type = AggregateCondition.Type.CONJUNCTION;
909                         else if (graph.isInstanceOf(resource, ES.Negation))
910                                 this.type = AggregateCondition.Type.NEGATION;
911                         else if (graph.isInstanceOf(resource, ES.Disjunction))
912                                 this.type = AggregateCondition.Type.DISJUNCTION;
913                         else
914                                 throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
915                 }
916
917                 public Type type;
918                 public List<Condition> conditions;
919                 
920                 @Override
921                 public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
922                         return doMatch(graph, r) ^ isInverse;
923                 }
924                 
925                 private boolean doMatch(ReadGraph graph, Resource r) throws DatabaseException {
926                         switch (type) {
927                         case DISJUNCTION:
928                                 for (Condition c : conditions)
929                                         if (c.match(graph, r)) return true;
930                                 return false;
931                         case CONJUNCTION:
932                                 for (Condition c : conditions)
933                                         if (!c.match(graph, r)) return false;
934                                 return true;
935                         case NEGATION:
936                                 for (Condition c : conditions)
937                                         if (c.match(graph, r)) return false;
938                                 return true;
939                         default:
940                                 // Should not happen
941                                 throw new IllegalArgumentException("Unknown aggregate condition type " + type);
942                         }
943                 }
944                 
945                 @Override
946                 public Resource update(WriteGraph graph) throws DatabaseException {
947                         Layer0 L0 = Layer0.getInstance(graph);
948                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
949                         
950                         if (resource == null) {
951                                 resource = graph.newResource();
952                         } else {
953                                 graph.deny(resource, L0.InstanceOf);
954                                 graph.deny(resource, ES.HasSubcondition);
955                         }
956                         
957                         Resource type;
958                         switch (this.type) {
959                         case CONJUNCTION:
960                                 type = ES.Conjunction; break;
961                         case DISJUNCTION:
962                                 type = ES.Disjunction; break;
963                         case NEGATION:
964                                 type = ES.Negation; break;
965                         default:
966                                 throw new IllegalStateException("Unknown condition type " + this.type);
967                         }
968                         
969                         graph.claim(resource, L0.InstanceOf, type);
970                         
971                         super.update(graph);
972                         
973                         for (Condition c : conditions) {
974                                 graph.claim(resource, ES.HasSubcondition, c.update(graph));
975                         }
976                         
977                         return resource;
978                 }
979         }
980         
981         public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {
982                 public ElementSelectorQuery(Resource resource) {
983                         super(resource);
984                 }
985                 
986                 @Override
987                 public ElementSelector perform(ReadGraph graph) throws DatabaseException {
988                         return new ElementSelector(graph, resource);
989                 }
990         }
991
992         public final static class SelectionExpressionRequest extends ResourceRead<String> {
993                 public SelectionExpressionRequest(Resource condition) {
994                         super(condition);
995                 }
996         
997                 @Override
998                 public String perform(ReadGraph graph) throws DatabaseException {
999                         return ElementSelector.buildExpression(graph, resource);
1000                 }
1001         }
1002
1003         public static final class SelectionConditionRequest extends ResourceRead<Condition> {
1004                 public SelectionConditionRequest(Resource resource) {
1005                         super(resource);
1006                 }
1007         
1008                 @Override
1009                 public Condition perform(ReadGraph graph) throws DatabaseException {
1010                         return buildCondition(graph, resource);
1011                 }
1012         }
1013 }