source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSRuleIndex.java@ 17318

Last change on this file since 17318 was 16821, checked in by simon04, 4 years ago

Java 8: use Map.getOrDefault

File size: 9.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.util.ArrayList;
5import java.util.BitSet;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.Iterator;
9import java.util.List;
10import java.util.Map;
11import java.util.NoSuchElementException;
12import java.util.Optional;
13
14import org.openstreetmap.josm.data.osm.IPrimitive;
15import org.openstreetmap.josm.data.osm.KeyValueVisitor;
16import org.openstreetmap.josm.data.osm.Tagged;
17import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyCondition;
18import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
19import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition;
20import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition;
21import org.openstreetmap.josm.tools.Utils;
22
23/**
24 * A collection of {@link MapCSSRule}s, that are indexed by tag key and value.
25 *
26 * Speeds up the process of finding all rules that match a certain primitive.
27 *
28 * Rules with a {@link SimpleKeyValueCondition} [key=value] or rules that require a specific key to be set are
29 * indexed. Now you only need to loop the tags of a primitive to retrieve the possibly matching rules.
30 *
31 * To use this index, you need to {@link #add(MapCSSRule)} all rules to it. You then need to call
32 * {@link #initIndex()}. Afterwards, you can use {@link #getRuleCandidates(IPrimitive)} to get an iterator over
33 * all rules that might be applied to that primitive.
34 */
35public class MapCSSRuleIndex {
36 /**
37 * This is an iterator over all rules that are marked as possible in the bitset.
38 *
39 * @author Michael Zangl
40 */
41 private final class RuleCandidatesIterator implements Iterator<MapCSSRule>, KeyValueVisitor {
42 private final BitSet ruleCandidates;
43 private int next;
44
45 private RuleCandidatesIterator(BitSet ruleCandidates) {
46 this.ruleCandidates = ruleCandidates;
47 }
48
49 @Override
50 public boolean hasNext() {
51 return next >= 0 && next < rules.size();
52 }
53
54 @Override
55 public MapCSSRule next() {
56 if (!hasNext())
57 throw new NoSuchElementException();
58 MapCSSRule rule = rules.get(next);
59 next = ruleCandidates.nextSetBit(next + 1);
60 return rule;
61 }
62
63 @Override
64 public void remove() {
65 throw new UnsupportedOperationException();
66 }
67
68 @Override
69 public void visitKeyValue(Tagged p, String key, String value) {
70 MapCSSKeyRules v = index.get(key);
71 if (v != null) {
72 BitSet rs = v.get(value);
73 ruleCandidates.or(rs);
74 }
75 }
76
77 /**
78 * Call this before using the iterator.
79 */
80 public void prepare() {
81 next = ruleCandidates.nextSetBit(0);
82 }
83 }
84
85 /**
86 * This is a map of all rules that are only applied if the primitive has a given key (and possibly value)
87 *
88 * @author Michael Zangl
89 */
90 private static final class MapCSSKeyRules {
91 /**
92 * The indexes of rules that might be applied if this tag is present and the value has no special handling.
93 */
94 BitSet generalRules = new BitSet();
95
96 /**
97 * A map that stores the indexes of rules that might be applied if the key=value pair is present on this
98 * primitive. This includes all key=* rules.
99 */
100 Map<String, BitSet> specialRules = new HashMap<>();
101
102 public void addForKey(int ruleIndex) {
103 generalRules.set(ruleIndex);
104 for (BitSet r : specialRules.values()) {
105 r.set(ruleIndex);
106 }
107 }
108
109 public void addForKeyAndValue(String value, int ruleIndex) {
110 BitSet forValue = specialRules.get(value);
111 if (forValue == null) {
112 forValue = new BitSet();
113 forValue.or(generalRules);
114 specialRules.put(value.intern(), forValue);
115 }
116 forValue.set(ruleIndex);
117 }
118
119 public BitSet get(String value) {
120 return specialRules.getOrDefault(value, generalRules);
121 }
122 }
123
124 /**
125 * All rules this index is for. Once this index is built, this list is sorted.
126 */
127 private final List<MapCSSRule> rules = new ArrayList<>();
128 /**
129 * All rules that only apply when the given key is present.
130 */
131 private final Map<String, MapCSSKeyRules> index = new HashMap<>();
132 /**
133 * Rules that do not require any key to be present. Only the index in the {@link #rules} array is stored.
134 */
135 private final BitSet remaining = new BitSet();
136
137 /**
138 * Add a rule to this index. This needs to be called before {@link #initIndex()} is called.
139 * @param rule The rule to add.
140 */
141 public void add(MapCSSRule rule) {
142 rules.add(rule);
143 }
144
145 /**
146 * Initialize the index.
147 * <p>
148 * You must own the write lock of STYLE_SOURCE_LOCK when calling this method.
149 */
150 public void initIndex() {
151 Collections.sort(rules);
152 for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
153 MapCSSRule r = rules.get(ruleIndex);
154 for (Selector selector : r.selectors) {
155 Selector selRightmost = selector;
156 while (selRightmost instanceof Selector.ChildOrParentSelector) {
157 selRightmost = ((Selector.ChildOrParentSelector) selRightmost).right;
158 }
159 final List<Condition> conditions = selRightmost.getConditions();
160 if (conditions == null || conditions.isEmpty()) {
161 remaining.set(ruleIndex);
162 continue;
163 }
164 Optional<SimpleKeyValueCondition> lastCondition = Utils.filteredCollection(conditions, SimpleKeyValueCondition.class)
165 .stream()
166 .reduce((first, last) -> last);
167 if (lastCondition.isPresent()) {
168 getEntryInIndex(lastCondition.get().k).addForKeyAndValue(lastCondition.get().v, ruleIndex);
169 } else {
170 String key = findAnyRequiredKey(conditions);
171 if (key != null) {
172 getEntryInIndex(key).addForKey(ruleIndex);
173 } else {
174 remaining.set(ruleIndex);
175 }
176 }
177 }
178 }
179 }
180
181 /**
182 * Search for any key that condition might depend on.
183 *
184 * @param conds The conditions to search through.
185 * @return An arbitrary key this rule depends on or <code>null</code> if there is no such key.
186 */
187 private static String findAnyRequiredKey(List<Condition> conds) {
188 String key = null;
189 for (Condition c : conds) {
190 if (c instanceof KeyCondition) {
191 KeyCondition keyCondition = (KeyCondition) c;
192 if (!keyCondition.negateResult && conditionRequiresKeyPresence(keyCondition.matchType)) {
193 key = keyCondition.label;
194 }
195 } else if (c instanceof KeyValueCondition) {
196 KeyValueCondition keyValueCondition = (KeyValueCondition) c;
197 if (keyValueCondition.requiresExactKeyMatch()) {
198 key = keyValueCondition.k;
199 }
200 }
201 }
202 return key;
203 }
204
205 private static boolean conditionRequiresKeyPresence(KeyMatchType matchType) {
206 return matchType != KeyMatchType.REGEX;
207 }
208
209 private MapCSSKeyRules getEntryInIndex(String key) {
210 MapCSSKeyRules rulesWithMatchingKey = index.get(key);
211 if (rulesWithMatchingKey == null) {
212 rulesWithMatchingKey = new MapCSSKeyRules();
213 index.put(key.intern(), rulesWithMatchingKey);
214 }
215 return rulesWithMatchingKey;
216 }
217
218 /**
219 * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to
220 * not match this primitive.
221 * <p>
222 * You must have a read lock of STYLE_SOURCE_LOCK when calling this method.
223 *
224 * @param osm the primitive to match
225 * @return An iterator over possible rules in the right order.
226 * @since 13810 (signature)
227 */
228 public Iterator<MapCSSRule> getRuleCandidates(IPrimitive osm) {
229 final BitSet ruleCandidates = new BitSet(rules.size());
230 ruleCandidates.or(remaining);
231
232 final RuleCandidatesIterator candidatesIterator = new RuleCandidatesIterator(ruleCandidates);
233 osm.visitKeys(candidatesIterator);
234 candidatesIterator.prepare();
235 return candidatesIterator;
236 }
237
238 /**
239 * Clear the index.
240 * <p>
241 * You must own the write lock STYLE_SOURCE_LOCK when calling this method.
242 */
243 public void clear() {
244 rules.clear();
245 index.clear();
246 remaining.clear();
247 }
248
249 /**
250 * Check if this index is empty.
251 * @return true if this index is empty.
252 * @since 16784
253 */
254 public boolean isEmpty() {
255 return rules.isEmpty();
256 }
257}
Note: See TracBrowser for help on using the repository browser.