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