]> gerrit.simantics Code Review - simantics/district.git/blobdiff - org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java
Fix use of static ontology variables out of context
[simantics/district.git] / org.simantics.district.selection / src / org / simantics / district / selection / ElementSelector.java
index bf051b4c9828e3f9d4d0b08a3e8409a3d4d3c091..9015b7117605674dd56bcaa9385e3116ccaedf43 100644 (file)
@@ -4,19 +4,29 @@ import java.awt.geom.Path2D;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
 import org.simantics.db.ReadGraph;
 import org.simantics.db.RequestProcessor;
 import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
 import org.simantics.db.common.request.ResourceRead;
 import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.exception.AssumptionException;
 import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.DoesNotContainValueException;
+import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
+import org.simantics.db.exception.NoSingleResultException;
+import org.simantics.db.exception.ServiceException;
+import org.simantics.db.exception.ValidationException;
 import org.simantics.db.layer0.QueryIndexUtils;
 import org.simantics.db.layer0.request.ActiveModels;
 import org.simantics.db.layer0.request.ActiveRuns;
@@ -29,13 +39,10 @@ import org.simantics.db.request.Read;
 import org.simantics.diagram.stubs.DiagramResource;
 import org.simantics.district.network.ontology.DistrictNetworkResource;
 import org.simantics.district.region.ontology.DiagramRegionsResource;
-import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
 import org.simantics.layer0.Layer0;
 import org.simantics.modeling.ModelingResources;
-import org.simantics.scl.runtime.Lists;
-import org.simantics.scl.runtime.function.FunctionImpl1;
-import org.simantics.scl.runtime.tuple.Tuple2;
 import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.utils.datastructures.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -163,20 +170,26 @@ public class ElementSelector {
                        return Simantics.getSession().syncRequest(new Read<Map<Resource, String>>() {
                                @Override
                                public Map<Resource, String> perform(ReadGraph graph) throws DatabaseException {
+                                       Layer0 L0 = Layer0.getInstance(graph);
+                                       StructuralResource2 STR = StructuralResource2.getInstance(graph);
+                                       DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
+                                       ModelingResources MOD = ModelingResources.getInstance(graph);
+                                       
                                        Map<Resource, String> result = new HashMap<>();
                                        Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
-                                       List<Resource> composites = QueryIndexUtils.searchByType(graph, model, StructuralResource2.getInstance(graph).Composite);
+                                       List<Resource> composites = QueryIndexUtils.searchByType(graph, model, STR.Composite);
                                        for (Resource r : composites) {
                                                // Get diagram
-                                               Resource diagram = graph.getPossibleObject(r, ModelingResources.getInstance(graph).CompositeToDiagram);
-                                               if (diagram == null) continue;
+                                               Resource diagram = graph.getPossibleObject(r, MOD.CompositeToDiagram);
+                                               if (diagram == null || !graph.isInstanceOf(diagram, DN.Diagram))
+                                                       continue;
                                                
                                                // Filter out user component diagrams
-                                               Resource parent = graph.getPossibleObject(r, Layer0.getInstance(graph).PartOf);
-                                               if (parent == null || graph.isInheritedFrom(parent, StructuralResource2.getInstance(graph).Component))
+                                               Resource parent = graph.getPossibleObject(r, L0.PartOf);
+                                               if (parent == null || graph.isInheritedFrom(parent, STR.Component))
                                                        continue;
                                                
-                                               result.put(r, graph.getRelatedValue(r, Layer0.getInstance(graph).HasName));
+                                               result.put(r, graph.getRelatedValue(r, L0.HasName));
                                        }
                                        
                                        return result;
@@ -234,31 +247,26 @@ public class ElementSelector {
                return null;
        }
 
-       public List<Resource> selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
+       public SelectionResult selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException {
                if (selector == null) {
                        buildSelection(graph);
                }
                
-               Collection<Resource> result = gather(graph, model);
-               return result instanceof List ? (List<Resource>) result : new ArrayList<>(result);
+               return gather(graph, model);
        }
 
        private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException {
                if (!graph.isInstanceOf(resource, ES.Generator))
                        throw new IllegalArgumentException("Resource " + resource + " is not a valid generator");
                
-               if (graph.isInstanceOf(resource, ES.Generator_Model)) {
+               if (graph.isInstanceOf(resource, ES.Generator_Model))
                        return new ModelGenerator();
-               }
-               else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) {
+               else if (graph.isInstanceOf(resource, ES.Generator_Diagram))
                        return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram));
-               }
-               else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) {
+               else if (graph.isInstanceOf(resource, ES.Generator_Explicit))
                        return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement));
-               }
-               else {
+               else
                        throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource)));
-               }
        }
 
        private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException {
@@ -266,18 +274,16 @@ public class ElementSelector {
                        throw new IllegalArgumentException("Resource " + resource + " is not a valid selector");
                
                Selector s;
-               if (graph.isInstanceOf(resource, ES.Selector_All)) {
+               if (graph.isInstanceOf(resource, ES.Selector_All))
                        s = new All();
-               }
-               else if (graph.isInstanceOf(resource, ES.PropertySelector)) {
-                       String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
-                       Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
-                       boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
-                       s = new PropertySelector(isSmallest, propertyName, resultCount);
-               }
-               else {
+               else if (graph.isInstanceOf(resource, ES.PropertySelector))
+                       s = new PropertySelector(graph, resource);
+               else
                        throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource)));
-               }
+               
+               Resource mapping = graph.getPossibleObject(resource, ES.Selector_HasMapping);
+               s.componentType = mapping;
+               
                return s;
        }
 
@@ -289,145 +295,179 @@ public class ElementSelector {
                        throw new IllegalArgumentException("Resource " + resource + " is not a valid condition");
                
                Condition cond;
-               if (graph.isInstanceOf(resource, ES.PropertyCondition)) {
-                       String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
-                       Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
-                       Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
-                       cond = new PropertyCondition(resource, propertyName, lowerLimit, upperLimit);
+               if (graph.isInstanceOf(resource, ES.PropertyCondition))
+                       cond = new PropertyCondition(graph, resource);
+               else if (graph.isInstanceOf(resource, ES.RegionCondition))
+                       cond = new RegionCondition(graph, resource);
+               else if (graph.isInstanceOf(resource, ES.RouteCondition))
+                       cond = new RouteCondition(graph, resource);
+               else if (graph.isInstanceOf(resource, ES.AggregateCondition))
+                       cond = new AggregateCondition(graph, resource);
+               else
+                       throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
+               
+               return cond;
+       }
+
+       private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
+               if (graph.isInstanceOf(r, ES.Selection))
+                       return buildSelectionExpression(graph, r);
+               else if (graph.isInstanceOf(r, ES.Condition))
+                       return buildConditionExpression(graph, r);
+               else if (graph.isInstanceOf(r, ES.Selector))
+                       return buildSelectorExpression(graph, r);
+               else if (graph.isInstanceOf(r, ES.Generator))
+                       return buildGeneratorExpression(graph, r);
+               else
+                       throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+       }
+
+       private static String buildSelectionExpression(ReadGraph graph, Resource r) throws DatabaseException,
+                       NoSingleResultException, ManyObjectsForFunctionalRelationException, ServiceException {
+               String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + 
+                            " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
+               
+               Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
+               return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
+       }
+
+       private static String buildGeneratorExpression(ReadGraph graph, Resource r)
+                       throws ServiceException, NoSingleResultException, DoesNotContainValueException,
+                       ManyObjectsForFunctionalRelationException, DatabaseException, AssumptionException, ValidationException {
+               if (graph.isInstanceOf(r, ES.Generator_Model)) {
+                       return "model";
                }
-               else if (graph.isInstanceOf(resource, ES.RegionCondition)) {
-                       DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
-                       Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion);
-                       double[] region = graph.getRelatedValue(regionResource, DR.Region_area);
-                       cond = new RegionCondition(resource, regionResource, region);
+               else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
+                       return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
                }
-               else if (graph.isInstanceOf(resource, ES.RouteCondition)) {
-                       Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute);
-                       Set<Resource> routePoints = new HashSet<>(ListUtils.toList(graph, routeResource));
-                       cond = new RouteCondition(resource, routeResource, routePoints);
+               else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
+                       return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
                }
-               else if (graph.isInstanceOf(resource, ES.AggregateCondition)) {
-                       Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
-                       List<Condition> conditions = new ArrayList<>(conditionResources.size());
-                       for (Resource c : conditionResources) {
-                               conditions.add(buildCondition(graph, c));
-                       }
-                       Type type;
-                       if (graph.isInstanceOf(resource, ES.Conjunction))
-                               type = AggregateCondition.Type.CONJUNCTION;
-                       else if (graph.isInstanceOf(resource, ES.Negation))
-                               type = AggregateCondition.Type.NEGATION;
-                       else if (graph.isInstanceOf(resource, ES.Disjunction))
-                               type = AggregateCondition.Type.DISJUNCTION;
-                       else
-                               throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
-                               
-                       cond = new AggregateCondition(resource, type, conditions);
+               else {
+                       throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+               }
+       }
+
+       private static String buildSelectorExpression(ReadGraph graph, Resource r)
+                       throws ServiceException, NoSingleResultException, DoesNotContainValueException, DatabaseException,
+                       AssumptionException, ValidationException, ManyObjectsForFunctionalRelationException {
+               String exp;
+               if (graph.isInstanceOf(r, ES.Selector_All)) {
+                       exp = "all";
+               }
+               else if (graph.isInstanceOf(r, ES.PropertySelector)) {
+                       Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
+                       exp = count.toString();
                }
                else {
-                       throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource)));
+                       throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
                }
                
-               return cond;
+               Resource mapping = graph.getPossibleObject(r, ES.Selector_HasMapping);
+               if (mapping != null) {
+                       String name = graph.getRelatedValue2(mapping, L0.HasName);
+                       exp = exp + " " + name;
+               } else {
+                       exp = exp + " elements";
+               }
+               
+               if (graph.isInstanceOf(r, ES.PropertySelector)) {
+                       String op;
+                       if (graph.isInstanceOf(r, ES.Selector_NLowest))
+                               op = "lowest";
+                       else if (graph.isInstanceOf(r, ES.Selector_NHighest))
+                               op = "highest";
+                       else
+                               throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+                       
+                       String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
+                       exp = exp + " with " + op + " " + name;
+               }
+               
+               return exp;
        }
 
-       private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException {
-               if (graph.isInstanceOf(r, ES.Selection)) {
-                       String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + 
-                                    " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator));
-                       
-                       Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition);
-                       return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp;
+       private static String buildConditionExpression(ReadGraph graph, Resource r) throws ServiceException,
+                       DatabaseException, NoSingleResultException, ManyObjectsForFunctionalRelationException,
+                       DoesNotContainValueException, AssumptionException, ValidationException {
+               String result;
+               boolean isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r);
+               if (graph.isInstanceOf(r, ES.PropertyCondition)) {
+                       result = buildPropertyConditionExpression(graph, r);
                }
-               else if (graph.isInstanceOf(r, ES.Condition)) {
-                       if (graph.isInstanceOf(r, ES.PropertyCondition)) {
-                               return buildPropertyConditionExpression(graph, r);
-                       }
-                       else if (graph.isInstanceOf(r, ES.RegionCondition)) {
-                               Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
-                               String name = graph.getRelatedValue(region, L0.HasLabel);
-                               return "in region " + name;
-                       }
-                       else if (graph.isInstanceOf(r, ES.RouteCondition)) {
-                               Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
-                               String name = graph.getRelatedValue(route, L0.HasLabel);
-                               return "in route " + name;
-                       }
-                       else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
-                               String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
-                               List<String> exps = new ArrayList<>();
-                               Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
-                               for (Resource c : objects) {
-                                       String exp = getExpression(graph, c);
-                                       exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
-                               }
-                               String result = String.join(op, exps);
-                               if (graph.isInstanceOf(r, ES.Negation))
-                                       result = "not {" + result + "}";
-                               return result;
-                       }
-                       else {
-                               throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
-                       }
+               else if (graph.isInstanceOf(r, ES.RegionCondition)) {
+                       result = buildRegionConditionExpression(graph, r);
                }
-               else if (graph.isInstanceOf(r, ES.Selector)) {
-                       if (graph.isInstanceOf(r, ES.Selector_All)) {
-                               return "all";
-                       }
-                       else if (graph.isInstanceOf(r, ES.PropertySelector)) {
-                               String op;
-                               if (graph.isInstanceOf(r, ES.Selector_NLowest))
-                                       op = "bottom";
-                               else if (graph.isInstanceOf(r, ES.Selector_NHighest))
-                                       op = "top";
-                               else
-                                       throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
-                               
-                               String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName);
-                               Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount);
-                               return op + " " + count + " of " + name;
-                       }
-                       else {
-                               throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
-                       }
+               else if (graph.isInstanceOf(r, ES.RouteCondition)) {
+                       result = buildRouteConditionExpression(graph, r);
                }
-               else if (graph.isInstanceOf(r, ES.Generator)) {
-                       if (graph.isInstanceOf(r, ES.Generator_Model)) {
-                               return "model";
-                       }
-                       else if (graph.isInstanceOf(r, ES.Generator_Diagram)) {
-                               return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\"";
-                       }
-                       else if (graph.isInstanceOf(r, ES.Generator_Explicit)) {
-                               return "<list of " + graph.getObjects(r, ES.Generator_HasSelectedElement).size() + " elements>";
-                       }
-                       else {
-                               throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
-                       }
+               else if (graph.isInstanceOf(r, ES.AggregateCondition)) {
+                       // This handles isInverse internally
+                       return buildAggregateConditionExpression(graph, r, isInverse);
                }
                else {
-                       throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+                       throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">");
+               }
+               
+               if (isInverse)
+                       result = "not {" + result + "}";
+               
+               return result;
+       }
+
+       private static String buildAggregateConditionExpression(ReadGraph graph, Resource r, boolean isInverse)
+                       throws ServiceException, DatabaseException {
+               String result;
+               String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or ";
+               List<String> exps = new ArrayList<>();
+               Collection<Resource> objects = graph.getObjects(r, ES.HasSubcondition);
+               for (Resource c : objects) {
+                       String exp = getExpression(graph, c);
+                       exps.add(objects.size() > 1 ? "{" + exp + "}" : exp);
                }
+               result = String.join(op, exps);
+               if (graph.isInstanceOf(r, ES.Negation) ^ isInverse)
+                       result = "not {" + result + "}";
+               return result;
+       }
+
+       private static String buildRouteConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException,
+                       ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException {
+               String result;
+               Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute);
+               String name = graph.getRelatedValue(route, L0.HasLabel);
+               result = "in route " + name;
+               return result;
+       }
+
+       private static String buildRegionConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException,
+                       ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException {
+               String result;
+               Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion);
+               String name = graph.getRelatedValue(region, L0.HasLabel);
+               result = "in region " + name;
+               return result;
        }
 
        private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException {
                String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName);
                Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit);
                Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit);
-               if (lowerLimit == null && upperLimit == null) {
-                       return "has property " + propertyName;
-               }
-               else {
+               if (upperLimit == null) {
+                       if (lowerLimit == null) {
+                               return "has property " + propertyName;
+                       } else {
+                               return propertyName + " \u2265 " + lowerLimit;
+                       }
+               } else {
                        StringBuilder result = new StringBuilder();
                        if (lowerLimit != null) {
                                result.append(lowerLimit);
-                               result.append(" < ");
+                               result.append(" \u2264 ");
                        }
                        result.append(propertyName);
-                       if (upperLimit != null) {
-                               result.append(" < ");
-                               result.append(upperLimit);
-                       }
+                       result.append(" \u2264 ");
+                       result.append(upperLimit);
                        return result.toString();
                }
        }
@@ -458,7 +498,7 @@ public class ElementSelector {
                return result;
        }
        
-       private Collection<Resource> gather(ReadGraph graph, Resource model) throws DatabaseException {
+       private SelectionResult gather(ReadGraph graph, Resource model) throws DatabaseException {
                return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model)));
        }
        
@@ -473,17 +513,28 @@ public class ElementSelector {
                Collection<Resource> generate(ReadGraph graph, Resource model) throws DatabaseException {
                        Resource conf = graph.syncRequest(new Configuration(model));
                        
-                       ArrayList<Resource> result = new ArrayList<>();
+                       HashMap<Resource, Resource> fromMapped = new HashMap<Resource, Resource>();
                        
                        // Iterate over diagrams
+                       // Each model element is represented by the corresponding mapping element, if present
                        for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) {
                                if (!graph.isInstanceOf(comp, STR.Composite)) continue;
                                
                                Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram);
-                               if (diagram != null) result.addAll(elementsOfDiagram(graph, diagram));
+                               if (diagram != null) {
+                                       for (Resource elem : elementsOfDiagram(graph, diagram)) {
+                                               Resource mapped = graph.getPossibleObject(elem, DN.MappedComponent);
+                                               if (mapped != null) {
+                                                       fromMapped.put(mapped, elem);
+                                               }
+                                               else if (!fromMapped.containsKey(elem)) {
+                                                       fromMapped.put(elem, elem);
+                                               }
+                                       }
+                               }
                        }
                        
-                       return result;
+                       return new ArrayList<>(fromMapped.values());
                }
        }
        
@@ -515,8 +566,39 @@ public class ElementSelector {
        
        // Selectors
        
+       public static class SelectionResult {
+               public final Collection<Resource> elements;
+               public final int tailCount;
+               public final int tailSize;
+               
+               public SelectionResult(Collection<Resource> elements, int tailCount, int tailSize) {
+                       this.elements = elements;
+                       this.tailCount = tailCount;
+                       this.tailSize = tailSize;
+               }
+       }
+
        public static abstract class Selector {
-               abstract Collection<Resource> select(ReadGraph graph, Collection<Resource> elements);
+               public Resource componentType = null;
+               
+               abstract SelectionResult select(ReadGraph graph, Collection<Resource> elements);
+               
+               Collection<Resource> filterElements(ReadGraph graph, Collection<Resource> elements) {
+                       if (componentType == null)
+                               return elements;
+                       
+                       Collection<Resource> selected = new HashSet<>(elements.size());
+                       for (Resource r : elements) {
+                               try {
+                                       if (graph.hasStatement(r, DN.HasMapping, componentType))
+                                               selected.add(r);
+                               } catch (DatabaseException e) {
+                                       // Just leave it out of the result
+                               }
+                       }
+                       
+                       return selected;
+               }
        }
        
        public static class All extends Selector {
@@ -524,8 +606,9 @@ public class ElementSelector {
                }
 
                @Override
-               Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
-                       return elements;
+               SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
+                       Collection<Resource> selected = filterElements(graph, elements);
+                       return new SelectionResult(selected, 0, 0);
                }
        }
        
@@ -536,44 +619,60 @@ public class ElementSelector {
                        this.resultCount = resultCount;
                }
                
+               public PropertySelector(ReadGraph graph, Resource resource) throws DatabaseException {
+                       this.propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName);
+                       this.resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount);
+                       this.smallest = graph.isInstanceOf(resource, ES.Selector_NLowest);
+               }
+
                public boolean smallest;
                public String propertyName;
                public int resultCount;
                
-               @SuppressWarnings("unchecked")
                @Override
-               Collection<Resource> select(ReadGraph graph, Collection<Resource> elements) {
-                       List<Tuple2> result2 = Lists.map(new FunctionImpl1<Resource, Tuple2>() {
-                               @Override
-                               public Tuple2 apply(Resource r) {
-                                       return new Tuple2(r, getPropertyValue(graph, r, propertyName));
-                               }
-                       }, new ArrayList<>(elements));
+               SelectionResult select(ReadGraph graph, Collection<Resource> elements) {
+                       elements = filterElements(graph, elements);
                        
-                       result2 = Lists.filter(new FunctionImpl1<Tuple2, Boolean>() {
-                               @Override
-                               public Boolean apply(Tuple2 t) {
-                                       return t.c1 != null;
-                               }
-                       }, result2);
+                       // Select sorting direction
+                       Comparator<Pair<Resource, Double>> comparator = smallest ?
+                                       (p1, p2) -> Double.compare(p1.second, p2.second) :
+                                       (p1, p2) -> Double.compare(p1.second, p2.second);
                        
-                       result2.sort((t1, t2) -> smallest ? Double.compare((Double) t1.c1, (Double) t2.c1) : Double.compare((Double) t2.c1, (Double) t1.c1));
+                       // Get association list to property values
+                       List<Pair<Resource, Double>> result2 = elements.stream()
+                                       .map(r -> Pair.make(r, getPropertyValue(graph, r, propertyName)))
+                                       .filter(t -> t.second != null)
+                                       .sorted(comparator)
+                                       .collect(Collectors.toList());
+                       int count = Math.min(resultCount, result2.size());
                        
-                       if (resultCount < result2.size()) {
-                               double limitValue = (double) result2.get(resultCount-1).c1;
-                               
-                               // Expand selection to contain all items with the same value as the nth one
-                               int count = resultCount;
-                               while (count < result2.size() && (double)result2.get(count).c1 == limitValue) count++;
-                               result2 = result2.subList(0, count);
+                       // Count number of equal values at the end of the list
+                       int tailCount = 0;
+                       double tailValue = count > 0 ? result2.get(count-1).second : 0.0;
+                       for (int i = count-1; i >= 0; i--) {
+                               if (result2.get(i).second == tailValue)
+                                       tailCount++;
+                               else
+                                       break;
                        }
                        
-                       return (List<Resource>) Lists.map(new FunctionImpl1<Tuple2, Resource>() {
-                               @Override
-                               public Resource apply(Tuple2 p0) {
-                                       return (Resource) p0.c0;
-                               }
-                       }, result2);
+                       // Count number of elements with value equal to the end of the list
+                       int tailSize = tailCount;
+                       for (int i = count; i < result2.size(); i++) {
+                               if (result2.get(i).second == tailValue)
+                                       tailSize++;
+                               else
+                                       break;
+                       }
+                       
+                       // Take first n items
+                       if (count < result2.size()) {
+                               result2 = result2.subList(0, resultCount);
+                       }
+                       
+                       // Map to list or resources
+                       List<Resource> selection = result2.stream().map(p -> p.first).collect(Collectors.toList());
+                       return new SelectionResult(selection, tailCount, tailSize);
                }
        }
        
@@ -581,15 +680,42 @@ public class ElementSelector {
        
        public static abstract class Condition {
                public Resource resource;
+               public boolean isInverse;
                
                Condition(Resource r) {
                        resource = r;
+                       isInverse = false;
                }
                
-               abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
+               Condition(ReadGraph graph, Resource r) throws DatabaseException {
+                       this(r);
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r);
+               }
+               
+               public abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException;
+               public Resource update(WriteGraph graph) throws DatabaseException {
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       
+                       assert(resource != null);
+                       if (isInverse)
+                               graph.claim(resource, ES.Condition_IsInverse, resource);
+                       else
+                               graph.deny(resource, ES.Condition_IsInverse, resource);
+                       return resource;
+               }
        }
        
        public static class PropertyCondition extends Condition {
+               public PropertyCondition(ReadGraph graph, Resource r) throws DatabaseException {
+                       super(graph, r);
+                       
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       this.propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName);
+                       this.lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit);
+                       this.upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit);
+               }
+               
                public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) {
                        super(r);
                        
@@ -603,27 +729,68 @@ public class ElementSelector {
                public Double upperLimit;
                
                @Override
-               boolean match(ReadGraph graph, Resource r) {
+               public boolean match(ReadGraph graph, Resource r) {
                        Double value = getPropertyValue(graph, r, propertyName);
-                       return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
+                       boolean result = value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit);
+                       return result ^ isInverse;
+               }
+               
+               @Override
+               public Resource update(WriteGraph graph) throws DatabaseException {
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       Layer0 L0 = Layer0.getInstance(graph);
+                       
+                       if (resource == null) {
+                               resource = graph.newResource();
+                               graph.claim(resource, L0.InstanceOf, ES.PropertyCondition);
+                       }
+                       
+                       super.update(graph);
+                       
+                       graph.claimLiteral(resource, ES.PropertyCondition_HasPropertyName, propertyName);
+                       if (lowerLimit != null)
+                               graph.claimLiteral(resource, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit);
+                       else
+                               graph.deny(resource, ES.PropertyCondition_HasLowerLimit);
+                       
+                       if (upperLimit != null)
+                               graph.claimLiteral(resource, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit);
+                       else
+                               graph.deny(resource, ES.PropertyCondition_HasUpperLimit);
+                       return resource;
                }
        }
        
        public static class RegionCondition extends Condition {
-               public RegionCondition(Resource r, Resource regionResoruce, double[] region) {
+               public RegionCondition(Resource r, Resource regionResource, double[] region) {
                        super(r);
                        this.region = region;
+                       this.path = createPathForRegion(region);
+                       this.regionResource = regionResource;
+               }
+
+               public RegionCondition(ReadGraph graph, Resource r) throws DatabaseException {
+                       super(graph, r);
                        
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
+                       this.regionResource = graph.getPossibleObject(resource, ES.RegionCondition_HasRegion);
+                       this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area) : null;
+                       this.path = createPathForRegion(region);
+               }
+
+               public static Path2D createPathForRegion(double[] region) {
                        Path2D path = new Path2D.Double();
-                       double startX = region[0];
-                       double startY = region[1];
-                       path.moveTo(startX, startY);
-                       for (int i = 2; i < region.length; i+=2)
-                               path.lineTo(region[i], region[i+1]);
-                       path.closePath();
+                       if (region != null) {
+                               double startX = region[0];
+                               double startY = region[1];
+                               path.moveTo(startX, startY);
+                               for (int i = 2; i < region.length; i+=2)
+                                       path.lineTo(region[i], region[i+1]);
+                               path.closePath();
+                       }
 
-                       this.path = path;
-                       this.regionResource = regionResoruce;
+                       return path;
                }
 
                public Resource regionResource;
@@ -631,12 +798,34 @@ public class ElementSelector {
                Path2D path;
 
                @Override
-               boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+               public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
                        double[] transform = graph.getRelatedValue(r, DIA.HasTransform);
                        double x = transform[4];
                        double y = transform[5];
-                       return path.contains(x, y);
-               } 
+                       return path.contains(x, y) ^ isInverse;
+               }
+               
+               @Override
+               public Resource update(WriteGraph graph) throws DatabaseException {
+                       Layer0 L0 = Layer0.getInstance(graph);
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
+                       
+                       if (resource == null) {
+                               resource = graph.newResource();
+                               graph.claim(resource, L0.InstanceOf, ES.RegionCondition);
+                       }
+                       
+                       super.update(graph);
+                       
+                       graph.claim(resource, ES.RegionCondition_HasRegion, regionResource);
+                       
+                       // Re-read region data to match DB
+                       this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area, Bindings.DOUBLE_ARRAY) : null;
+                       this.path = createPathForRegion(region);
+                       
+                       return resource;
+               }
        }
        
        public static class RouteCondition extends Condition {
@@ -646,12 +835,44 @@ public class ElementSelector {
                        this.routeResource = routeResource;
                }
 
+               public RouteCondition(ReadGraph graph, Resource r) throws DatabaseException {
+                       super(graph, r);
+                       this.routeResource = graph.getPossibleObject(resource, ES.RouteCondition_HasRoute);
+                       this.routePoints = getRoutePoints(graph, routeResource);
+               }
+
+               public static Set<Resource> getRoutePoints(ReadGraph graph, Resource routeResource) throws DatabaseException {
+                       return routeResource != null ?
+                                       new HashSet<>(ListUtils.toList(graph, routeResource)) :
+                                       Collections.emptySet();
+               }
+
                public Resource routeResource;
                Set<Resource> routePoints;
 
                @Override
-               boolean match(ReadGraph graph, Resource r) throws DatabaseException {
-                       return routePoints.contains(r);
+               public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+                       return routePoints.contains(r) ^ isInverse;
+               }
+               
+               @Override
+               public Resource update(WriteGraph graph) throws DatabaseException {
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       Layer0 L0 = Layer0.getInstance(graph);
+                       
+                       if (resource == null) {
+                               resource = graph.newResource();
+                               graph.claim(resource, L0.InstanceOf, ES.RouteCondition);
+                       }
+                       
+                       super.update(graph);
+                       
+                       if (routeResource != null)
+                               graph.claim(resource, ES.RouteCondition_HasRoute, routeResource);
+                       
+                       this.routePoints = getRoutePoints(graph, routeResource);
+                       
+                       return resource;
                }
        }
        
@@ -664,11 +885,32 @@ public class ElementSelector {
                        this.conditions = conditions;
                }
                
+               public AggregateCondition(ReadGraph graph, Resource r) throws DatabaseException {
+                       super(graph, r);
+                       Collection<Resource> conditionResources = graph.getObjects(resource, ES.HasSubcondition);
+                       conditions = new ArrayList<>(conditionResources.size());
+                       for (Resource c : conditionResources) {
+                               conditions.add(buildCondition(graph, c));
+                       }
+                       if (graph.isInstanceOf(resource, ES.Conjunction))
+                               this.type = AggregateCondition.Type.CONJUNCTION;
+                       else if (graph.isInstanceOf(resource, ES.Negation))
+                               this.type = AggregateCondition.Type.NEGATION;
+                       else if (graph.isInstanceOf(resource, ES.Disjunction))
+                               this.type = AggregateCondition.Type.DISJUNCTION;
+                       else
+                               throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource)));
+               }
+
                public Type type;
                public List<Condition> conditions;
                
                @Override
-               boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+               public boolean match(ReadGraph graph, Resource r) throws DatabaseException {
+                       return doMatch(graph, r) ^ isInverse;
+               }
+               
+               private boolean doMatch(ReadGraph graph, Resource r) throws DatabaseException {
                        switch (type) {
                        case DISJUNCTION:
                                for (Condition c : conditions)
@@ -687,6 +929,41 @@ public class ElementSelector {
                                throw new IllegalArgumentException("Unknown aggregate condition type " + type);
                        }
                }
+               
+               @Override
+               public Resource update(WriteGraph graph) throws DatabaseException {
+                       Layer0 L0 = Layer0.getInstance(graph);
+                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                       
+                       if (resource == null) {
+                               resource = graph.newResource();
+                       } else {
+                               graph.deny(resource, L0.InstanceOf);
+                               graph.deny(resource, ES.HasSubcondition);
+                       }
+                       
+                       Resource type;
+                       switch (this.type) {
+                       case CONJUNCTION:
+                               type = ES.Conjunction; break;
+                       case DISJUNCTION:
+                               type = ES.Disjunction; break;
+                       case NEGATION:
+                               type = ES.Negation; break;
+                       default:
+                               throw new IllegalStateException("Unknown condition type " + this.type);
+                       }
+                       
+                       graph.claim(resource, L0.InstanceOf, type);
+                       
+                       super.update(graph);
+                       
+                       for (Condition c : conditions) {
+                               graph.claim(resource, ES.HasSubcondition, c.update(graph));
+                       }
+                       
+                       return resource;
+               }
        }
        
        public static class ElementSelectorQuery extends ResourceRead<ElementSelector> {