source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java@ 11384

Last change on this file since 11384 was 11384, checked in by Don-vip, 7 years ago

findbugs - EQ_DOESNT_OVERRIDE_EQUALS, MS_MUTABLE_COLLECTION_PKGPROTECT

  • Property svn:eol-style set to native
File size: 27.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.io.ByteArrayInputStream;
8import java.io.File;
9import java.io.IOException;
10import java.io.InputStream;
11import java.lang.reflect.Field;
12import java.nio.charset.StandardCharsets;
13import java.text.MessageFormat;
14import java.util.ArrayList;
15import java.util.BitSet;
16import java.util.Collections;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.List;
21import java.util.Locale;
22import java.util.Map;
23import java.util.Map.Entry;
24import java.util.NoSuchElementException;
25import java.util.Set;
26import java.util.concurrent.locks.ReadWriteLock;
27import java.util.concurrent.locks.ReentrantReadWriteLock;
28import java.util.zip.ZipEntry;
29import java.util.zip.ZipFile;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.data.Version;
33import org.openstreetmap.josm.data.osm.AbstractPrimitive;
34import org.openstreetmap.josm.data.osm.AbstractPrimitive.KeyValueVisitor;
35import org.openstreetmap.josm.data.osm.Node;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.Relation;
38import org.openstreetmap.josm.data.osm.Way;
39import org.openstreetmap.josm.gui.mappaint.Cascade;
40import org.openstreetmap.josm.gui.mappaint.Environment;
41import org.openstreetmap.josm.gui.mappaint.MultiCascade;
42import org.openstreetmap.josm.gui.mappaint.Range;
43import org.openstreetmap.josm.gui.mappaint.StyleKeys;
44import org.openstreetmap.josm.gui.mappaint.StyleSetting;
45import org.openstreetmap.josm.gui.mappaint.StyleSetting.BooleanStyleSetting;
46import org.openstreetmap.josm.gui.mappaint.StyleSource;
47import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyCondition;
48import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
49import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition;
50import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.Op;
51import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition;
52import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
53import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
54import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;
55import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
56import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
57import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
58import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement;
59import org.openstreetmap.josm.gui.preferences.SourceEntry;
60import org.openstreetmap.josm.io.CachedFile;
61import org.openstreetmap.josm.tools.CheckParameterUtil;
62import org.openstreetmap.josm.tools.JosmRuntimeException;
63import org.openstreetmap.josm.tools.LanguageInfo;
64import org.openstreetmap.josm.tools.Utils;
65
66/**
67 * This is a mappaint style that is based on MapCSS rules.
68 */
69public class MapCSSStyleSource extends StyleSource {
70
71 /**
72 * The accepted MIME types sent in the HTTP Accept header.
73 * @since 6867
74 */
75 public static final String MAPCSS_STYLE_MIME_TYPES =
76 "text/x-mapcss, text/mapcss, text/css; q=0.9, text/plain; q=0.8, application/zip, application/octet-stream; q=0.5";
77
78 // all rules
79 public final List<MapCSSRule> rules = new ArrayList<>();
80 // rule indices, filtered by primitive type
81 public final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); // nodes
82 public final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); // ways without tag area=no
83 public final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); // ways with tag area=no
84 public final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); // relations that are not multipolygon relations
85 public final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); // multipolygon relations
86 public final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex(); // rules to apply canvas properties
87
88 private Color backgroundColorOverride;
89 private String css;
90 private ZipFile zipFile;
91
92 /**
93 * This lock prevents concurrent execution of {@link MapCSSRuleIndex#clear() } /
94 * {@link MapCSSRuleIndex#initIndex()} and {@link MapCSSRuleIndex#getRuleCandidates }.
95 *
96 * For efficiency reasons, these methods are synchronized higher up the
97 * stack trace.
98 */
99 public static final ReadWriteLock STYLE_SOURCE_LOCK = new ReentrantReadWriteLock();
100
101 /**
102 * Set of all supported MapCSS keys.
103 */
104 static final Set<String> SUPPORTED_KEYS = new HashSet<>();
105 static {
106 Field[] declaredFields = StyleKeys.class.getDeclaredFields();
107 for (Field f : declaredFields) {
108 try {
109 SUPPORTED_KEYS.add((String) f.get(null));
110 if (!f.getName().toLowerCase(Locale.ENGLISH).replace('_', '-').equals(f.get(null))) {
111 throw new JosmRuntimeException(f.getName());
112 }
113 } catch (IllegalArgumentException | IllegalAccessException ex) {
114 throw new JosmRuntimeException(ex);
115 }
116 }
117 for (LineElement.LineType lt : LineElement.LineType.values()) {
118 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.COLOR);
119 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES);
120 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_BACKGROUND_COLOR);
121 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_BACKGROUND_OPACITY);
122 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_OFFSET);
123 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.LINECAP);
124 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.LINEJOIN);
125 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.MITERLIMIT);
126 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.OFFSET);
127 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.OPACITY);
128 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.REAL_WIDTH);
129 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.WIDTH);
130 }
131 }
132
133 /**
134 * A collection of {@link MapCSSRule}s, that are indexed by tag key and value.
135 *
136 * Speeds up the process of finding all rules that match a certain primitive.
137 *
138 * Rules with a {@link SimpleKeyValueCondition} [key=value] or rules that require a specific key to be set are
139 * indexed. Now you only need to loop the tags of a primitive to retrieve the possibly matching rules.
140 *
141 * To use this index, you need to {@link #add(MapCSSRule)} all rules to it. You then need to call
142 * {@link #initIndex()}. Afterwards, you can use {@link #getRuleCandidates(OsmPrimitive)} to get an iterator over
143 * all rules that might be applied to that primitive.
144 */
145 public static class MapCSSRuleIndex {
146 /**
147 * This is an iterator over all rules that are marked as possible in the bitset.
148 *
149 * @author Michael Zangl
150 */
151 private final class RuleCandidatesIterator implements Iterator<MapCSSRule>, KeyValueVisitor {
152 private final BitSet ruleCandidates;
153 private int next;
154
155 private RuleCandidatesIterator(BitSet ruleCandidates) {
156 this.ruleCandidates = ruleCandidates;
157 }
158
159 @Override
160 public boolean hasNext() {
161 return next >= 0 && next < rules.size();
162 }
163
164 @Override
165 public MapCSSRule next() {
166 if (!hasNext())
167 throw new NoSuchElementException();
168 MapCSSRule rule = rules.get(next);
169 next = ruleCandidates.nextSetBit(next + 1);
170 return rule;
171 }
172
173 @Override
174 public void remove() {
175 throw new UnsupportedOperationException();
176 }
177
178 @Override
179 public void visitKeyValue(AbstractPrimitive p, String key, String value) {
180 MapCSSKeyRules v = index.get(key);
181 if (v != null) {
182 BitSet rs = v.get(value);
183 ruleCandidates.or(rs);
184 }
185 }
186
187 /**
188 * Call this before using the iterator.
189 */
190 public void prepare() {
191 next = ruleCandidates.nextSetBit(0);
192 }
193 }
194
195 /**
196 * This is a map of all rules that are only applied if the primitive has a given key (and possibly value)
197 *
198 * @author Michael Zangl
199 */
200 private static final class MapCSSKeyRules {
201 /**
202 * The indexes of rules that might be applied if this tag is present and the value has no special handling.
203 */
204 BitSet generalRules = new BitSet();
205
206 /**
207 * A map that sores the indexes of rules that might be applied if the key=value pair is present on this
208 * primitive. This includes all key=* rules.
209 */
210 Map<String, BitSet> specialRules = new HashMap<>();
211
212 public void addForKey(int ruleIndex) {
213 generalRules.set(ruleIndex);
214 for (BitSet r : specialRules.values()) {
215 r.set(ruleIndex);
216 }
217 }
218
219 public void addForKeyAndValue(String value, int ruleIndex) {
220 BitSet forValue = specialRules.get(value);
221 if (forValue == null) {
222 forValue = new BitSet();
223 forValue.or(generalRules);
224 specialRules.put(value.intern(), forValue);
225 }
226 forValue.set(ruleIndex);
227 }
228
229 public BitSet get(String value) {
230 BitSet forValue = specialRules.get(value);
231 if (forValue != null) return forValue; else return generalRules;
232 }
233 }
234
235 /**
236 * All rules this index is for. Once this index is built, this list is sorted.
237 */
238 private final List<MapCSSRule> rules = new ArrayList<>();
239 /**
240 * All rules that only apply when the given key is present.
241 */
242 private final Map<String, MapCSSKeyRules> index = new HashMap<>();
243 /**
244 * Rules that do not require any key to be present. Only the index in the {@link #rules} array is stored.
245 */
246 private final BitSet remaining = new BitSet();
247
248 /**
249 * Add a rule to this index. This needs to be called before {@link #initIndex()} is called.
250 * @param rule The rule to add.
251 */
252 public void add(MapCSSRule rule) {
253 rules.add(rule);
254 }
255
256 /**
257 * Initialize the index.
258 * <p>
259 * You must own the write lock of STYLE_SOURCE_LOCK when calling this method.
260 */
261 public void initIndex() {
262 Collections.sort(rules);
263 for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
264 MapCSSRule r = rules.get(ruleIndex);
265 // find the rightmost selector, this must be a GeneralSelector
266 Selector selRightmost = r.selector;
267 while (selRightmost instanceof ChildOrParentSelector) {
268 selRightmost = ((ChildOrParentSelector) selRightmost).right;
269 }
270 OptimizedGeneralSelector s = (OptimizedGeneralSelector) selRightmost;
271 if (s.conds == null) {
272 remaining.set(ruleIndex);
273 continue;
274 }
275 List<SimpleKeyValueCondition> sk = new ArrayList<>(Utils.filteredCollection(s.conds,
276 SimpleKeyValueCondition.class));
277 if (!sk.isEmpty()) {
278 SimpleKeyValueCondition c = sk.get(sk.size() - 1);
279 getEntryInIndex(c.k).addForKeyAndValue(c.v, ruleIndex);
280 } else {
281 String key = findAnyRequiredKey(s.conds);
282 if (key != null) {
283 getEntryInIndex(key).addForKey(ruleIndex);
284 } else {
285 remaining.set(ruleIndex);
286 }
287 }
288 }
289 }
290
291 /**
292 * Search for any key that condition might depend on.
293 *
294 * @param conds The conditions to search through.
295 * @return An arbitrary key this rule depends on or <code>null</code> if there is no such key.
296 */
297 private static String findAnyRequiredKey(List<Condition> conds) {
298 String key = null;
299 for (Condition c : conds) {
300 if (c instanceof KeyCondition) {
301 KeyCondition keyCondition = (KeyCondition) c;
302 if (!keyCondition.negateResult && conditionRequiresKeyPresence(keyCondition.matchType)) {
303 key = keyCondition.label;
304 }
305 } else if (c instanceof KeyValueCondition) {
306 KeyValueCondition keyValueCondition = (KeyValueCondition) c;
307 if (!Op.NEGATED_OPS.contains(keyValueCondition.op)) {
308 key = keyValueCondition.k;
309 }
310 }
311 }
312 return key;
313 }
314
315 private static boolean conditionRequiresKeyPresence(KeyMatchType matchType) {
316 return matchType != KeyMatchType.REGEX;
317 }
318
319 private MapCSSKeyRules getEntryInIndex(String key) {
320 MapCSSKeyRules rulesWithMatchingKey = index.get(key);
321 if (rulesWithMatchingKey == null) {
322 rulesWithMatchingKey = new MapCSSKeyRules();
323 index.put(key.intern(), rulesWithMatchingKey);
324 }
325 return rulesWithMatchingKey;
326 }
327
328 /**
329 * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to
330 * not match this primitive.
331 * <p>
332 * You must have a read lock of STYLE_SOURCE_LOCK when calling this method.
333 *
334 * @param osm the primitive to match
335 * @return An iterator over possible rules in the right order.
336 */
337 public Iterator<MapCSSRule> getRuleCandidates(OsmPrimitive osm) {
338 final BitSet ruleCandidates = new BitSet(rules.size());
339 ruleCandidates.or(remaining);
340
341 final RuleCandidatesIterator candidatesIterator = new RuleCandidatesIterator(ruleCandidates);
342 osm.visitKeys(candidatesIterator);
343 candidatesIterator.prepare();
344 return candidatesIterator;
345 }
346
347 /**
348 * Clear the index.
349 * <p>
350 * You must own the write lock STYLE_SOURCE_LOCK when calling this method.
351 */
352 public void clear() {
353 rules.clear();
354 index.clear();
355 remaining.clear();
356 }
357 }
358
359 /**
360 * Constructs a new, active {@link MapCSSStyleSource}.
361 * @param url URL that {@link org.openstreetmap.josm.io.CachedFile} understands
362 * @param name The name for this StyleSource
363 * @param shortdescription The title for that source.
364 */
365 public MapCSSStyleSource(String url, String name, String shortdescription) {
366 super(url, name, shortdescription);
367 }
368
369 /**
370 * Constructs a new {@link MapCSSStyleSource}
371 * @param entry The entry to copy the data (url, name, ...) from.
372 */
373 public MapCSSStyleSource(SourceEntry entry) {
374 super(entry);
375 }
376
377 /**
378 * <p>Creates a new style source from the MapCSS styles supplied in
379 * {@code css}</p>
380 *
381 * @param css the MapCSS style declaration. Must not be null.
382 * @throws IllegalArgumentException if {@code css} is null
383 */
384 public MapCSSStyleSource(String css) {
385 super(null, null, null);
386 CheckParameterUtil.ensureParameterNotNull(css);
387 this.css = css;
388 }
389
390 @Override
391 public void loadStyleSource() {
392 STYLE_SOURCE_LOCK.writeLock().lock();
393 try {
394 init();
395 rules.clear();
396 nodeRules.clear();
397 wayRules.clear();
398 wayNoAreaRules.clear();
399 relationRules.clear();
400 multipolygonRules.clear();
401 canvasRules.clear();
402 try (InputStream in = getSourceInputStream()) {
403 try {
404 // evaluate @media { ... } blocks
405 MapCSSParser preprocessor = new MapCSSParser(in, "UTF-8", MapCSSParser.LexicalState.PREPROCESSOR);
406 String mapcss = preprocessor.pp_root(this);
407
408 // do the actual mapcss parsing
409 InputStream in2 = new ByteArrayInputStream(mapcss.getBytes(StandardCharsets.UTF_8));
410 MapCSSParser parser = new MapCSSParser(in2, "UTF-8", MapCSSParser.LexicalState.DEFAULT);
411 parser.sheet(this);
412
413 loadMeta();
414 loadCanvas();
415 loadSettings();
416 } finally {
417 closeSourceInputStream(in);
418 }
419 } catch (IOException e) {
420 Main.warn(tr("Failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
421 Main.error(e);
422 logError(e);
423 } catch (TokenMgrError e) {
424 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
425 Main.error(e);
426 logError(e);
427 } catch (ParseException e) {
428 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
429 Main.error(e);
430 logError(new ParseException(e.getMessage())); // allow e to be garbage collected, it links to the entire token stream
431 }
432 // optimization: filter rules for different primitive types
433 for (MapCSSRule r: rules) {
434 // find the rightmost selector, this must be a GeneralSelector
435 Selector selRightmost = r.selector;
436 while (selRightmost instanceof ChildOrParentSelector) {
437 selRightmost = ((ChildOrParentSelector) selRightmost).right;
438 }
439 MapCSSRule optRule = new MapCSSRule(r.selector.optimizedBaseCheck(), r.declaration);
440 final String base = ((GeneralSelector) selRightmost).getBase();
441 switch (base) {
442 case "node":
443 nodeRules.add(optRule);
444 break;
445 case "way":
446 wayNoAreaRules.add(optRule);
447 wayRules.add(optRule);
448 break;
449 case "area":
450 wayRules.add(optRule);
451 multipolygonRules.add(optRule);
452 break;
453 case "relation":
454 relationRules.add(optRule);
455 multipolygonRules.add(optRule);
456 break;
457 case "*":
458 nodeRules.add(optRule);
459 wayRules.add(optRule);
460 wayNoAreaRules.add(optRule);
461 relationRules.add(optRule);
462 multipolygonRules.add(optRule);
463 break;
464 case "canvas":
465 canvasRules.add(r);
466 break;
467 case "meta":
468 case "setting":
469 break;
470 default:
471 final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base));
472 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
473 Main.error(e);
474 logError(e);
475 }
476 }
477 nodeRules.initIndex();
478 wayRules.initIndex();
479 wayNoAreaRules.initIndex();
480 relationRules.initIndex();
481 multipolygonRules.initIndex();
482 canvasRules.initIndex();
483 } finally {
484 STYLE_SOURCE_LOCK.writeLock().unlock();
485 }
486 }
487
488 @Override
489 public InputStream getSourceInputStream() throws IOException {
490 if (css != null) {
491 return new ByteArrayInputStream(css.getBytes(StandardCharsets.UTF_8));
492 }
493 CachedFile cf = getCachedFile();
494 if (isZip) {
495 File file = cf.getFile();
496 zipFile = new ZipFile(file, StandardCharsets.UTF_8);
497 zipIcons = file;
498 ZipEntry zipEntry = zipFile.getEntry(zipEntryPath);
499 return zipFile.getInputStream(zipEntry);
500 } else {
501 zipFile = null;
502 zipIcons = null;
503 return cf.getInputStream();
504 }
505 }
506
507 @Override
508 @SuppressWarnings("resource")
509 public CachedFile getCachedFile() throws IOException {
510 return new CachedFile(url).setHttpAccept(MAPCSS_STYLE_MIME_TYPES); // NOSONAR
511 }
512
513 @Override
514 public void closeSourceInputStream(InputStream is) {
515 super.closeSourceInputStream(is);
516 if (isZip) {
517 Utils.close(zipFile);
518 }
519 }
520
521 /**
522 * load meta info from a selector "meta"
523 */
524 private void loadMeta() {
525 Cascade c = constructSpecial("meta");
526 String pTitle = c.get("title", null, String.class);
527 if (title == null) {
528 title = pTitle;
529 }
530 String pIcon = c.get("icon", null, String.class);
531 if (icon == null) {
532 icon = pIcon;
533 }
534 }
535
536 private void loadCanvas() {
537 Cascade c = constructSpecial("canvas");
538 backgroundColorOverride = c.get("fill-color", null, Color.class);
539 }
540
541 private void loadSettings() {
542 settings.clear();
543 settingValues.clear();
544 MultiCascade mc = new MultiCascade();
545 Node n = new Node();
546 String code = LanguageInfo.getJOSMLocaleCode();
547 n.put("lang", code);
548 // create a fake environment to read the meta data block
549 Environment env = new Environment(n, mc, "default", this);
550
551 for (MapCSSRule r : rules) {
552 if (r.selector instanceof GeneralSelector) {
553 GeneralSelector gs = (GeneralSelector) r.selector;
554 if ("setting".equals(gs.getBase())) {
555 if (!gs.matchesConditions(env)) {
556 continue;
557 }
558 env.layer = null;
559 env.layer = gs.getSubpart().getId(env);
560 r.execute(env);
561 }
562 }
563 }
564 for (Entry<String, Cascade> e : mc.getLayers()) {
565 if ("default".equals(e.getKey())) {
566 Main.warn("setting requires layer identifier e.g. 'setting::my_setting {...}'");
567 continue;
568 }
569 Cascade c = e.getValue();
570 String type = c.get("type", null, String.class);
571 StyleSetting set = null;
572 if ("boolean".equals(type)) {
573 set = BooleanStyleSetting.create(c, this, e.getKey());
574 } else {
575 Main.warn("Unkown setting type: "+type);
576 }
577 if (set != null) {
578 settings.add(set);
579 settingValues.put(e.getKey(), set.getValue());
580 }
581 }
582 }
583
584 private Cascade constructSpecial(String type) {
585
586 MultiCascade mc = new MultiCascade();
587 Node n = new Node();
588 String code = LanguageInfo.getJOSMLocaleCode();
589 n.put("lang", code);
590 // create a fake environment to read the meta data block
591 Environment env = new Environment(n, mc, "default", this);
592
593 for (MapCSSRule r : rules) {
594 if (r.selector instanceof GeneralSelector) {
595 GeneralSelector gs = (GeneralSelector) r.selector;
596 if (gs.getBase().equals(type)) {
597 if (!gs.matchesConditions(env)) {
598 continue;
599 }
600 r.execute(env);
601 }
602 }
603 }
604 return mc.getCascade("default");
605 }
606
607 @Override
608 public Color getBackgroundColorOverride() {
609 return backgroundColorOverride;
610 }
611
612 @Override
613 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, boolean pretendWayIsClosed) {
614 Environment env = new Environment(osm, mc, null, this);
615 MapCSSRuleIndex matchingRuleIndex;
616 if (osm instanceof Node) {
617 matchingRuleIndex = nodeRules;
618 } else if (osm instanceof Way) {
619 if (osm.isKeyFalse("area")) {
620 matchingRuleIndex = wayNoAreaRules;
621 } else {
622 matchingRuleIndex = wayRules;
623 }
624 } else if (osm instanceof Relation) {
625 if (((Relation) osm).isMultipolygon()) {
626 matchingRuleIndex = multipolygonRules;
627 } else if (osm.hasKey("#canvas")) {
628 matchingRuleIndex = canvasRules;
629 } else {
630 matchingRuleIndex = relationRules;
631 }
632 } else {
633 throw new IllegalArgumentException("Unsupported type: " + osm);
634 }
635
636 // the declaration indices are sorted, so it suffices to save the last used index
637 int lastDeclUsed = -1;
638
639 Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(osm);
640 while (candidates.hasNext()) {
641 MapCSSRule r = candidates.next();
642 env.clearSelectorMatchingInformation();
643 env.layer = r.selector.getSubpart().getId(env);
644 String sub = env.layer;
645 if (r.selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
646 Selector s = r.selector;
647 if (s.getRange().contains(scale)) {
648 mc.range = Range.cut(mc.range, s.getRange());
649 } else {
650 mc.range = mc.range.reduceAround(scale, s.getRange());
651 continue;
652 }
653
654 if (r.declaration.idx == lastDeclUsed)
655 continue; // don't apply one declaration more than once
656 lastDeclUsed = r.declaration.idx;
657 if ("*".equals(sub)) {
658 for (Entry<String, Cascade> entry : mc.getLayers()) {
659 env.layer = entry.getKey();
660 if ("*".equals(env.layer)) {
661 continue;
662 }
663 r.execute(env);
664 }
665 }
666 env.layer = sub;
667 r.execute(env);
668 }
669 }
670 }
671
672 public boolean evalSupportsDeclCondition(String feature, Object val) {
673 if (feature == null) return false;
674 if (SUPPORTED_KEYS.contains(feature)) return true;
675 switch (feature) {
676 case "user-agent":
677 {
678 String s = Cascade.convertTo(val, String.class);
679 return "josm".equals(s);
680 }
681 case "min-josm-version":
682 {
683 Float v = Cascade.convertTo(val, Float.class);
684 return v != null && Math.round(v) <= Version.getInstance().getVersion();
685 }
686 case "max-josm-version":
687 {
688 Float v = Cascade.convertTo(val, Float.class);
689 return v != null && Math.round(v) >= Version.getInstance().getVersion();
690 }
691 default:
692 return false;
693 }
694 }
695
696 @Override
697 public String toString() {
698 return Utils.join("\n", rules);
699 }
700}
Note: See TracBrowser for help on using the repository browser.