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

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

fix squid:S1319 - Declarations should use Java collection interfaces rather than specific implementation classes

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