/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.styling.css.selector; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.util.logging.Logging; /** * A selector identifies which features are going to be matched by a certain feature, possibly * including one or more scale ranges in which the rule is valid. A subclass of selectors, known as * pseudo-selectors, are used to specify how to fill/stroke the innards of a mark used to depict * points, lines and fills. * * @author Andrea Aime - GeoSolutions * */ public abstract class Selector implements Comparable<Selector> { private static final List<Class<? extends Selector>> BASE_CLASSES = Arrays.asList(TypeName.class, ScaleRange.class, Id.class, Data.class, PseudoClass.class); static final Logger LOGGER = Logging.getLogger(Selector.class); public static final Selector ACCEPT = new Accept(); public static final Selector REJECT = new Reject(); public static Selector and(Selector s1, Selector s2) { return and(s1, s2, null); } public static Selector and(Selector s1, Selector s2, Object context) { Selector result = andInternal(s1, s2, context); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Combined " + s1 + " and " + s2 + " into: " + result); } return result; } private static Selector andInternal(Selector s1, Selector s2, Object context) { // merge with Accept if (s1 instanceof Accept) { return s2; } else if (s2 instanceof Accept) { return s1; } // merge with Negate if (s1 instanceof Reject || s2 instanceof Reject) { return REJECT; } // if one of the two is an or, we can fold the other into it to preserve // a structure with a top-most or if(s1 instanceof Or) { return foldInOr((Or) s1, s2, context); } else if(s2 instanceof Or) { return foldInOr((Or) s2, s1, context); } // ok, we can flatten all the concatenated and nested ands in a single list List<Selector> selectors = new ArrayList<>(); flatten(selectors, s1, And.class); flatten(selectors, s2, And.class); // map by class, same class selectors can be merged Map<Class, List<Selector>> classifieds = mapByClass(selectors); // simplest scenario, there is a Reject if(classifieds.get(Reject.class) != null) { return REJECT; } // get rid of Accept, they are irrelevant classifieds.remove(Accept.class); // perform combinations for selected types for (Class<? extends Selector> clazz : BASE_CLASSES) { List<Selector> classSelectors = classifieds.get(clazz); if (classSelectors == null) { continue; } if(classSelectors.size() > 1) { try { Method combineAnd = clazz.getDeclaredMethod("combineAnd", List.class, Object.class); Selector result = (Selector) combineAnd.invoke(null, classSelectors, context); if (result == REJECT) { return REJECT; } else if (result == ACCEPT) { classifieds.remove(clazz); } else if (result instanceof And) { classifieds.put(clazz, new ArrayList<>(((Composite) result).getChildren())); } else { classifieds.put(clazz, Collections.singletonList(result)); } } catch (Exception e) { throw new RuntimeException(e); } } } // build the result List<Selector> finalList = new ArrayList<>(); for (Class c : classifieds.keySet()) { List<Selector> list = classifieds.get(c); if (list != null) { finalList.addAll(list); } } if (finalList.size() == 0) { return ACCEPT; } else if (finalList.size() == 1) { return finalList.get(0); } else { return new And(finalList); } } private static Selector foldInOr(Or or, Selector anded, Object context) { List<Selector> newChildren = new ArrayList<>(); for (Selector s : or.getChildren()) { Selector combined = and(s, anded, context); if (combined == ACCEPT) { return ACCEPT; } else if (combined != REJECT) { newChildren.add(combined); } } if (newChildren.size() == 0) { return REJECT; } // flatten the nested or-s if necessary List<Selector> selectors = new ArrayList<>(); for (Selector child : newChildren) { flatten(selectors, child, Or.class); } return new Or(selectors); } /** * Combines in or and simplifies the two given selectors * * @param s1 * @param s2 * @param context * @return */ public static Selector or(Selector s1, Selector s2, Object context) { // merge with Reject if (s1 instanceof Reject) { return s2; } else if (s2 instanceof Reject) { return s1; } // merge with Accept if (s1 instanceof Accept || s2 instanceof Accept) { return ACCEPT; } // ok, we can flatten all the concatenated and nested ors in a single list List<Selector> selectors = new ArrayList<>(); flatten(selectors, s1, Or.class); flatten(selectors, s2, Or.class); // map by class, same class selectors can be merged Map<Class, List<Selector>> classifieds = mapByClass(selectors); // simplest scenario, there is an Accept if (classifieds.get(Accept.class) != null) { return ACCEPT; } // get rid of Reject, they are irrelevant classifieds.remove(Reject.class); // build the result List<Selector> finalList = new ArrayList<>(); for (Class c : classifieds.keySet()) { List<Selector> list = classifieds.get(c); if (list != null) { finalList.addAll(list); } } if (finalList.size() == 0) { return REJECT; } else if (finalList.size() == 1) { return finalList.get(0); } else { return new Or(finalList); } } private static void flatten(List<Selector> selectors, Selector s, Class<? extends Composite> clazz) { if (!clazz.isInstance(s)) { selectors.add(s); } else { Composite composite = ((Composite) s); for (Selector child : composite.getChildren()) { flatten(selectors, child, clazz); } } } private static Map<Class, List<Selector>> mapByClass(List<Selector> selectors) { Map<Class, List<Selector>> result = new LinkedHashMap<>(); for (Selector s : selectors) { Class<? extends Selector> clazz = s.getClass(); List<Selector> list = result.get(clazz); if (list == null) { list = new ArrayList<>(); result.put(clazz, list); } list.add(s); } return result; } /** * Returns the specificity of this selector * * @return */ public abstract Specificity getSpecificity(); @Override public int compareTo(Selector o) { return getSpecificity().compareTo(o.getSpecificity()); } public abstract Object accept(SelectorVisitor visitor); }