]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java
Change selection logic for n lowest/highest value queries
[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.db.ReadGraph;
17 import org.simantics.db.RequestProcessor;
18 import org.simantics.db.Resource;
19 import org.simantics.db.common.request.ResourceRead;
20 import org.simantics.db.common.utils.ListUtils;
21 import org.simantics.db.exception.DatabaseException;
22 import org.simantics.db.layer0.QueryIndexUtils;
23 import org.simantics.db.layer0.request.ActiveModels;
24 import org.simantics.db.layer0.request.ActiveRuns;
25 import org.simantics.db.layer0.request.Configuration;
26 import org.simantics.db.layer0.request.PossibleActiveModel;
27 import org.simantics.db.layer0.variable.RVI;
28 import org.simantics.db.layer0.variable.Variable;
29 import org.simantics.db.layer0.variable.Variables;
30 import org.simantics.db.request.Read;
31 import org.simantics.diagram.stubs.DiagramResource;
32 import org.simantics.district.network.ontology.DistrictNetworkResource;
33 import org.simantics.district.region.ontology.DiagramRegionsResource;
34 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
35 import org.simantics.layer0.Layer0;
36 import org.simantics.modeling.ModelingResources;
37 import org.simantics.structural.stubs.StructuralResource2;
38 import org.simantics.utils.datastructures.Pair;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 public class ElementSelector {
43         String name;
44         String expression;
45         
46         Resource resource;
47         
48         Generator generator;
49         Selector selector;
50         Condition condition;
51         
52         static Logger LOG = LoggerFactory.getLogger(ElementSelector.class);
53         
54         static ElementSelectionResource ES;
55         static Layer0 L0;
56         static StructuralResource2 STR;
57         static ModelingResources MOD;
58         static DiagramResource DIA;
59         static DistrictNetworkResource DN;
60         
61         private static Logger LOGGER = LoggerFactory.getLogger(ElementSelector.class);
62         
63         ElementSelector(ReadGraph graph, Resource resource) throws DatabaseException {
64                 super();
65                 
66                 L0 = Layer0.getInstance(graph);
67                 ES = ElementSelectionResource.getInstance(graph);
68                 STR = StructuralResource2.getInstance(graph);
69                 MOD = ModelingResources.getInstance(graph);
70                 DIA = DiagramResource.getInstance(graph);
71                 DN = DistrictNetworkResource.getInstance(graph);
72                 
73                 this.resource = resource;
74                 try {
75                         this.name = graph.getRelatedValue(resource, L0.HasLabel);
76                         this.expression = getExpression(graph, resource);
77                 } catch (DatabaseException e) {
78                         LOG.error("Error reading element selector", e);
79                         throw e;
80                 }
81         }
82         
83         /**
84          * Instantiate an element selector object for an ES.Selection resource.
85          */
86         public static ElementSelector getSelector(RequestProcessor graph, Resource resource) throws DatabaseException {
87                 return graph.syncRequest(new ElementSelectorQuery(resource));
88         }
89         
90         /**
91          * Get an SQL-like textual description of an ES.Selection resource.
92          */
93         public static String getExpression(RequestProcessor graph, Resource resource) throws DatabaseException {
94                 return graph.syncRequest(new SelectionExpressionRequest(resource));
95         }
96
97         /**
98          * Get a Java object representation of an ES.Condition resource.
99          */
100         public static Condition getCondition(RequestProcessor graph, Resource condition) throws DatabaseException {
101                 return graph.syncRequest(new SelectionConditionRequest(condition));
102         }
103
104         /**
105          * Get the name of the element selector.
106          */
107         public String getName() {
108                 return name;
109         }
110
111         /**
112          * Get a textual SQL-like description of the element selector.
113          */
114         public String getExpression() {
115                 return expression;
116         }
117
118         /**
119          * Get the resource that this selector represents. 
120          */
121         public Resource getResource() {
122                 return resource;
123         }
124
125         /**
126          * Get the generator component. Use {@link #buildSelection(ReadGraph)} to make it available first.
127          */
128         public Generator getGenerator() {
129                 return generator;
130         }
131
132         /**
133          * Get the selector component. Use {@link #buildSelection(ReadGraph)} to make it available first.
134          */
135         public Selector getSelector() {
136                 return selector;
137         }
138
139         /**
140          * Get the condition component. Use {@link #buildSelection(ReadGraph)} to make it available first.
141          */
142         public Condition getCondition() {
143                 return condition;
144         }
145
146         /**
147          * 
148          * @param graph
149          * @throws DatabaseException
150          */
151         public void buildSelection(ReadGraph graph) throws DatabaseException {
152                 Resource selector = graph.getSingleObject(resource, ES.Selection_HasSelector);
153                 Resource generator = graph.getSingleObject(resource, ES.Selection_HasGenerator);
154                 Resource condition = graph.getPossibleObject(resource, ES.Selection_HasCondition);
155                 
156                 this.selector = buildSelector(graph, selector);
157                 this.generator = buildGenerator(graph, generator);
158                 this.condition = buildCondition(graph, condition);
159         }
160
161         public static Map<Resource, String> findDiagrams() {
162                 try {
163                         return Simantics.getSession().syncRequest(new Read<Map<Resource, String>>() {
164                                 @Override
165                                 public Map<Resource, String> perform(ReadGraph graph) throws DatabaseException {
166                                         Map<Resource, String> result = new HashMap<>();
167                                         Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
168                                         List<Resource> composites = QueryIndexUtils.searchByType(graph, model, StructuralResource2.getInstance(graph).Composite);
169                                         for (Resource r : composites) {
170                                                 // Get diagram
171                                                 Resource diagram = graph.getPossibleObject(r, ModelingResources.getInstance(graph).CompositeToDiagram);
172                                                 if (diagram == null) continue;
173                                                 
174                                                 // Filter out user component diagrams
175                                                 Resource parent = graph.getPossibleObject(r, Layer0.getInstance(graph).PartOf);
176                                                 if (parent == null || graph.isInheritedFrom(parent, StructuralResource2.getInstance(graph).Component))
177                                                         continue;
178                                                 
179                                                 result.put(r, graph.getRelatedValue(r, Layer0.getInstance(graph).HasName));
180                                         }
181                                         
182                                         return result;
183                                 }
184                         });
185                 } catch (DatabaseException e) {
186                         LOGGER.error("Query for model diagrams failed", e);
187                         return Collections.emptyMap();
188                 }
189         }
190
191         public static Variable getVariableForElement(ReadGraph graph, Resource element) throws DatabaseException {
192                 Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
193                 if (component != null) {
194                         Variable var = Variables.getVariable(graph, component);
195                         RVI realRvi = var.getRVI(graph);
196                         
197                         for (Resource activeModel : graph.syncRequest(new ActiveModels(Simantics.getProjectResource()))) {
198                                 for (Variable run : graph.syncRequest(new ActiveRuns(activeModel))) {
199                                         Variable v = realRvi.resolvePossible(graph, run);
200                                         if (v != null) {
201                                                 return v;
202                                         }
203                                 }
204                                 Variable configuration = Variables.getPossibleConfigurationContext(graph, activeModel);
205                                 if (configuration != null) {
206                                         Variable v = realRvi.resolvePossible(graph, configuration);
207                                         if (v != null) {
208                                                 return v;
209                                         }
210                                 }
211                         }
212                 }
213                 
214                 return null;
215         }
216
217         public static Double getPropertyValue(ReadGraph graph, Resource element, String propertyName) {
218                 try {
219                         Variable v = getVariableForElement(graph, element);
220                         if (v != null) {
221                                 Number value = v.getPossiblePropertyValue(graph, propertyName);
222                                 if (value != null)
223                                         return value.doubleValue();
224                         }
225                         
226                         // No property found - try possible mapped element property as well
227                         Resource mappedElement = graph.getPossibleObject(element, DN.MappedComponent);
228                         if (mappedElement != null)
229                                 return getPropertyValue(graph, mappedElement, propertyName);
230                 }
231                 catch (DatabaseException e) {
232                 }
233                 
234                 return null;
235         }
236
237         public SelectionResult selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
238                 if (selector == null) {
239                         buildSelection(graph);
240                 }
241                 
242                 return gather(graph, model);
243         }
244
245         private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
246                 if (!graph.isInstanceOf(resource, ES.Generator))
247                         throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
248                 
249                 if (graph.isInstanceOf(resource, ES.Generator_Model)) {
250                         return new ModelGenerator();
251                 }
252                 else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) {
253                         return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
254                 }
255                 else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) {
256                         return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
257                 }
258                 else {
259                         throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
260                 }
261         }
262
263         private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
264                 if (!graph.isInstanceOf(resource, ES.Selector))
265                         throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
266                 
267                 Selector s;
268                 if (graph.isInstanceOf(resource, ES.Selector_All)) {
269                         s = new All();
270                 }
271                 else if (graph.isInstanceOf(resource, ES.PropertySelector)) {
272                         String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
273                         Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
274                         boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
275                         s = new PropertySelector(isSmallest, propertyName, resultCount);
276                 }
277                 else {
278                         throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
279                 }
280                 return s;
281         }
282
283         private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException {
284                 if (resource == null)
285                         return null;
286                 
287                 if (!graph.isInstanceOf(resource, ES.Condition))
288                         throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
289                 
290                 Condition cond;
291                 if (graph.isInstanceOf(resource, ES.PropertyCondition)) {
292                         String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
293                         Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
294                         Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
295                         cond = new PropertyCondition(resource, propertyName, lowerLimit, upperLimit);
296                 }
297                 else if (graph.isInstanceOf(resource, ES.RegionCondition)) {
298                         DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
299                         Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion);
300                         double[] region = graph.getRelatedValue(regionResource, DR.Region_area);
301                         cond = new RegionCondition(resource, regionResource, region);
302                 }
303                 else if (graph.isInstanceOf(resource, ES.RouteCondition)) {
304                         Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute);
305                         Set<Resource> routePoints = new HashSet<>(ListUtils.toList(graph, routeResource));
306                         cond = new RouteCondition(resource, routeResource, routePoints);
307                 }
308                 else if (graph.isInstanceOf(resource, ES.AggregateCondition)) {
309                         Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
310                         List<Condition> conditions = new ArrayList<>(conditionResources.size());
311                         for (Resource c : conditionResources) {
312                                 conditions.add(buildCondition(graph, c));
313                         }
314                         Type type;
315                         if (graph.isInstanceOf(resource, ES.Conjunction))
316                                 type = AggregateCondition.Type.CONJUNCTION;
317                         else if (graph.isInstanceOf(resource, ES.Negation))
318                                 type = AggregateCondition.Type.NEGATION;
319                         else if (graph.isInstanceOf(resource, ES.Disjunction))
320                                 type = AggregateCondition.Type.DISJUNCTION;
321                         else
322                                 throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
323                                 
324                         cond = new AggregateCondition(resource, type, conditions);
325                 }
326                 else {
327                         throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
328                 }
329                 
330                 return cond;
331         }
332
333         private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
334                 if (graph.isInstanceOf(r, ES.Selection)) {
335                         String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + 
336                                      " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
337                         
338                         Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
339                         return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
340                 }
341                 else if (graph.isInstanceOf(r, ES.Condition)) {
342                         if (graph.isInstanceOf(r, ES.PropertyCondition)) {
343                                 return buildPropertyConditionExpression(graph, r);
344                         }
345                         else if (graph.isInstanceOf(r, ES.RegionCondition)) {
346                                 Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
347                                 String name = graph.getRelatedValue(region, L0.HasLabel);
348                                 return "in region " + name;
349                         }
350                         else if (graph.isInstanceOf(r, ES.RouteCondition)) {
351                                 Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
352                                 String name = graph.getRelatedValue(route, L0.HasLabel);
353                                 return "in route " + name;
354                         }
355                         else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
356                                 String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
357                                 List<String> exps = new ArrayList<>();
358                                 Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
359                                 for (Resource c : objects) {
360                                         String exp = getExpression(graph, c);
361                                         exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
362                                 }
363                                 String result = String.join(op, exps);
364                                 if (graph.isInstanceOf(r, ES.Negation))
365                                         result = "not {" + result + "}";
366                                 return result;
367                         }
368                         else {
369                                 throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
370                         }
371                 }
372                 else if (graph.isInstanceOf(r, ES.Selector)) {
373                         if (graph.isInstanceOf(r, ES.Selector_All)) {
374                                 return "all";
375                         }
376                         else if (graph.isInstanceOf(r, ES.PropertySelector)) {
377                                 String op;
378                                 if (graph.isInstanceOf(r, ES.Selector_NLowest))
379                                         op = "bottom";
380                                 else if (graph.isInstanceOf(r, ES.Selector_NHighest))
381                                         op = "top";
382                                 else
383                                         throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
384                                 
385                                 String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
386                                 Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
387                                 return op + " " + count + " of " + name;
388                         }
389                         else {
390                                 throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
391                         }
392                 }
393                 else if (graph.isInstanceOf(r, ES.Generator)) {
394                         if (graph.isInstanceOf(r, ES.Generator_Model)) {
395                                 return "model";
396                         }
397                         else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
398                                 return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
399                         }
400                         else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
401                                 return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
402                         }
403                         else {
404                                 throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
405                         }
406                 }
407                 else {
408                         throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
409                 }
410         }
411
412         private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
413                 String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
414                 Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
415                 Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
416                 if (lowerLimit == null && upperLimit == null) {
417                         return "has property " + propertyName;
418                 }
419                 else {
420                         StringBuilder result = new StringBuilder();
421                         if (lowerLimit != null) {
422                                 result.append(lowerLimit);
423                                 result.append(" < ");
424                         }
425                         result.append(propertyName);
426                         if (upperLimit != null) {
427                                 result.append(" < ");
428                                 result.append(upperLimit);
429                         }
430                         return result.toString();
431                 }
432         }
433
434         private static Collection<Resource> elementsOfDiagram(ReadGraph graph, Resource diagram) throws DatabaseException {
435                 if (graph.isInstanceOf(diagram, STR.Composite)) {
436                         // Resource is a composite - get diagram
437                         return elementsOfDiagram(graph, graph.getSingleObject(diagram, MOD.CompositeToDiagram));
438                 }
439                 
440                 Collection<Resource> elements = graph.getObjects(diagram, L0.ConsistsOf);
441                 Collection<Resource> result = new ArrayList<>();
442                 for (Resource r : elements)
443                         if (graph.isInstanceOf(r, DIA.Element))
444                                 result .add(r);
445                 return result;
446         }
447
448         private Collection<Resource> filterElementsFrom(ReadGraph graph, Collection<Resource> elements) throws DatabaseException {
449                 if (condition == null) return elements;
450                 
451                 ArrayList<Resource> result = new ArrayList<>();
452                 for (Resource r : elements) {
453                         if (condition.match(graph, r))
454                                 result.add(r);
455                 }
456                 
457                 return result;
458         }
459         
460         private SelectionResult gather(ReadGraph graph, Resource model) throws DatabaseException {
461                 return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
462         }
463         
464         // Generators
465         
466         public static abstract class Generator {
467                 abstract Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException;
468         }
469         
470         public static class ModelGenerator extends Generator {
471                 @Override
472                 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
473                         Resource conf = graph.syncRequest(new Configuration(model));
474                         
475                         HashMap<Resource, Resource> fromMapped = new HashMap<Resource, Resource>();
476                         
477                         // Iterate over diagrams
478                         // Each model element is represented by the corresponding mapping element, if present
479                         for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) {
480                                 if (!graph.isInstanceOf(comp, STR.Composite)) continue;
481                                 
482                                 Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram);
483                                 if (diagram != null) {
484                                         for (Resource elem : elementsOfDiagram(graph, diagram)) {
485                                                 Resource mapped = graph.getPossibleObject(elem, DN.MappedComponent);
486                                                 if (mapped != null) {
487                                                         fromMapped.put(mapped, elem);
488                                                 }
489                                                 else if (!fromMapped.containsKey(elem)) {
490                                                         fromMapped.put(elem, elem);
491                                                 }
492                                         }
493                                 }
494                         }
495                         
496                         return new ArrayList<>(fromMapped.values());
497                 }
498         }
499         
500         public static class DiagramGenerator extends Generator {
501                 public Resource diagram;
502                 
503                 public DiagramGenerator(Resource diagram) {
504                         this.diagram = diagram;
505                 }
506
507                 @Override
508                 Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
509                         return elementsOfDiagram(graph, diagram);
510                 }
511         }
512         
513         public static class ExplicitGenerator extends Generator {
514                 public ExplicitGenerator(Collection<Resource> elements) {
515                         this.elements = elements;
516                 }
517
518                 public Collection<Resource> elements;
519
520                 @Override
521                 Collection<Resource> generate(ReadGraph graph, Resource model) {
522                         return elements;
523                 }
524         }
525         
526         // Selectors
527         
528         public static class SelectionResult {
529                 public final Collection<Resource> elements;
530                 public final int tailCount;
531                 public final int tailSize;
532                 
533                 public SelectionResult(Collection<Resource> elements, int tailCount, int tailSize) {
534                         this.elements = elements;
535                         this.tailCount = tailCount;
536                         this.tailSize = tailSize;
537                 }
538         }
539
540         public static abstract class Selector {
541                 abstract SelectionResult select(ReadGraph graph, Collection<Resource> elements);
542         }
543         
544         public static class All extends Selector {
545                 public All() {
546                 }
547
548                 @Override
549                 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
550                         return new SelectionResult(elements, 0, 0);
551                 }
552         }
553         
554         public static class PropertySelector extends Selector {
555                 public PropertySelector(boolean smallest, String propertyName, int resultCount) {
556                         this.smallest = smallest;
557                         this.propertyName = propertyName;
558                         this.resultCount = resultCount;
559                 }
560                 
561                 public boolean smallest;
562                 public String propertyName;
563                 public int resultCount;
564                 
565                 @Override
566                 SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
567                         // Select sorting direction
568                         Comparator<Pair<Resource, Double>> comparator = smallest ?
569                                         (p1, p2) -> Double.compare(p1.second, p2.second) :
570                                         (p1, p2) -> Double.compare(p1.second, p2.second);
571                         
572                         // Get association list to property values
573                         List<Pair<Resource, Double>> result2 = elements.stream()
574                                         .map(r -> Pair.make(r, getPropertyValue(graph, r, propertyName)))
575                                         .filter(t -> t.second != null)
576                                         .sorted(comparator)
577                                         .collect(Collectors.toList());
578                         int count = Math.min(resultCount, result2.size());
579                         
580                         // Count number of equal values at the end of the list
581                         int tailCount = 0;
582                         double tailValue = count > 0 ? result2.get(count-1).second : 0.0;
583                         for (int i = count-1; i >= 0; i--) {
584                                 if (result2.get(i).second == tailValue)
585                                         tailCount++;
586                                 else
587                                         break;
588                         }
589                         
590                         // Count number of elements with value equal to the end of the list
591                         int tailSize = tailCount;
592                         for (int i = count; i < result2.size(); i++) {
593                                 if (result2.get(i).second == tailValue)
594                                         tailSize++;
595                                 else
596                                         break;
597                         }
598                         
599                         // Take first n items
600                         if (count < result2.size()) {
601                                 result2 = result2.subList(0, resultCount);
602                         }
603                         
604                         // Map to list or resources
605                         List<Resource> selection = result2.stream().map(p -> p.first).collect(Collectors.toList());
606                         return new SelectionResult(selection, tailCount, tailSize);
607                 }
608         }
609         
610         // Conditions
611         
612         public static abstract class Condition {
613                 public Resource resource;
614                 
615                 Condition(Resource r) {
616                         resource = r;
617                 }
618                 
619                 abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
620         }
621         
622         public static class PropertyCondition extends Condition {
623                 public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) {
624                         super(r);
625                         
626                         this.propertyName = propertyName;
627                         this.lowerLimit = lowerLimit;
628                         this.upperLimit = upperLimit;
629                 }
630                 
631                 public String propertyName;
632                 public Double lowerLimit;
633                 public Double upperLimit;
634                 
635                 @Override
636                 boolean match(ReadGraph graph, Resource r) {
637                         Double value = getPropertyValue(graph, r, propertyName);
638                         return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
639                 }
640         }
641         
642         public static class RegionCondition extends Condition {
643                 public RegionCondition(Resource r, Resource regionResoruce, double[] region) {
644                         super(r);
645                         this.region = region;
646                         
647                         Path2D path = new Path2D.Double();
648                         double startX = region[0];
649                         double startY = region[1];
650                         path.moveTo(startX, startY);
651                         for (int i = 2; i < region.length; i+=2)
652                                 path.lineTo(region[i], region[i+1]);
653                         path.closePath();
654
655                         this.path = path;
656                         this.regionResource = regionResoruce;
657                 }
658
659                 public Resource regionResource;
660                 double[] region;
661                 Path2D path;
662
663                 @Override
664                 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
665                         double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
666                         double x = transform[4];
667                         double y = transform[5];
668                         return path.contains(x, y);
669                 } 
670         }
671         
672         public static class RouteCondition extends Condition {
673                 public RouteCondition(Resource r, Resource routeResource, Set<Resource> routePoints) {
674                         super(r);
675                         this.routePoints = routePoints;
676                         this.routeResource = routeResource;
677                 }
678
679                 public Resource routeResource;
680                 Set<Resource> routePoints;
681
682                 @Override
683                 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
684                         return routePoints.contains(r);
685                 }
686         }
687         
688         public static class AggregateCondition extends Condition {
689                 public static enum Type { DISJUNCTION, CONJUNCTION, NEGATION };
690                 
691                 public AggregateCondition(Resource r, Type type, List<Condition> conditions) {
692                         super(r);
693                         this.type = type;
694                         this.conditions = conditions;
695                 }
696                 
697                 public Type type;
698                 public List<Condition> conditions;
699                 
700                 @Override
701                 boolean match(ReadGraph graph, Resource r) throws DatabaseException {
702                         switch (type) {
703                         case DISJUNCTION:
704                                 for (Condition c : conditions)
705                                         if (c.match(graph, r)) return true;
706                                 return false;
707                         case CONJUNCTION:
708                                 for (Condition c : conditions)
709                                         if (!c.match(graph, r)) return false;
710                                 return true;
711                         case NEGATION:
712                                 for (Condition c : conditions)
713                                         if (c.match(graph, r)) return false;
714                                 return true;
715                         default:
716                                 // Should not happen
717                                 throw new IllegalArgumentException("Unknown aggregate condition type " + type);
718                         }
719                 }
720         }
721         
722         public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {
723                 public ElementSelectorQuery(Resource resource) {
724                         super(resource);
725                 }
726                 
727                 @Override
728                 public ElementSelector perform(ReadGraph graph) throws DatabaseException {
729                         return new ElementSelector(graph, resource);
730                 }
731         }
732
733         public final static class SelectionExpressionRequest extends ResourceRead<String> {
734                 public SelectionExpressionRequest(Resource condition) {
735                         super(condition);
736                 }
737         
738                 @Override
739                 public String perform(ReadGraph graph) throws DatabaseException {
740                         return ElementSelector.buildExpression(graph, resource);
741                 }
742         }
743
744         public static final class SelectionConditionRequest extends ResourceRead<Condition> {
745                 public SelectionConditionRequest(Resource resource) {
746                         super(resource);
747                 }
748         
749                 @Override
750                 public Condition perform(ReadGraph graph) throws DatabaseException {
751                         return buildCondition(graph, resource);
752                 }
753         }
754 }