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

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

sonar - squid:AssignmentInSubExpressionCheck - Assignments should not be made from within sub-expressions

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