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