001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.text.MessageFormat;
007import java.util.Collections;
008import java.util.Iterator;
009import java.util.Map;
010import java.util.Set;
011import java.util.stream.Collectors;
012import java.util.stream.Stream;
013
014import org.openstreetmap.josm.data.osm.INode;
015import org.openstreetmap.josm.data.osm.IPrimitive;
016import org.openstreetmap.josm.data.osm.IRelation;
017import org.openstreetmap.josm.data.osm.IWay;
018import org.openstreetmap.josm.data.osm.OsmUtils;
019import org.openstreetmap.josm.tools.JosmRuntimeException;
020import org.openstreetmap.josm.tools.Logging;
021
022/**
023 * Store indexes of {@link MapCSSRule}s using {@link MapCSSRuleIndex} differentiated by {@linkplain Selector#getBase() base}
024 */
025public final class MapCSSStyleIndex {
026
027    /**
028     * Rules for nodes
029     */
030    final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex();
031    /**
032     * Rules for ways without tag area=no
033     */
034    final MapCSSRuleIndex wayRules = new MapCSSRuleIndex();
035    /**
036     * Rules for ways with tag area=no
037     */
038    final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex();
039    /**
040     * Rules for relations that are not multipolygon relations
041     */
042    final MapCSSRuleIndex relationRules = new MapCSSRuleIndex();
043    /**
044     * Rules for multipolygon relations
045     */
046    final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex();
047    /**
048     * rules to apply canvas properties
049     */
050    final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex();
051
052    /**
053     * Clear the index.
054     * <p>
055     * You must own the write lock STYLE_SOURCE_LOCK when calling this method.
056     */
057    public void clear() {
058        nodeRules.clear();
059        wayRules.clear();
060        wayNoAreaRules.clear();
061        relationRules.clear();
062        multipolygonRules.clear();
063        canvasRules.clear();
064    }
065
066    /**
067     * Builds and initializes the index.
068     * <p>
069     * You must own the write lock of STYLE_SOURCE_LOCK when calling this method.
070     * @param ruleStream the rules to index
071     */
072    public void buildIndex(Stream<MapCSSRule> ruleStream) {
073        clear();
074        // optimization: filter rules for different primitive types
075        ruleStream.forEach(rule -> {
076            final Map<String, MapCSSRule> selectorsByBase;
077            final Set<String> bases = rule.selectors.stream().map(Selector::getBase).collect(Collectors.toSet());
078            if (bases.size() == 1) {
079                // reuse rule
080                selectorsByBase = Collections.singletonMap(bases.iterator().next(), rule);
081            } else {
082                selectorsByBase = rule.selectors.stream()
083                        .collect(Collectors.groupingBy(Selector::getBase,
084                                Collectors.collectingAndThen(Collectors.toList(), selectors -> new MapCSSRule(selectors, rule.declaration))));
085            }
086            selectorsByBase.forEach((base, optRule) -> {
087                switch (base) {
088                case Selector.BASE_NODE:
089                    nodeRules.add(optRule);
090                    break;
091                case Selector.BASE_WAY:
092                    wayNoAreaRules.add(optRule);
093                    wayRules.add(optRule);
094                    break;
095                case Selector.BASE_AREA:
096                    wayRules.add(optRule);
097                    multipolygonRules.add(optRule);
098                    break;
099                case Selector.BASE_RELATION:
100                    relationRules.add(optRule);
101                    multipolygonRules.add(optRule);
102                    break;
103                case Selector.BASE_ANY:
104                    nodeRules.add(optRule);
105                    wayRules.add(optRule);
106                    wayNoAreaRules.add(optRule);
107                    relationRules.add(optRule);
108                    multipolygonRules.add(optRule);
109                    break;
110                case Selector.BASE_CANVAS:
111                    canvasRules.add(optRule);
112                    break;
113                case Selector.BASE_META:
114                case Selector.BASE_SETTING:
115                case Selector.BASE_SETTINGS:
116                    break;
117                default:
118                    final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base));
119                    Logging.warn(tr("Failed to index validator rules. Error was: {0}", e.getMessage()));
120                    Logging.error(e);
121                }
122            });
123        });
124        initIndex();
125    }
126
127    private void initIndex() {
128        nodeRules.initIndex();
129        wayRules.initIndex();
130        wayNoAreaRules.initIndex();
131        relationRules.initIndex();
132        multipolygonRules.initIndex();
133        canvasRules.initIndex();
134    }
135
136    /**
137     * Get the index of rules for the given primitive.
138     * @param p the primitive
139     * @return index of rules for the given primitive
140     */
141    public MapCSSRuleIndex get(IPrimitive p) {
142        if (p instanceof INode) {
143            return nodeRules;
144        } else if (p instanceof IWay) {
145            if (OsmUtils.isFalse(p.get("area"))) {
146                return wayNoAreaRules;
147            } else {
148                return wayRules;
149            }
150        } else if (p instanceof IRelation) {
151            if (((IRelation<?>) p).isMultipolygon()) {
152                return multipolygonRules;
153            } else if (p.hasKey("#canvas")) {
154                return canvasRules;
155            } else {
156                return relationRules;
157            }
158        } else {
159            throw new IllegalArgumentException("Unsupported type: " + p);
160        }
161    }
162
163    /**
164     * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to
165     * not match this primitive.
166     * <p>
167     * You must have a read lock of STYLE_SOURCE_LOCK when calling this method.
168     *
169     * @param osm the primitive to match
170     * @return An iterator over possible rules in the right order.
171     */
172    public Iterator<MapCSSRule> getRuleCandidates(IPrimitive osm) {
173        return get(osm).getRuleCandidates(osm);
174    }
175
176    /**
177     * Check if this index is empty.
178     * @return true if this index is empty.
179     * @since 16784
180     */
181    public boolean isEmpty() {
182        return nodeRules.isEmpty() && wayRules.isEmpty() && wayNoAreaRules.isEmpty() && relationRules.isEmpty()
183                && multipolygonRules.isEmpty() && canvasRules.isEmpty();
184    }
185}