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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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