Ticket #18802: 18802.patch
File 18802.patch, 98.3 KB (added by , 5 years ago) |
---|
-
scripts/TagInfoExtract.java
diff --git a/scripts/TagInfoExtract.java b/scripts/TagInfoExtract.java index 6a18235bd..ca15cc9d2 100644
a b private void parseStyleSheet() throws IOException, ParseException { 342 342 */ 343 343 private List<TagInfoTag> convertStyleSheet() { 344 344 return styleSource.rules.stream() 345 .map(rule -> rule.selector) 346 .filter(Selector.GeneralSelector.class::isInstance) 347 .map(Selector.GeneralSelector.class::cast) 348 .map(Selector.AbstractSelector::getConditions) 349 .flatMap(Collection::stream) 345 .flatMap(rule -> rule.selectors.stream()) 346 .flatMap(selector -> selector.getConditions().stream()) 350 347 .filter(ConditionFactory.SimpleKeyValueCondition.class::isInstance) 351 348 .map(ConditionFactory.SimpleKeyValueCondition.class::cast) 352 349 .map(condition -> condition.asTag(null)) … … Environment applyStylesheet(OsmPrimitive osm) { 393 390 Environment env = new Environment(osm, mc, null, styleSource); 394 391 for (MapCSSRule r : styleSource.rules) { 395 392 env.clearSelectorMatchingInformation(); 396 if (r. selector.matches(env)) {393 if (r.matches(env)) { 397 394 // ignore selector range 398 395 if (env.layer == null) { 399 396 env.layer = "default"; -
src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java index 37e6134f7..8b81fe41f 100644
a b 9 9 import java.io.InputStream; 10 10 import java.io.Reader; 11 11 import java.io.StringReader; 12 import java.lang.reflect.Method;13 import java.text.MessageFormat;14 12 import java.util.ArrayList; 15 13 import java.util.Collection; 16 import java.util.Collections;17 14 import java.util.HashMap; 18 15 import java.util.HashSet; 19 16 import java.util.Iterator; 20 import java.util.LinkedHashMap;21 import java.util.LinkedHashSet;22 17 import java.util.LinkedList; 23 18 import java.util.List; 24 import java.util.Locale;25 19 import java.util.Map; 26 20 import java.util.Objects; 27 21 import java.util.Optional; 28 22 import java.util.Set; 23 import java.util.function.Consumer; 29 24 import java.util.function.Predicate; 30 25 import java.util.regex.Matcher; 31 26 import java.util.regex.Pattern; 27 import java.util.stream.Stream; 32 28 33 29 import org.openstreetmap.josm.command.ChangePropertyCommand; 34 30 import org.openstreetmap.josm.command.ChangePropertyKeyCommand; 35 31 import org.openstreetmap.josm.command.Command; 36 32 import org.openstreetmap.josm.command.DeleteCommand; 37 33 import org.openstreetmap.josm.command.SequenceCommand; 38 import org.openstreetmap.josm.data.coor.LatLon;39 import org.openstreetmap.josm.data.osm.DataSet;40 34 import org.openstreetmap.josm.data.osm.IPrimitive; 41 35 import org.openstreetmap.josm.data.osm.OsmPrimitive; 42 import org.openstreetmap.josm.data.osm.OsmUtils;43 import org.openstreetmap.josm.data.osm.Relation;44 36 import org.openstreetmap.josm.data.osm.Tag; 45 import org.openstreetmap.josm.data.osm.Way;46 37 import org.openstreetmap.josm.data.preferences.sources.SourceEntry; 47 38 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 48 39 import org.openstreetmap.josm.data.validation.OsmValidator; … … 53 44 import org.openstreetmap.josm.gui.mappaint.Keyword; 54 45 import org.openstreetmap.josm.gui.mappaint.MultiCascade; 55 46 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition; 56 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.ClassCondition;57 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.ExpressionCondition;58 47 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression; 59 import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.ParameterFunction;60 import org.openstreetmap.josm.gui.mappaint.mapcss.Functions;61 48 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction; 62 import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;63 49 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule; 64 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSS Rule.Declaration;50 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleIndex; 65 51 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 66 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex;67 52 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector; 68 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.AbstractSelector;69 53 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector; 70 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;71 54 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser; 72 55 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 73 56 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError; … … 78 61 import org.openstreetmap.josm.io.UTFInputStreamReader; 79 62 import org.openstreetmap.josm.spi.preferences.Config; 80 63 import org.openstreetmap.josm.tools.CheckParameterUtil; 81 import org.openstreetmap.josm.tools.DefaultGeoProperty;82 import org.openstreetmap.josm.tools.GeoProperty;83 import org.openstreetmap.josm.tools.GeoPropertyIndex;84 64 import org.openstreetmap.josm.tools.I18n; 85 65 import org.openstreetmap.josm.tools.Logging; 86 66 import org.openstreetmap.josm.tools.MultiMap; 87 import org.openstreetmap.josm.tools.Territories;88 67 import org.openstreetmap.josm.tools.Utils; 89 68 90 69 /** … … 92 71 * @since 6506 93 72 */ 94 73 public class MapCSSTagChecker extends Test.TagTest { 95 MapCSSTagCheckerIndex indexData; 74 MapCSSStyleIndex indexData; 75 final Map<MapCSSRule, TagCheck> ruleToCheckMap = new HashMap<>(); 96 76 final Set<OsmPrimitive> tested = new HashSet<>(); 97 98 /** 99 * A grouped MapCSSRule with multiple selectors for a single declaration. 100 * @see MapCSSRule 101 */ 102 public static class GroupedMapCSSRule { 103 /** MapCSS selectors **/ 104 public final List<Selector> selectors; 105 /** MapCSS declaration **/ 106 public final Declaration declaration; 107 108 /** 109 * Constructs a new {@code GroupedMapCSSRule}. 110 * @param selectors MapCSS selectors 111 * @param declaration MapCSS declaration 112 */ 113 public GroupedMapCSSRule(List<Selector> selectors, Declaration declaration) { 114 this.selectors = selectors; 115 this.declaration = declaration; 116 } 117 118 @Override 119 public int hashCode() { 120 return Objects.hash(selectors, declaration); 121 } 122 123 @Override 124 public boolean equals(Object obj) { 125 if (this == obj) return true; 126 if (obj == null || getClass() != obj.getClass()) return false; 127 GroupedMapCSSRule that = (GroupedMapCSSRule) obj; 128 return Objects.equals(selectors, that.selectors) && 129 Objects.equals(declaration, that.declaration); 130 } 131 132 @Override 133 public String toString() { 134 return "GroupedMapCSSRule [selectors=" + selectors + ", declaration=" + declaration + ']'; 135 } 136 } 77 static final boolean ALL_TESTS = true; 78 static final boolean ONLY_SELECTED_TESTS = false; 137 79 138 80 /** 139 81 * The preference key for tag checker source entries. … … public ParseResult(List<TagCheck> parseChecks, Collection<Throwable> parseErrors 284 226 */ 285 227 public static class TagCheck implements Predicate<OsmPrimitive> { 286 228 /** The selector of this {@code TagCheck} */ 287 protected final GroupedMapCSSRule rule;229 protected final MapCSSRule rule; 288 230 /** Commands to apply in order to fix a matching primitive */ 289 protected final List<FixCommand> fixCommands = new ArrayList<>();231 protected final List<FixCommand> fixCommands; 290 232 /** Tags (or arbitrary strings) of alternatives to be presented to the user */ 291 protected final List<String> alternatives = new ArrayList<>();233 protected final List<String> alternatives; 292 234 /** An {@link org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.AssignmentInstruction}-{@link Severity} pair. 293 235 * Is evaluated on the matching primitive to give the error message. Map is checked to contain exactly one element. */ 294 protected final Map<Instruction.AssignmentInstruction, Severity> errors = new HashMap<>(); 295 /** Unit tests */ 296 protected final Map<String, Boolean> assertions = new HashMap<>(); 236 protected final Map<Instruction.AssignmentInstruction, Severity> errors; 297 237 /** MapCSS Classes to set on matching primitives */ 298 protected final Set<String> setClassExpressions = new HashSet<>();238 protected final Collection<String> setClassExpressions; 299 239 /** Denotes whether the object should be deleted for fixing it */ 300 240 protected boolean deletion; 301 241 /** A string used to group similar tests */ 302 242 protected String group; 303 243 304 TagCheck( GroupedMapCSSRule rule) {244 TagCheck(MapCSSRule rule) { 305 245 this.rule = rule; 246 this.fixCommands = new ArrayList<>(); 247 this.alternatives = new ArrayList<>(); 248 this.errors = new HashMap<>(); 249 this.setClassExpressions = new HashSet<>(); 306 250 } 307 251 308 private static final String POSSIBLE_THROWS = possibleThrows(); 252 TagCheck(TagCheck check) { 253 this.rule = check.rule; 254 this.fixCommands = Utils.toUnmodifiableList(check.fixCommands); 255 this.alternatives = Utils.toUnmodifiableList(check.alternatives); 256 this.errors = Utils.toUnmodifiableMap(check.errors); 257 this.setClassExpressions = Utils.toUnmodifiableList(check.setClassExpressions); 258 this.deletion = check.deletion; 259 this.group = check.group; 260 } 309 261 310 static final String possibleThrows() { 311 StringBuilder sb = new StringBuilder(); 312 for (Severity s : Severity.values()) { 313 if (sb.length() > 0) { 314 sb.append('/'); 315 } 316 sb.append("throw") 317 .append(s.name().charAt(0)) 318 .append(s.name().substring(1).toLowerCase(Locale.ENGLISH)); 319 } 320 return sb.toString(); 262 TagCheck toImmutable() { 263 return new TagCheck(this); 321 264 } 322 265 323 static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataException { 266 private static final String POSSIBLE_THROWS = "throwError/throwWarning/throwOther"; 267 268 static TagCheck ofMapCSSRule(final MapCSSRule rule, AssertionConsumer assertionConsumer) throws IllegalDataException { 324 269 final TagCheck check = new TagCheck(rule); 270 final Map<String, Boolean> assertions = new HashMap<>(); 325 271 for (Instruction i : rule.declaration.instructions) { 326 272 if (i instanceof Instruction.AssignmentInstruction) { 327 273 final Instruction.AssignmentInstruction ai = (Instruction.AssignmentInstruction) i; … … static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 338 284 : ai.val instanceof Keyword 339 285 ? ((Keyword) ai.val).val 340 286 : null; 341 if (ai.key.startsWith("throw")) { 342 try { 343 check.errors.put(ai, Severity.valueOf(ai.key.substring("throw".length()).toUpperCase(Locale.ENGLISH))); 344 } catch (IllegalArgumentException e) { 345 Logging.log(Logging.LEVEL_WARN, 346 "Unsupported "+ai.key+" instruction. Allowed instructions are "+POSSIBLE_THROWS+'.', e); 347 } 287 if ("throwError".equals(ai.key)) { 288 check.errors.put(ai, Severity.ERROR); 289 } else if ("throwWarning".equals(ai.key)) { 290 check.errors.put(ai, Severity.WARNING); 291 } else if ("throwOther".equals(ai.key)) { 292 check.errors.put(ai, Severity.OTHER); 293 } else if (ai.key.startsWith("throw")) { 294 Logging.log(Logging.LEVEL_WARN, 295 "Unsupported " + ai.key + " instruction. Allowed instructions are " + POSSIBLE_THROWS + '.', null); 348 296 } else if ("fixAdd".equals(ai.key)) { 349 297 check.fixCommands.add(FixCommand.fixAdd(ai.val)); 350 298 } else if ("fixRemove".equals(ai.key)) { … … static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 361 309 } else if (val != null && "suggestAlternative".equals(ai.key)) { 362 310 check.alternatives.add(val); 363 311 } else if (val != null && "assertMatch".equals(ai.key)) { 364 check.assertions.put(val, Boolean.TRUE);312 assertions.put(val, Boolean.TRUE); 365 313 } else if (val != null && "assertNoMatch".equals(ai.key)) { 366 check.assertions.put(val, Boolean.FALSE);314 assertions.put(val, Boolean.FALSE); 367 315 } else if (val != null && "group".equals(ai.key)) { 368 316 check.group = val; 369 317 } else if (ai.key.startsWith("-")) { … … static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 384 332 "More than one "+POSSIBLE_THROWS+" given! You should specify a single validation error message for " 385 333 + rule.selectors); 386 334 } 387 return check; 335 if (assertionConsumer != null) { 336 MapCSSTagCheckerAsserts.checkAsserts(check, assertions, assertionConsumer); 337 } 338 return check.toImmutable(); 388 339 } 389 340 390 static ParseResult readMapCSS(Reader css ) throws ParseException {341 static ParseResult readMapCSS(Reader css, AssertionConsumer assertionConsumer) throws ParseException { 391 342 CheckParameterUtil.ensureParameterNotNull(css, "css"); 392 343 393 344 final MapCSSStyleSource source = new MapCSSStyleSource(""); … … static ParseResult readMapCSS(Reader css) throws ParseException { 397 348 } 398 349 // Ignore "meta" rule(s) from external rules of JOSM wiki 399 350 source.removeMetaRules(); 400 // group rules with common declaration block401 Map<Declaration, List<Selector>> g = new LinkedHashMap<>();402 for (MapCSSRule rule : source.rules) {403 if (!g.containsKey(rule.declaration)) {404 List<Selector> sels = new ArrayList<>();405 sels.add(rule.selector);406 g.put(rule.declaration, sels);407 } else {408 g.get(rule.declaration).add(rule.selector);409 }410 }411 351 List<TagCheck> parseChecks = new ArrayList<>(); 412 for (Map .Entry<Declaration, List<Selector>> map : g.entrySet()) {352 for (MapCSSRule rule : source.rules) { 413 353 try { 414 parseChecks.add(TagCheck.ofMapCSSRule( 415 new GroupedMapCSSRule(map.getValue(), map.getKey()))); 354 parseChecks.add(TagCheck.ofMapCSSRule(rule, assertionConsumer)); 416 355 } catch (IllegalDataException e) { 417 356 Logging.error("Cannot add MapCss rule: "+e.getMessage()); 418 357 source.logError(e); … … Selector whichSelectorMatchesEnvironment(Environment env) { 443 382 444 383 /** 445 384 * Determines the {@code index}-th key/value/tag (depending on {@code type}) of the 446 * {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector}.385 * {@link GeneralSelector}. 447 386 * @param matchingSelector matching selector 448 387 * @param index index 449 388 * @param type selector type ("key", "value" or "tag") 450 389 * @param p OSM primitive 451 390 * @return argument value, can be {@code null} 452 391 */ 453 static String determineArgument( OptimizedGeneralSelector matchingSelector, int index, String type, OsmPrimitive p) {392 static String determineArgument(GeneralSelector matchingSelector, int index, String type, OsmPrimitive p) { 454 393 try { 455 394 final Condition c = matchingSelector.getConditions().get(index); 456 395 final Tag tag = c instanceof Condition.ToTagConvertable … … static String determineArgument(OptimizedGeneralSelector matchingSelector, int i 482 421 static String insertArguments(Selector matchingSelector, String s, OsmPrimitive p) { 483 422 if (s != null && matchingSelector instanceof Selector.ChildOrParentSelector) { 484 423 return insertArguments(((Selector.ChildOrParentSelector) matchingSelector).right, s, p); 485 } else if (s == null || !(matchingSelector instanceof Selector.OptimizedGeneralSelector)) {424 } else if (s == null || !(matchingSelector instanceof GeneralSelector)) { 486 425 return s; 487 426 } 488 427 final Matcher m = Pattern.compile("\\{(\\d+)\\.(key|value|tag)\\}").matcher(s); 489 428 final StringBuffer sb = new StringBuffer(); 490 429 while (m.find()) { 491 final String argument = determineArgument(( Selector.OptimizedGeneralSelector) matchingSelector,430 final String argument = determineArgument((GeneralSelector) matchingSelector, 492 431 Integer.parseInt(m.group(1)), m.group(2), p); 493 432 try { 494 433 // Perform replacement with null-safe + regex-safe handling … … public String toString() { 635 574 return res; 636 575 } 637 576 638 /**639 * Returns the set of tagchecks on which this check depends on.640 * @param schecks the collection of tagcheks to search in641 * @return the set of tagchecks on which this check depends on642 * @since 7881643 */644 public Set<TagCheck> getTagCheckDependencies(Collection<TagCheck> schecks) {645 Set<TagCheck> result = new HashSet<>();646 Set<String> classes = getClassesIds();647 if (schecks != null && !classes.isEmpty()) {648 for (TagCheck tc : schecks) {649 if (this.equals(tc)) {650 continue;651 }652 for (String id : tc.setClassExpressions) {653 if (classes.contains(id)) {654 result.add(tc);655 break;656 }657 }658 }659 }660 return result;661 }662 663 /**664 * Returns the list of ids of all MapCSS classes referenced in the rule selectors.665 * @return the list of ids of all MapCSS classes referenced in the rule selectors666 * @since 7881667 */668 public Set<String> getClassesIds() {669 Set<String> result = new HashSet<>();670 for (Selector s : rule.selectors) {671 if (s instanceof AbstractSelector) {672 for (Condition c : ((AbstractSelector) s).getConditions()) {673 if (c instanceof ClassCondition) {674 result.add(((ClassCondition) c).id);675 }676 }677 }678 }679 return result;680 }681 577 } 682 578 683 579 static class MapCSSTagCheckerAndRule extends MapCSSTagChecker { 684 public final GroupedMapCSSRule rule;580 public final MapCSSRule rule; 685 581 686 MapCSSTagCheckerAndRule( GroupedMapCSSRule rule) {582 MapCSSTagCheckerAndRule(MapCSSRule rule) { 687 583 this.rule = rule; 688 584 } 689 585 … … public String toString() { 693 589 } 694 590 } 695 591 592 static MapCSSStyleIndex createMapCSSTagCheckerIndex(MultiMap<String, TagCheck> checks, boolean includeOtherSeverity, boolean allTests) { 593 final MapCSSStyleIndex index = new MapCSSStyleIndex(); 594 final Stream<MapCSSRule> ruleStream = checks.values().stream() 595 .flatMap(Collection::stream) 596 // Ignore "information" level checks if not wanted, unless they also set a MapCSS class 597 .filter(c -> includeOtherSeverity || Severity.OTHER != c.getSeverity() || !c.setClassExpressions.isEmpty()) 598 .filter(c -> allTests || c.rule.selectors.stream().anyMatch(Selector.ChildOrParentSelector.class::isInstance)) 599 .map(c -> c.rule); 600 index.buildIndex(ruleStream); 601 return index; 602 } 603 696 604 /** 697 605 * Obtains all {@link TestError}s for the {@link OsmPrimitive} {@code p}. 698 606 * @param p The OSM primitive … … public String toString() { 702 610 public synchronized Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity) { 703 611 final List<TestError> res = new ArrayList<>(); 704 612 if (indexData == null) { 705 indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverity, MapCSSTagCheckerIndex.ALL_TESTS);613 indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverity, ALL_TESTS); 706 614 } 707 615 708 MapCSSRuleIndex matchingRuleIndex = indexData.get(p);709 616 710 617 Environment env = new Environment(p, new MultiCascade(), Environment.DEFAULT_LAYER, null); 711 // the declaration indices are sorted, so it suffices to save the last used index712 Declaration lastDeclUsed = null;713 618 714 Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(p);619 Iterator<MapCSSRule> candidates = indexData.getRuleCandidates(p); 715 620 while (candidates.hasNext()) { 716 621 MapCSSRule r = candidates.next(); 717 env.clearSelectorMatchingInformation(); 718 if (r.selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector) 719 TagCheck check = indexData.getCheck(r); 622 for (Selector selector : r.selectors) { 623 env.clearSelectorMatchingInformation(); 624 if (!selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector) 625 continue; 626 } 627 TagCheck check = ruleToCheckMap.computeIfAbsent(r, rule -> checks.values().stream() 628 .flatMap(Collection::stream) 629 .filter(c -> Objects.equals(rule, c.rule)) 630 .findAny() 631 .orElse(null)); 720 632 if (check != null) { 721 if (r.declaration == lastDeclUsed)722 continue; // don't apply one declaration more than once723 lastDeclUsed = r.declaration;724 725 633 r.declaration.execute(env); 726 634 if (!check.errors.isEmpty()) { 727 for (TestError e: check.getErrorsForPrimitive(p, r.selector, env, new MapCSSTagCheckerAndRule(check.rule))) {635 for (TestError e: check.getErrorsForPrimitive(p, selector, env, new MapCSSTagCheckerAndRule(check.rule))) { 728 636 addIfNotSimilar(e, res); 729 637 } 730 638 } … … private static boolean highlightedIsEqual(Collection<?> highlighted, Collection< 774 682 return false; 775 683 } 776 684 777 privatestatic Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,685 static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity, 778 686 Collection<Set<TagCheck>> checksCol) { 779 687 final List<TestError> r = new ArrayList<>(); 780 688 final Environment env = new Environment(p, new MultiCascade(), Environment.DEFAULT_LAYER, null); … … public void check(OsmPrimitive p) { 812 720 } 813 721 } 814 722 723 /** 724 * A handler for assertion error messages (for not fulfilled "assertMatch", "assertNoMatch"). 725 */ 726 @FunctionalInterface 727 interface AssertionConsumer extends Consumer<String> { 728 } 729 815 730 /** 816 731 * Adds a new MapCSS config file from the given URL. 817 732 * @param url The unique URL of the MapCSS config file … … public void check(OsmPrimitive p) { 821 736 * @since 7275 822 737 */ 823 738 public synchronized ParseResult addMapCSS(String url) throws ParseException, IOException { 739 // Check assertions, useful for development of local files 740 final boolean checkAssertions = Config.getPref().getBoolean("validator.check_assert_local_rules", false) && Utils.isLocalUrl(url); 741 return addMapCSS(url, checkAssertions ? Logging::warn : null); 742 } 743 744 synchronized ParseResult addMapCSS(String url, AssertionConsumer assertionConsumer) throws ParseException, IOException { 824 745 CheckParameterUtil.ensureParameterNotNull(url, "url"); 825 746 ParseResult result; 826 747 try (CachedFile cache = new CachedFile(url); … … public synchronized ParseResult addMapCSS(String url) throws ParseException, IOE 829 750 Reader reader = new BufferedReader(UTFInputStreamReader.create(s))) { 830 751 if (zip != null) 831 752 I18n.addTexts(cache.getFile()); 832 result = TagCheck.readMapCSS(reader );753 result = TagCheck.readMapCSS(reader, assertionConsumer); 833 754 checks.remove(url); 834 755 checks.putAll(url, result.parseChecks); 835 756 indexData = null; 836 // Check assertions, useful for development of local files837 if (Config.getPref().getBoolean("validator.check_assert_local_rules", false) && Utils.isLocalUrl(url)) {838 for (String msg : checkAsserts(result.parseChecks)) {839 Logging.warn(msg);840 }841 }842 757 } 843 758 return result; 844 759 } … … public synchronized void initialize() throws Exception { 870 785 Logging.warn(ex); 871 786 } 872 787 } 873 } 874 875 private static Method getFunctionMethod(String method) { 876 try { 877 return Functions.class.getDeclaredMethod(method, Environment.class, String.class); 878 } catch (NoSuchMethodException | SecurityException e) { 879 Logging.error(e); 880 return null; 881 } 882 } 883 884 private static Optional<String> getFirstInsideCountry(TagCheck check, Method insideMethod) { 885 return check.rule.selectors.stream() 886 .filter(s -> s instanceof GeneralSelector) 887 .flatMap(s -> ((GeneralSelector) s).getConditions().stream()) 888 .filter(c -> c instanceof ExpressionCondition) 889 .map(c -> ((ExpressionCondition) c).getExpression()) 890 .filter(c -> c instanceof ParameterFunction) 891 .map(c -> (ParameterFunction) c) 892 .filter(c -> c.getMethod().equals(insideMethod)) 893 .flatMap(c -> c.getArgs().stream()) 894 .filter(e -> e instanceof LiteralExpression) 895 .map(e -> ((LiteralExpression) e).getLiteral()) 896 .filter(l -> l instanceof String) 897 .map(l -> ((String) l).split(",")[0]) 898 .findFirst(); 899 } 900 901 private static LatLon getLocation(TagCheck check, Method insideMethod) { 902 Optional<String> inside = getFirstInsideCountry(check, insideMethod); 903 if (inside.isPresent()) { 904 GeoPropertyIndex<Boolean> index = Territories.getGeoPropertyIndex(inside.get()); 905 if (index != null) { 906 GeoProperty<Boolean> prop = index.getGeoProperty(); 907 if (prop instanceof DefaultGeoProperty) { 908 return ((DefaultGeoProperty) prop).getRandomLatLon(); 909 } 910 } 911 } 912 return LatLon.ZERO; 913 } 914 915 /** 916 * Checks that rule assertions are met for the given set of TagChecks. 917 * @param schecks The TagChecks for which assertions have to be checked 918 * @return A set of error messages, empty if all assertions are met 919 * @since 7356 920 */ 921 public Set<String> checkAsserts(final Collection<TagCheck> schecks) { 922 Set<String> assertionErrors = new LinkedHashSet<>(); 923 final Method insideMethod = getFunctionMethod("inside"); 924 final DataSet ds = new DataSet(); 925 for (final TagCheck check : schecks) { 926 Logging.debug("Check: {0}", check); 927 for (final Map.Entry<String, Boolean> i : check.assertions.entrySet()) { 928 Logging.debug("- Assertion: {0}", i); 929 final OsmPrimitive p = OsmUtils.createPrimitive(i.getKey(), getLocation(check, insideMethod), true); 930 // Build minimal ordered list of checks to run to test the assertion 931 List<Set<TagCheck>> checksToRun = new ArrayList<>(); 932 Set<TagCheck> checkDependencies = check.getTagCheckDependencies(schecks); 933 if (!checkDependencies.isEmpty()) { 934 checksToRun.add(checkDependencies); 935 } 936 checksToRun.add(Collections.singleton(check)); 937 // Add primitive to dataset to avoid DataIntegrityProblemException when evaluating selectors 938 addPrimitive(ds, p); 939 final Collection<TestError> pErrors = getErrorsForPrimitive(p, true, checksToRun); 940 Logging.debug("- Errors: {0}", pErrors); 941 final boolean isError = pErrors.stream().anyMatch(e -> e.getTester() instanceof MapCSSTagCheckerAndRule 942 && ((MapCSSTagCheckerAndRule) e.getTester()).rule.equals(check.rule)); 943 if (isError != i.getValue()) { 944 assertionErrors.add(MessageFormat.format("Expecting test ''{0}'' (i.e., {1}) to {2} {3} (i.e., {4})", 945 check.getMessage(p), check.rule.selectors, i.getValue() ? "match" : "not match", i.getKey(), p.getKeys())); 946 } 947 if (isError) { 948 // Check that autofix works as expected 949 Command fix = check.fixPrimitive(p); 950 if (fix != null && fix.executeCommand() && !getErrorsForPrimitive(p, true, checksToRun).isEmpty()) { 951 assertionErrors.add(MessageFormat.format("Autofix does not work for test ''{0}'' (i.e., {1})", 952 check.getMessage(p), check.rule.selectors)); 953 } 954 } 955 ds.removePrimitive(p); 956 } 957 } 958 return assertionErrors; 959 } 960 961 private static void addPrimitive(DataSet ds, OsmPrimitive p) { 962 if (p instanceof Way) { 963 ((Way) p).getNodes().forEach(n -> addPrimitive(ds, n)); 964 } else if (p instanceof Relation) { 965 ((Relation) p).getMembers().forEach(m -> addPrimitive(ds, m.getMember())); 966 } 967 ds.addPrimitive(p); 788 MapCSSTagCheckerAsserts.clear(); 968 789 } 969 790 970 791 /** … … public synchronized void startTest(ProgressMonitor progressMonitor) { 988 809 super.startTest(progressMonitor); 989 810 super.setShowElements(true); 990 811 if (indexData == null) { 991 indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), MapCSSTagCheckerIndex.ALL_TESTS);812 indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ALL_TESTS); 992 813 } 993 814 tested.clear(); 994 815 } … … public synchronized void endTest() { 1001 822 1002 823 // rebuild index with a reduced set of rules (those that use ChildOrParentSelector) and thus may have left selectors 1003 824 // matching the previously tested elements 1004 indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), MapCSSTagCheckerIndex.ONLY_SELECTED_TESTS);825 indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ONLY_SELECTED_TESTS); 1005 826 1006 827 Set<OsmPrimitive> surrounding = new HashSet<>(); 1007 828 for (OsmPrimitive p : tested) { -
new file src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java new file mode 100644 index 000000000..796cf5404
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import java.lang.reflect.Method; 5 import java.text.MessageFormat; 6 import java.util.ArrayList; 7 import java.util.Collection; 8 import java.util.Collections; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Map; 12 import java.util.Optional; 13 import java.util.Set; 14 import java.util.stream.Collectors; 15 16 import org.openstreetmap.josm.command.Command; 17 import org.openstreetmap.josm.data.coor.LatLon; 18 import org.openstreetmap.josm.data.osm.DataSet; 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 20 import org.openstreetmap.josm.data.osm.OsmUtils; 21 import org.openstreetmap.josm.data.osm.Relation; 22 import org.openstreetmap.josm.data.osm.Way; 23 import org.openstreetmap.josm.data.validation.TestError; 24 import org.openstreetmap.josm.gui.mappaint.Environment; 25 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory; 26 import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory; 27 import org.openstreetmap.josm.gui.mappaint.mapcss.Functions; 28 import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression; 29 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector; 30 import org.openstreetmap.josm.tools.DefaultGeoProperty; 31 import org.openstreetmap.josm.tools.GeoProperty; 32 import org.openstreetmap.josm.tools.GeoPropertyIndex; 33 import org.openstreetmap.josm.tools.Logging; 34 import org.openstreetmap.josm.tools.Territories; 35 36 /** 37 * Utility class for checking rule assertions of {@link MapCSSTagChecker.TagCheck}. 38 */ 39 final class MapCSSTagCheckerAsserts { 40 41 private MapCSSTagCheckerAsserts() { 42 // private constructor 43 } 44 45 private static final ArrayList<MapCSSTagChecker.TagCheck> previousChecks = new ArrayList<>(); 46 47 /** 48 * Checks that rule assertions are met for the given set of TagChecks. 49 * @param check The TagCheck for which assertions have to be checked 50 * @param assertionConsumer The handler for assertion error messages 51 */ 52 static void checkAsserts(final MapCSSTagChecker.TagCheck check, final Map<String, Boolean> assertions, 53 final MapCSSTagChecker.AssertionConsumer assertionConsumer) { 54 final Method insideMethod = getFunctionMethod("inside"); 55 final DataSet ds = new DataSet(); 56 Logging.debug("Check: {0}", check); 57 for (final Map.Entry<String, Boolean> i : assertions.entrySet()) { 58 Logging.debug("- Assertion: {0}", i); 59 final OsmPrimitive p = OsmUtils.createPrimitive(i.getKey(), getLocation(check, insideMethod), true); 60 // Build minimal ordered list of checks to run to test the assertion 61 List<Set<MapCSSTagChecker.TagCheck>> checksToRun = new ArrayList<>(); 62 Set<MapCSSTagChecker.TagCheck> checkDependencies = getTagCheckDependencies(check, previousChecks); 63 if (!checkDependencies.isEmpty()) { 64 checksToRun.add(checkDependencies); 65 } 66 checksToRun.add(Collections.singleton(check)); 67 // Add primitive to dataset to avoid DataIntegrityProblemException when evaluating selectors 68 addPrimitive(ds, p); 69 final Collection<TestError> pErrors = MapCSSTagChecker.getErrorsForPrimitive(p, true, checksToRun); 70 Logging.debug("- Errors: {0}", pErrors); 71 final boolean isError = pErrors.stream().anyMatch(e -> e.getTester() instanceof MapCSSTagChecker.MapCSSTagCheckerAndRule 72 && ((MapCSSTagChecker.MapCSSTagCheckerAndRule) e.getTester()).rule.equals(check.rule)); 73 if (isError != i.getValue()) { 74 assertionConsumer.accept(MessageFormat.format("Expecting test ''{0}'' (i.e., {1}) to {2} {3} (i.e., {4})", 75 check.getMessage(p), check.rule.selectors, i.getValue() ? "match" : "not match", i.getKey(), p.getKeys())); 76 } 77 if (isError) { 78 // Check that autofix works as expected 79 Command fix = check.fixPrimitive(p); 80 if (fix != null && fix.executeCommand() && !MapCSSTagChecker.getErrorsForPrimitive(p, true, checksToRun).isEmpty()) { 81 assertionConsumer.accept(MessageFormat.format("Autofix does not work for test ''{0}'' (i.e., {1})", 82 check.getMessage(p), check.rule.selectors)); 83 } 84 } 85 ds.removePrimitive(p); 86 } 87 previousChecks.add(check); 88 } 89 90 public static void clear() { 91 previousChecks.clear(); 92 previousChecks.trimToSize(); 93 } 94 95 private static Method getFunctionMethod(String method) { 96 try { 97 return Functions.class.getDeclaredMethod(method, Environment.class, String.class); 98 } catch (NoSuchMethodException | SecurityException e) { 99 Logging.error(e); 100 return null; 101 } 102 } 103 104 private static void addPrimitive(DataSet ds, OsmPrimitive p) { 105 if (p instanceof Way) { 106 ((Way) p).getNodes().forEach(n -> addPrimitive(ds, n)); 107 } else if (p instanceof Relation) { 108 ((Relation) p).getMembers().forEach(m -> addPrimitive(ds, m.getMember())); 109 } 110 ds.addPrimitive(p); 111 } 112 113 private static LatLon getLocation(MapCSSTagChecker.TagCheck check, Method insideMethod) { 114 Optional<String> inside = getFirstInsideCountry(check, insideMethod); 115 if (inside.isPresent()) { 116 GeoPropertyIndex<Boolean> index = Territories.getGeoPropertyIndex(inside.get()); 117 if (index != null) { 118 GeoProperty<Boolean> prop = index.getGeoProperty(); 119 if (prop instanceof DefaultGeoProperty) { 120 return ((DefaultGeoProperty) prop).getRandomLatLon(); 121 } 122 } 123 } 124 return LatLon.ZERO; 125 } 126 127 private static Optional<String> getFirstInsideCountry(MapCSSTagChecker.TagCheck check, Method insideMethod) { 128 return check.rule.selectors.stream() 129 .filter(s -> s instanceof Selector.GeneralSelector) 130 .flatMap(s -> ((Selector.GeneralSelector) s).getConditions().stream()) 131 .filter(c -> c instanceof ConditionFactory.ExpressionCondition) 132 .map(c -> ((ConditionFactory.ExpressionCondition) c).getExpression()) 133 .filter(c -> c instanceof ExpressionFactory.ParameterFunction) 134 .map(c -> (ExpressionFactory.ParameterFunction) c) 135 .filter(c -> c.getMethod().equals(insideMethod)) 136 .flatMap(c -> c.getArgs().stream()) 137 .filter(e -> e instanceof LiteralExpression) 138 .map(e -> ((LiteralExpression) e).getLiteral()) 139 .filter(l -> l instanceof String) 140 .map(l -> ((String) l).split(",")[0]) 141 .findFirst(); 142 } 143 144 /** 145 * Returns the set of tagchecks on which this check depends on. 146 * @param check the tagcheck 147 * @param schecks the collection of tagcheks to search in 148 * @return the set of tagchecks on which this check depends on 149 * @since 7881 150 */ 151 private static Set<MapCSSTagChecker.TagCheck> getTagCheckDependencies(MapCSSTagChecker.TagCheck check, Collection<MapCSSTagChecker.TagCheck> schecks) { 152 Set<MapCSSTagChecker.TagCheck> result = new HashSet<>(); 153 Set<String> classes = check.rule.selectors.stream() 154 .filter(s -> s instanceof Selector.AbstractSelector) 155 .flatMap(s -> ((Selector.AbstractSelector) s).getConditions().stream()) 156 .filter(c -> c instanceof ConditionFactory.ClassCondition) 157 .map(c -> ((ConditionFactory.ClassCondition) c).id) 158 .collect(Collectors.toSet()); 159 if (schecks != null && !classes.isEmpty()) { 160 return schecks.stream() 161 .filter(tc -> !check.equals(tc)) 162 .filter(tc -> tc.setClassExpressions.stream().anyMatch(classes::contains)) 163 .collect(Collectors.toSet()); 164 } 165 return result; 166 } 167 } -
src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java index 059f9ac14..129ab8265 100644
a b public String toString() { 359 359 * @throws PatternSyntaxException if the value syntax is invalid 360 360 */ 361 361 public KeyValueRegexpCondition(String k, String v, Op op, boolean considerValAsKey) { 362 super(k, v, op, considerValAsKey);362 super(k, "" /* v is not needed */, op, considerValAsKey); 363 363 CheckParameterUtil.ensureThat(!considerValAsKey, "considerValAsKey is not supported"); 364 364 CheckParameterUtil.ensureThat(SUPPORTED_OPS.contains(op), "Op must be REGEX or NREGEX"); 365 365 this.pattern = Pattern.compile(v); -
new file src/org/openstreetmap/josm/gui/mappaint/mapcss/Declaration.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Declaration.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Declaration.java new file mode 100644 index 000000000..9adb581d2
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.mappaint.mapcss; 3 4 import java.util.List; 5 import java.util.Objects; 6 7 import org.openstreetmap.josm.gui.mappaint.Environment; 8 import org.openstreetmap.josm.gui.mappaint.StyleSource; 9 import org.openstreetmap.josm.tools.Utils; 10 11 /** 12 * A declaration is a list of {@link Instruction}s 13 */ 14 public class Declaration { 15 /** 16 * The instructions in this declaration 17 */ 18 public final List<Instruction> instructions; 19 /** 20 * The index of this declaration 21 * <p> 22 * declarations in the StyleSource are numbered consecutively 23 */ 24 public final int idx; 25 26 /** 27 * Create a new {@link Declaration} 28 * @param instructions The instructions for this declaration 29 * @param idx The index in the {@link StyleSource} 30 */ 31 public Declaration(List<Instruction> instructions, int idx) { 32 this.instructions = Utils.toUnmodifiableList(instructions); 33 this.idx = idx; 34 } 35 36 /** 37 * <p>Executes the instructions against the environment {@code env}</p> 38 * 39 * @param env the environment 40 */ 41 public void execute(Environment env) { 42 for (Instruction i : instructions) { 43 i.execute(env); 44 } 45 } 46 47 @Override 48 public int hashCode() { 49 return Objects.hash(instructions, idx); 50 } 51 52 @Override 53 public boolean equals(Object obj) { 54 if (this == obj) return true; 55 if (obj == null || getClass() != obj.getClass()) return false; 56 Declaration that = (Declaration) obj; 57 return idx == that.idx && 58 Objects.equals(instructions, that.instructions); 59 } 60 61 @Override 62 public String toString() { 63 return "Declaration [instructions=" + instructions + ", idx=" + idx + ']'; 64 } 65 } -
src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java index 0647da838..616d07246 100644
a b 15 15 * 16 16 * For example a simple assignment like <code>width: 3;</code>, but may also 17 17 * be a set instruction (<code>set highway;</code>). 18 * A MapCSS {@link MapCSSRule.Declaration} is a list of instructions.18 * A MapCSS {@link Declaration} is a list of instructions. 19 19 */ 20 20 @FunctionalInterface 21 21 public interface Instruction extends StyleKeys { -
src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj b/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj index 2451a1603..c707fce2c 100644
a b import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context; 25 25 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory; 26 26 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType; 27 27 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.Op; 28 import org.openstreetmap.josm.gui.mappaint.mapcss.Declaration; 28 29 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression; 29 30 import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory; 30 31 import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.NullExpression; … … import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction; 32 33 import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression; 33 34 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException; 34 35 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule; 35 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule.Declaration;36 36 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 37 37 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector; 38 38 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector; … … void rule(): 649 649 { 650 650 selectors=selectors() 651 651 decl=declaration() 652 { 653 for (Selector s : selectors) { 654 sheet.rules.add(new MapCSSRule(s, decl)); 655 } 652 { 653 sheet.rules.add(new MapCSSRule(selectors, decl)); 656 654 } 657 655 } 658 656 -
src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSRule.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSRule.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSRule.java index 5a4b503e0..bdb81cba1 100644
a b 2 2 package org.openstreetmap.josm.gui.mappaint.mapcss; 3 3 4 4 import java.util.List; 5 import java.util.Objects;6 5 import java.util.stream.Collectors; 7 6 8 7 import org.openstreetmap.josm.gui.mappaint.Environment; 9 import org.openstreetmap.josm.gui.mappaint.StyleSource;10 8 import org.openstreetmap.josm.tools.Utils; 11 9 12 10 /** … … 21 19 /** 22 20 * The selector. If it matches, this rule should be applied 23 21 */ 24 public final Selector selector;22 public final List<Selector> selectors; 25 23 /** 26 24 * The instructions for this selector 27 25 */ 28 26 public final Declaration declaration; 29 27 30 28 /** 31 * A declaration is a set of {@link Instruction}s 29 * Constructs a new {@code MapCSSRule}. 30 * @param selectors The selectors 31 * @param declaration The declaration 32 32 */ 33 public static class Declaration { 34 /** 35 * The instructions in this declaration 36 */ 37 public final List<Instruction> instructions; 38 /** 39 * The index of this declaration 40 * <p> 41 * declarations in the StyleSource are numbered consecutively 42 */ 43 public final int idx; 44 45 /** 46 * Create a new {@link Declaration} 47 * @param instructions The instructions for this dectlaration 48 * @param idx The index in the {@link StyleSource} 49 */ 50 public Declaration(List<Instruction> instructions, int idx) { 51 this.instructions = Utils.toUnmodifiableList(instructions); 52 this.idx = idx; 53 } 54 55 /** 56 * <p>Executes the instructions against the environment {@code env}</p> 57 * 58 * @param env the environment 59 */ 60 public void execute(Environment env) { 61 for (Instruction i : instructions) { 62 i.execute(env); 63 } 64 } 65 66 @Override 67 public int hashCode() { 68 return Objects.hash(instructions, idx); 69 } 70 71 @Override 72 public boolean equals(Object obj) { 73 if (this == obj) return true; 74 if (obj == null || getClass() != obj.getClass()) return false; 75 Declaration that = (Declaration) obj; 76 return idx == that.idx && 77 Objects.equals(instructions, that.instructions); 78 } 79 80 @Override 81 public String toString() { 82 return "Declaration [instructions=" + instructions + ", idx=" + idx + ']'; 83 } 33 public MapCSSRule(List<Selector> selectors, Declaration declaration) { 34 this.selectors = Utils.toUnmodifiableList(selectors); 35 this.declaration = declaration; 84 36 } 85 37 86 38 /** 87 * Constructs a new {@code MapCSSRule}. 88 * @param selector The selector 89 * @param declaration The declaration 39 * Test whether the selector of this rule applies to the primitive. 40 * 41 * @param env the Environment. env.mc and env.layer are read-only when matching a selector. 42 * env.source is not needed. This method will set the matchingReferrers field of env as 43 * a side effect! Make sure to clear it before invoking this method. 44 * @return true, if the selector applies 45 * @see Selector#matches 90 46 */ 91 public MapCSSRule(Selector selector, Declaration declaration) { 92 this.selector = selector; 93 this.declaration = declaration; 47 public boolean matches(Environment env) { 48 return selectors.stream().anyMatch(s -> s.matches(env)); 94 49 } 95 50 96 51 /** 97 52 * <p>Executes the instructions against the environment {@code env}</p> 98 53 * 99 54 * @param env the environment 55 * @see Declaration#execute 100 56 */ 101 57 public void execute(Environment env) { 102 58 declaration.execute(env); … … public int compareTo(MapCSSRule o) { 109 65 110 66 @Override 111 67 public String toString() { 112 return selector + declaration.instructions.stream() 68 final String selectorsString = selectors.stream().map(String::valueOf) 69 .collect(Collectors.joining(",\n")); 70 final String declarationString = declaration.instructions.stream() 113 71 .map(String::valueOf) 114 72 .collect(Collectors.joining("\n ", " {\n ", "\n}")); 73 return selectorsString + declarationString; 115 74 } 116 75 } 117 76 -
.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerIndex.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleIndex.java similarity index 55% rename from src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerIndex.java rename to src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleIndex.java index 909042237..5fe45aad3 100644
old new 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.text.MessageFormat; 7 import java.util.ArrayList; 8 import java.util.HashMap; 9 import java.util.List; 10 import java.util.Map; 11 import java.util.Set; 12 13 import org.openstreetmap.josm.data.osm.INode; 14 import org.openstreetmap.josm.data.osm.IRelation; 15 import org.openstreetmap.josm.data.osm.IWay; 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 import org.openstreetmap.josm.data.osm.OsmUtils; 18 import org.openstreetmap.josm.data.validation.Severity; 19 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.TagCheck; 20 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule; 21 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex; 22 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector; 23 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector; 24 import org.openstreetmap.josm.tools.JosmRuntimeException; 25 import org.openstreetmap.josm.tools.Logging; 26 import org.openstreetmap.josm.tools.MultiMap; 27 28 /** 29 * Helper class for {@link MapCSSTagChecker} to store indexes of rules 30 * @author Gerd 31 * 32 */ 33 final class MapCSSTagCheckerIndex { 34 final Map<MapCSSRule, TagCheck> ruleToCheckMap = new HashMap<>(); 35 36 static final boolean ALL_TESTS = true; 37 static final boolean ONLY_SELECTED_TESTS = false; 38 39 /** 40 * Rules for nodes 41 */ 42 final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); 43 /** 44 * Rules for ways without tag area=no 45 */ 46 final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); 47 /** 48 * Rules for ways with tag area=no 49 */ 50 final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); 51 /** 52 * Rules for relations that are not multipolygon relations 53 */ 54 final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); 55 /** 56 * Rules for multipolygon relations 57 */ 58 final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); 59 60 MapCSSTagCheckerIndex(MultiMap<String, TagCheck> checks, boolean includeOtherSeverity, boolean allTests) { 61 buildIndex(checks, includeOtherSeverity, allTests); 62 } 63 64 private void buildIndex(MultiMap<String, TagCheck> checks, boolean includeOtherSeverity, boolean allTests) { 65 List<TagCheck> allChecks = new ArrayList<>(); 66 for (Set<TagCheck> cs : checks.values()) { 67 allChecks.addAll(cs); 68 } 69 70 ruleToCheckMap.clear(); 71 nodeRules.clear(); 72 wayRules.clear(); 73 wayNoAreaRules.clear(); 74 relationRules.clear(); 75 multipolygonRules.clear(); 76 77 // optimization: filter rules for different primitive types 78 for (TagCheck c : allChecks) { 79 if (!includeOtherSeverity && Severity.OTHER == c.getSeverity() 80 && c.setClassExpressions.isEmpty()) { 81 // Ignore "information" level checks if not wanted, unless they also set a MapCSS class 82 continue; 83 } 84 85 for (Selector s : c.rule.selectors) { 86 // find the rightmost selector, this must be a GeneralSelector 87 boolean hasLeftRightSel = false; 88 Selector selRightmost = s; 89 while (selRightmost instanceof Selector.ChildOrParentSelector) { 90 hasLeftRightSel = true; 91 selRightmost = ((Selector.ChildOrParentSelector) selRightmost).right; 92 } 93 if (!allTests && !hasLeftRightSel) { 94 continue; 95 } 96 97 MapCSSRule optRule = new MapCSSRule(s.optimizedBaseCheck(), c.rule.declaration); 98 99 ruleToCheckMap.put(optRule, c); 100 final String base = ((GeneralSelector) selRightmost).getBase(); 101 switch (base) { 102 case Selector.BASE_NODE: 103 nodeRules.add(optRule); 104 break; 105 case Selector.BASE_WAY: 106 wayNoAreaRules.add(optRule); 107 wayRules.add(optRule); 108 break; 109 case Selector.BASE_AREA: 110 wayRules.add(optRule); 111 multipolygonRules.add(optRule); 112 break; 113 case Selector.BASE_RELATION: 114 relationRules.add(optRule); 115 multipolygonRules.add(optRule); 116 break; 117 case Selector.BASE_ANY: 118 nodeRules.add(optRule); 119 wayRules.add(optRule); 120 wayNoAreaRules.add(optRule); 121 relationRules.add(optRule); 122 multipolygonRules.add(optRule); 123 break; 124 case Selector.BASE_CANVAS: 125 case Selector.BASE_META: 126 case Selector.BASE_SETTING: 127 break; 128 default: 129 final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base)); 130 Logging.warn(tr("Failed to index validator rules. Error was: {0}", e.getMessage())); 131 Logging.error(e); 132 } 133 } 134 } 135 nodeRules.initIndex(); 136 wayRules.initIndex(); 137 wayNoAreaRules.initIndex(); 138 relationRules.initIndex(); 139 multipolygonRules.initIndex(); 140 } 141 142 /** 143 * Get the index of rules for the given primitive. 144 * @param p the primitve 145 * @return index of rules for the given primitive 146 */ 147 public MapCSSRuleIndex get(OsmPrimitive p) { 148 if (p instanceof INode) { 149 return nodeRules; 150 } else if (p instanceof IWay) { 151 if (OsmUtils.isFalse(p.get("area"))) { 152 return wayNoAreaRules; 153 } else { 154 return wayRules; 155 } 156 } else if (p instanceof IRelation) { 157 if (((IRelation<?>) p).isMultipolygon()) { 158 return multipolygonRules; 159 } else { 160 return relationRules; 161 } 162 } else { 163 throw new IllegalArgumentException("Unsupported type: " + p); 164 } 165 } 166 167 /** 168 * return the TagCheck for which the given indexed rule was created. 169 * @param rule an indexed rule 170 * @return the original TagCheck 171 */ 172 public TagCheck getCheck(MapCSSRule rule) { 173 return ruleToCheckMap.get(rule); 174 } 175 } 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.mappaint.mapcss; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.text.MessageFormat; 7 import java.util.Iterator; 8 import java.util.Map; 9 import java.util.stream.Collectors; 10 import java.util.stream.Stream; 11 12 import org.openstreetmap.josm.data.osm.INode; 13 import org.openstreetmap.josm.data.osm.IPrimitive; 14 import org.openstreetmap.josm.data.osm.IRelation; 15 import org.openstreetmap.josm.data.osm.IWay; 16 import org.openstreetmap.josm.data.osm.OsmUtils; 17 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex; 18 import org.openstreetmap.josm.tools.JosmRuntimeException; 19 import org.openstreetmap.josm.tools.Logging; 20 21 /** 22 * Store indexes of {@link MapCSSRule}s using {@link MapCSSRuleIndex} differentiated by {@linkplain Selector#getBase() base} 23 */ 24 public final class MapCSSStyleIndex { 25 26 /** 27 * Rules for nodes 28 */ 29 final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); 30 /** 31 * Rules for ways without tag area=no 32 */ 33 final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); 34 /** 35 * Rules for ways with tag area=no 36 */ 37 final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); 38 /** 39 * Rules for relations that are not multipolygon relations 40 */ 41 final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); 42 /** 43 * Rules for multipolygon relations 44 */ 45 final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); 46 /** 47 * rules to apply canvas properties 48 */ 49 final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex(); 50 51 /** 52 * Clear the index. 53 * <p> 54 * You must own the write lock STYLE_SOURCE_LOCK when calling this method. 55 */ 56 public void clear() { 57 nodeRules.clear(); 58 wayRules.clear(); 59 wayNoAreaRules.clear(); 60 relationRules.clear(); 61 multipolygonRules.clear(); 62 canvasRules.clear(); 63 } 64 65 /** 66 * Builds and initializes the index. 67 * <p> 68 * You must own the write lock of STYLE_SOURCE_LOCK when calling this method. 69 */ 70 public void buildIndex(Stream<MapCSSRule> ruleStream) { 71 clear(); 72 // optimization: filter rules for different primitive types 73 ruleStream.forEach(rule -> { 74 final Map<String, MapCSSRule> selectorsByBase = rule.selectors.stream() 75 .collect(Collectors.groupingBy(Selector::getBase, 76 Collectors.collectingAndThen(Collectors.toList(), selectors -> new MapCSSRule(selectors, rule.declaration)))); 77 selectorsByBase.forEach((base, optRule) -> { 78 switch (base) { 79 case Selector.BASE_NODE: 80 nodeRules.add(optRule); 81 break; 82 case Selector.BASE_WAY: 83 wayNoAreaRules.add(optRule); 84 wayRules.add(optRule); 85 break; 86 case Selector.BASE_AREA: 87 wayRules.add(optRule); 88 multipolygonRules.add(optRule); 89 break; 90 case Selector.BASE_RELATION: 91 relationRules.add(optRule); 92 multipolygonRules.add(optRule); 93 break; 94 case Selector.BASE_ANY: 95 nodeRules.add(optRule); 96 wayRules.add(optRule); 97 wayNoAreaRules.add(optRule); 98 relationRules.add(optRule); 99 multipolygonRules.add(optRule); 100 break; 101 case Selector.BASE_CANVAS: 102 canvasRules.add(optRule); 103 break; 104 case Selector.BASE_META: 105 case Selector.BASE_SETTING: 106 case Selector.BASE_SETTINGS: 107 break; 108 default: 109 final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base)); 110 Logging.warn(tr("Failed to index validator rules. Error was: {0}", e.getMessage())); 111 Logging.error(e); 112 } 113 }); 114 }); 115 initIndex(); 116 } 117 118 private void initIndex() { 119 nodeRules.initIndex(); 120 wayRules.initIndex(); 121 wayNoAreaRules.initIndex(); 122 relationRules.initIndex(); 123 multipolygonRules.initIndex(); 124 canvasRules.initIndex(); 125 } 126 127 /** 128 * Get the index of rules for the given primitive. 129 * @param p the primitive 130 * @return index of rules for the given primitive 131 */ 132 public MapCSSRuleIndex get(IPrimitive p) { 133 if (p instanceof INode) { 134 return nodeRules; 135 } else if (p instanceof IWay) { 136 if (OsmUtils.isFalse(p.get("area"))) { 137 return wayNoAreaRules; 138 } else { 139 return wayRules; 140 } 141 } else if (p instanceof IRelation) { 142 if (((IRelation<?>) p).isMultipolygon()) { 143 return multipolygonRules; 144 } else if (p.hasKey("#canvas")) { 145 return canvasRules; 146 } else { 147 return relationRules; 148 } 149 } else { 150 throw new IllegalArgumentException("Unsupported type: " + p); 151 } 152 } 153 154 /** 155 * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to 156 * not match this primitive. 157 * <p> 158 * You must have a read lock of STYLE_SOURCE_LOCK when calling this method. 159 * 160 * @param osm the primitive to match 161 * @return An iterator over possible rules in the right order. 162 */ 163 public Iterator<MapCSSRule> getRuleCandidates(IPrimitive osm) { 164 return get(osm).getRuleCandidates(osm); 165 } 166 } -
src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java index c959d18b6..86280c808 100644
a b 13 13 import java.io.StringReader; 14 14 import java.lang.reflect.Field; 15 15 import java.nio.charset.StandardCharsets; 16 import java.text.MessageFormat;17 16 import java.util.ArrayList; 18 17 import java.util.BitSet; 19 18 import java.util.Collections; … … 25 24 import java.util.Map; 26 25 import java.util.Map.Entry; 27 26 import java.util.NoSuchElementException; 27 import java.util.Optional; 28 28 import java.util.Set; 29 29 import java.util.concurrent.locks.ReadWriteLock; 30 30 import java.util.concurrent.locks.ReentrantReadWriteLock; … … 33 33 import java.util.zip.ZipFile; 34 34 35 35 import org.openstreetmap.josm.data.Version; 36 import org.openstreetmap.josm.data.osm.INode;37 36 import org.openstreetmap.josm.data.osm.IPrimitive; 38 import org.openstreetmap.josm.data.osm.IRelation;39 import org.openstreetmap.josm.data.osm.IWay;40 37 import org.openstreetmap.josm.data.osm.KeyValueVisitor; 41 38 import org.openstreetmap.josm.data.osm.Node; 42 import org.openstreetmap.josm.data.osm.OsmUtils;43 39 import org.openstreetmap.josm.data.osm.Tagged; 44 40 import org.openstreetmap.josm.data.preferences.sources.SourceEntry; 45 41 import org.openstreetmap.josm.gui.mappaint.Cascade; … … 55 51 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType; 56 52 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition; 57 53 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition; 58 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;59 54 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector; 60 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;61 55 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser; 62 56 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 63 57 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError; … … 88 82 */ 89 83 public final List<MapCSSRule> rules = new ArrayList<>(); 90 84 /** 91 * Rules for nodes85 * Index of rules in this style file 92 86 */ 93 public final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); 94 /** 95 * Rules for ways without tag area=no 96 */ 97 public final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); 98 /** 99 * Rules for ways with tag area=no 100 */ 101 public final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); 102 /** 103 * Rules for relations that are not multipolygon relations 104 */ 105 public final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); 106 /** 107 * Rules for multipolygon relations 108 */ 109 public final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); 110 /** 111 * rules to apply canvas properties 112 */ 113 public final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex(); 87 private final MapCSSStyleIndex ruleIndex = new MapCSSStyleIndex(); 114 88 115 89 private Color backgroundColorOverride; 116 90 private String css; … … public void initIndex() { 291 265 Collections.sort(rules); 292 266 for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) { 293 267 MapCSSRule r = rules.get(ruleIndex); 294 // find the rightmost selector, this must be a GeneralSelector 295 Selector selRightmost = r.selector; 296 while (selRightmost instanceof ChildOrParentSelector) { 297 selRightmost = ((ChildOrParentSelector) selRightmost).right; 298 } 299 OptimizedGeneralSelector s = (OptimizedGeneralSelector) selRightmost; 300 if (s.conds == null) { 301 remaining.set(ruleIndex); 302 continue; 303 } 304 List<SimpleKeyValueCondition> sk = new ArrayList<>(Utils.filteredCollection(s.conds, 305 SimpleKeyValueCondition.class)); 306 if (!sk.isEmpty()) { 307 SimpleKeyValueCondition c = sk.get(sk.size() - 1); 308 getEntryInIndex(c.k).addForKeyAndValue(c.v, ruleIndex); 309 } else { 310 String key = findAnyRequiredKey(s.conds); 311 if (key != null) { 312 getEntryInIndex(key).addForKey(ruleIndex); 313 } else { 268 for (Selector selector : r.selectors) { 269 final List<Condition> conditions = selector.getConditions(); 270 if (conditions == null || conditions.isEmpty()) { 314 271 remaining.set(ruleIndex); 272 continue; 273 } 274 Optional<SimpleKeyValueCondition> lastCondition = Utils.filteredCollection(conditions, SimpleKeyValueCondition.class) 275 .stream() 276 .reduce((first, last) -> last); 277 if (lastCondition.isPresent()) { 278 getEntryInIndex(lastCondition.get().k).addForKeyAndValue(lastCondition.get().v, ruleIndex); 279 } else { 280 String key = findAnyRequiredKey(conditions); 281 if (key != null) { 282 getEntryInIndex(key).addForKey(ruleIndex); 283 } else { 284 remaining.set(ruleIndex); 285 } 315 286 } 316 287 } 317 288 } … … public void loadStyleSource(boolean metadataOnly) { 423 394 try { 424 395 init(); 425 396 rules.clear(); 426 nodeRules.clear(); 427 wayRules.clear(); 428 wayNoAreaRules.clear(); 429 relationRules.clear(); 430 multipolygonRules.clear(); 431 canvasRules.clear(); 397 ruleIndex.clear(); 432 398 // remove "areaStyle" pseudo classes intended only for validator (causes StackOverflowError otherwise), see #16183 433 399 removeAreaStylePseudoClass = url == null || !url.contains("validator"); // resource://data/validator/ or xxx.validator.mapcss 434 400 try (InputStream in = getSourceInputStream()) { … … public void loadStyleSource(boolean metadataOnly) { 466 432 return; 467 433 } 468 434 // optimization: filter rules for different primitive types 469 for (MapCSSRule r: rules) { 470 // find the rightmost selector, this must be a GeneralSelector 471 Selector selRightmost = r.selector; 472 while (selRightmost instanceof ChildOrParentSelector) { 473 selRightmost = ((ChildOrParentSelector) selRightmost).right; 474 } 475 MapCSSRule optRule = new MapCSSRule(r.selector.optimizedBaseCheck(), r.declaration); 476 final String base = ((GeneralSelector) selRightmost).getBase(); 477 switch (base) { 478 case Selector.BASE_NODE: 479 nodeRules.add(optRule); 480 break; 481 case Selector.BASE_WAY: 482 wayNoAreaRules.add(optRule); 483 wayRules.add(optRule); 484 break; 485 case Selector.BASE_AREA: 486 wayRules.add(optRule); 487 multipolygonRules.add(optRule); 488 break; 489 case Selector.BASE_RELATION: 490 relationRules.add(optRule); 491 multipolygonRules.add(optRule); 492 break; 493 case Selector.BASE_ANY: 494 nodeRules.add(optRule); 495 wayRules.add(optRule); 496 wayNoAreaRules.add(optRule); 497 relationRules.add(optRule); 498 multipolygonRules.add(optRule); 499 break; 500 case Selector.BASE_CANVAS: 501 canvasRules.add(r); 502 break; 503 case Selector.BASE_META: 504 case Selector.BASE_SETTING: 505 case Selector.BASE_SETTINGS: 506 break; 507 default: 508 final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base)); 509 Logging.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage())); 510 Logging.error(e); 511 logError(e); 512 } 513 } 514 nodeRules.initIndex(); 515 wayRules.initIndex(); 516 wayNoAreaRules.initIndex(); 517 relationRules.initIndex(); 518 multipolygonRules.initIndex(); 519 canvasRules.initIndex(); 435 ruleIndex.buildIndex(rules.stream()); 520 436 loaded = true; 521 437 } finally { 522 438 STYLE_SOURCE_LOCK.writeLock().unlock(); … … private void loadSettings() { 599 515 600 516 // Parse rules 601 517 for (MapCSSRule r : rules) { 602 if (r.selector instanceof GeneralSelector) {603 GeneralSelector gs = (GeneralSelector) r.selector;518 final Selector gs = r.selectors.get(0); 519 if (gs instanceof GeneralSelector) { 604 520 if (Selector.BASE_SETTING.equals(gs.getBase())) { 605 loadSettings(r, gs, env);521 loadSettings(r, ((GeneralSelector) gs), env); 606 522 } else if (Selector.BASE_SETTINGS.equals(gs.getBase())) { 607 loadSettings(r, gs, envGroups);523 loadSettings(r, ((GeneralSelector) gs), envGroups); 608 524 } 609 525 } 610 526 } … … private Cascade constructSpecial(String type) { 650 566 Environment env = new Environment(n, mc, "default", this); 651 567 652 568 for (MapCSSRule r : rules) { 653 if (r.selector instanceof GeneralSelector) { 654 GeneralSelector gs = (GeneralSelector) r.selector; 655 if (gs.getBase().equals(type)) { 656 if (!gs.matchesConditions(env)) { 657 continue; 658 } 659 r.execute(env); 660 } 569 final boolean matches = r.selectors.stream().anyMatch(gs -> gs instanceof GeneralSelector 570 && gs.getBase().equals(type) 571 && ((GeneralSelector) gs).matchesConditions(env)); 572 if (matches) { 573 r.execute(env); 661 574 } 662 575 } 663 576 return mc.getCascade("default"); … … public Color getBackgroundColorOverride() { 670 583 671 584 @Override 672 585 public void apply(MultiCascade mc, IPrimitive osm, double scale, boolean pretendWayIsClosed) { 673 MapCSSRuleIndex matchingRuleIndex;674 if (osm instanceof INode) {675 matchingRuleIndex = nodeRules;676 } else if (osm instanceof IWay) {677 if (OsmUtils.isFalse(osm.get("area"))) {678 matchingRuleIndex = wayNoAreaRules;679 } else {680 matchingRuleIndex = wayRules;681 }682 } else if (osm instanceof IRelation) {683 if (((IRelation<?>) osm).isMultipolygon()) {684 matchingRuleIndex = multipolygonRules;685 } else if (osm.hasKey("#canvas")) {686 matchingRuleIndex = canvasRules;687 } else {688 matchingRuleIndex = relationRules;689 }690 } else {691 throw new IllegalArgumentException("Unsupported type: " + osm);692 }693 586 694 587 Environment env = new Environment(osm, mc, null, this); 695 588 // the declaration indices are sorted, so it suffices to save the last used index 696 589 int lastDeclUsed = -1; 697 590 698 Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(osm);591 Iterator<MapCSSRule> candidates = ruleIndex.getRuleCandidates(osm); 699 592 while (candidates.hasNext()) { 700 593 MapCSSRule r = candidates.next(); 701 env.clearSelectorMatchingInformation(); 702 env.layer = r.selector.getSubpart().getId(env); 703 String sub = env.layer; 704 if (r.selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector) 705 Selector s = r.selector; 594 for (Selector s : r.selectors) { 595 env.clearSelectorMatchingInformation(); 596 env.layer = s.getSubpart().getId(env); 597 String sub = env.layer; 598 if (!s.matches(env)) { // as side effect env.parent will be set (if s is a child selector) 599 continue; 600 } 706 601 if (s.getRange().contains(scale)) { 707 602 mc.range = Range.cut(mc.range, s.getRange()); 708 603 } else { … … public boolean evalSupportsDeclCondition(String feature, Object val) { 757 652 * @since 13633 758 653 */ 759 654 public void removeMetaRules() { 760 for (Iterator<MapCSSRule> it = rules.iterator(); it.hasNext();) { 761 MapCSSRule x = it.next(); 762 if (x.selector instanceof GeneralSelector) { 763 GeneralSelector gs = (GeneralSelector) x.selector; 764 if (Selector.BASE_META.equals(gs.base)) { 765 it.remove(); 766 } 767 } 768 } 655 rules.removeIf(x -> x.selectors.get(0) instanceof GeneralSelector && Selector.BASE_META.equals(x.selectors.get(0).getBase())); 769 656 } 770 657 771 658 /** -
src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java index 21089f147..ca85f24d0 100644
a b 31 31 import org.openstreetmap.josm.gui.mappaint.Range; 32 32 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition; 33 33 import org.openstreetmap.josm.tools.CheckParameterUtil; 34 import org.openstreetmap.josm.tools.CompositeList; 34 35 import org.openstreetmap.josm.tools.Geometry; 35 36 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 36 37 import org.openstreetmap.josm.tools.Logging; … … 105 106 */ 106 107 Range getRange(); 107 108 109 String getBase(); 110 108 111 /** 109 * Create an "optimized" copy of this selector that omits the base check. 110 * 111 * For the style source, the list of rules is preprocessed, such that 112 * there is a separate list of rules for nodes, ways, ... 113 * 114 * This means that the base check does not have to be performed 115 * for each rule, but only once for each primitive. 116 * 117 * @return a selector that is identical to this object, except the base of the 118 * "rightmost" selector is not checked 112 * Returns the list of conditions. 113 * @return the list of conditions 119 114 */ 120 Selector optimizedBaseCheck();115 List<Condition> getConditions(); 121 116 122 117 /** 123 118 * The type of child of parent selector. … … public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrP 166 161 this.type = type; 167 162 } 168 163 164 @Override 165 public String getBase() { 166 // take the base from the rightmost selector 167 return right.getBase(); 168 } 169 170 @Override 171 public List<Condition> getConditions() { 172 return new CompositeList<>(left.getConditions(), right.getConditions()); 173 } 174 169 175 /** 170 176 * <p>Finds the first referrer matching {@link #left}</p> 171 177 * … … private void visitBBox(Environment e, AbstractFinder finder) { 396 402 boolean withNodes = finder instanceof ContainsFinder; 397 403 if (e.osm.getDataSet() == null) { 398 404 // do nothing 399 } else if (left instanceof OptimizedGeneralSelector) {400 if (withNodes && (( OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) {405 } else if (left instanceof GeneralSelector) { 406 if (withNodes && ((GeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) { 401 407 finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox())); 402 408 } 403 if ((( OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) {409 if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) { 404 410 finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); 405 411 } 406 if ((( OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) {412 if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) { 407 413 finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox())); 408 414 } 409 415 } else { … … public boolean matches(Environment e) { 454 460 455 461 } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) { 456 462 e.parent = e.osm; 457 if (right instanceof OptimizedGeneralSelector463 if (right instanceof GeneralSelector 458 464 && e.osm.getDataSet() != null 459 && (( OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {465 && ((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) { 460 466 final CrossingFinder crossingFinder = new CrossingFinder(e); 461 467 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); 462 468 } … … public Range getRange() { 533 539 return right.getRange(); 534 540 } 535 541 536 @Override537 public Selector optimizedBaseCheck() {538 return new ChildOrParentSelector(left, link, right.optimizedBaseCheck(), type);539 }540 541 542 @Override 542 543 public String toString() { 543 544 return left.toString() + ' ' + (ChildOrParentSelectorType.PARENT == type ? '<' : '>') + link + ' ' + right; … … public boolean matches(Environment env) { 576 577 return true; 577 578 } 578 579 579 /** 580 * Returns the list of conditions. 581 * @return the list of conditions 582 */ 580 @Override 583 581 public List<Condition> getConditions() { 584 582 return conds; 585 583 } … … public boolean matches(Environment env) { 602 600 } 603 601 604 602 @Override 605 public S ubpart getSubpart() {606 throw new UnsupportedOperationException( "Not supported yet.");603 public String getBase() { 604 throw new UnsupportedOperationException(); 607 605 } 608 606 609 607 @Override 610 public Range getRange() {611 throw new UnsupportedOperationException( "Not supported yet.");608 public Subpart getSubpart() { 609 throw new UnsupportedOperationException(); 612 610 } 613 611 614 612 @Override 615 public Selector optimizedBaseCheck() {613 public Range getRange() { 616 614 throw new UnsupportedOperationException(); 617 615 } 618 616 … … public String toString() { 625 623 /** 626 624 * General selector. See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Selectors">wiki</a> 627 625 */ 628 class GeneralSelector extends OptimizedGeneralSelector { 629 630 public GeneralSelector(String base, Range zoom, List<Condition> conds, Subpart subpart) { 631 super(base, zoom, conds, subpart); 632 } 633 634 public boolean matchesConditions(Environment e) { 635 return super.matches(e); 636 } 637 638 @Override 639 public Selector optimizedBaseCheck() { 640 return new OptimizedGeneralSelector(this); 641 } 626 class GeneralSelector extends AbstractSelector { 642 627 643 @Override644 public boolean matches(Environment e) {645 return matchesBase(e) && super.matches(e);646 }647 }648 649 /**650 * Superclass of {@link GeneralSelector}. Used to create an "optimized" copy of this selector that omits the base check.651 * @see Selector#optimizedBaseCheck652 */653 class OptimizedGeneralSelector extends AbstractSelector {654 628 public final String base; 655 629 public final Range range; 656 630 public final Subpart subpart; 657 631 658 public OptimizedGeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) {632 public GeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) { 659 633 super(conds); 660 634 this.base = checkBase(base); 661 635 this.range = Objects.requireNonNull(range, "range"); 662 636 this.subpart = subpart != null ? subpart : Subpart.DEFAULT_SUBPART; 663 637 } 664 638 665 public OptimizedGeneralSelector(GeneralSelector s) {666 this(s.base, s.range, s.conds, s.subpart);667 }668 669 639 @Override 670 640 public Subpart getSubpart() { 671 641 return subpart; … … public Range getRange() { 676 646 return range; 677 647 } 678 648 649 public boolean matchesConditions(Environment e) { 650 return super.matches(e); 651 } 652 653 @Override 654 public boolean matches(Environment e) { 655 return matchesBase(e) && super.matches(e); 656 } 657 679 658 /** 680 659 * Set base and check if this is a known value. 681 660 * @param base value for base … … private static String checkBase(String base) { 698 677 } 699 678 } 700 679 680 @Override 701 681 public String getBase() { 702 682 return base; 703 683 } … … public boolean matchesBase(Environment e) { 734 714 return matchesBase(e.osm); 735 715 } 736 716 737 @Override738 public Selector optimizedBaseCheck() {739 throw new UnsupportedOperationException();740 }741 742 717 public static Range fromLevel(int a, int b) { 743 718 // for input validation in Range constructor below 744 719 double lower = 0; -
src/org/openstreetmap/josm/tools/Utils.java
diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java index 1a02f4085..9a2317941 100644
a b 47 47 import java.util.Iterator; 48 48 import java.util.List; 49 49 import java.util.Locale; 50 import java.util.Map; 50 51 import java.util.Optional; 51 52 import java.util.concurrent.ExecutionException; 52 53 import java.util.concurrent.Executor; … … 68 69 import javax.script.ScriptEngine; 69 70 import javax.script.ScriptEngineManager; 70 71 72 import com.kitfox.svg.xml.XMLParseUtil; 71 73 import org.openstreetmap.josm.spi.preferences.Config; 72 74 73 75 /** … … public B get(int index) { 767 769 } 768 770 } 769 771 772 /** 773 * Returns an unmodifiable map for the given map. 774 * Makes use of {@link Collections#emptyMap()} and {@link Collections#singletonMap} and {@link Map#ofEntries(Map.Entry[])} to save memory. 775 * 776 * @param map the map for which an unmodifiable map is to be returned 777 * @param <K> the type of keys maintained by this map 778 * @param <V> the type of mapped values 779 * @return an unmodifiable map 780 * @see <a href="https://dzone.com/articles/preventing-your-java-collections-from-wasting-memo"> 781 * How to Prevent Your Java Collections From Wasting Memory</a> 782 */ 783 public static <K, V> Map<K, V> toUnmodifiableMap(Map<K, V> map) { 784 return XMLParseUtil.toUnmodifiableMap(map); 785 } 786 770 787 /** 771 788 * Returns the first not empty string in the given candidates, otherwise the default string. 772 789 * @param defaultString default string returned if all candidates would be empty if stripped -
test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java
diff --git a/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java b/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java index a5d2c13be..d7b27a7ee 100644
a b public void run() throws IOException { 197 197 } 198 198 nc.zoomTo(ProjectionRegistry.getProjection().latlon2eastNorth(center), scale); 199 199 if (checkScale) { 200 int lvl = Selector. OptimizedGeneralSelector.scale2level(nc.getDist100Pixel());200 int lvl = Selector.GeneralSelector.scale2level(nc.getDist100Pixel()); 201 201 Assert.assertEquals(17, lvl); 202 202 } 203 203 -
test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java index edcdef51b..7d10ca653 100644
a b 15 15 import java.util.List; 16 16 import java.util.Set; 17 17 18 import org.junit.Before; 18 19 import org.junit.Rule; 19 20 import org.junit.Test; 20 21 import org.openstreetmap.josm.TestUtils; … … 27 28 import org.openstreetmap.josm.data.osm.IPrimitive; 28 29 import org.openstreetmap.josm.data.osm.OsmPrimitive; 29 30 import org.openstreetmap.josm.data.osm.OsmUtils; 31 import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry; 32 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 30 33 import org.openstreetmap.josm.data.validation.Severity; 31 34 import org.openstreetmap.josm.data.validation.TestError; 32 35 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.ParseResult; … … 52 55 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 53 56 public JOSMTestRules test = new JOSMTestRules().projection().territories().preferences(); 54 57 58 /** 59 * Setup test. 60 */ 61 @Before 62 public void setUp() { 63 MapCSSTagCheckerAsserts.clear(); 64 } 65 55 66 static MapCSSTagChecker buildTagChecker(String css) throws ParseException { 56 67 final MapCSSTagChecker test = new MapCSSTagChecker(); 57 test.checks.putAll("test", TagCheck.readMapCSS(new StringReader(css)).parseChecks); 68 Set<String> errors = new HashSet<>(); 69 test.checks.putAll("test", TagCheck.readMapCSS(new StringReader(css), errors::add).parseChecks); 70 assertTrue(errors.toString(), errors.isEmpty()); 58 71 return test; 59 72 } 60 73 … … public void testNaturalMarsh() throws ParseException { 71 84 " fixRemove: \"{0.key}\";\n" + 72 85 " fixAdd: \"natural=wetland\";\n" + 73 86 " fixAdd: \"wetland=marsh\";\n" + 74 "}") );87 "}"), null); 75 88 final List<TagCheck> checks = result.parseChecks; 76 89 assertEquals(1, checks.size()); 77 90 assertTrue(result.parseErrors.isEmpty()); … … public void testTicket10913() throws ParseException { 110 123 "throwError: \"error\";" + 111 124 "fixChangeKey: \"highway => construction\";\n" + 112 125 "fixAdd: \"highway=construction\";\n" + 113 "}") ).parseChecks.get(0);126 "}"), null).parseChecks.get(0); 114 127 final Command command = check.fixPrimitive(p); 115 128 assertTrue(command instanceof SequenceCommand); 116 129 final Iterator<PseudoCommand> it = command.getChildren().iterator(); … … public void testTicket10859() throws ParseException { 155 168 @Test 156 169 public void testTicket13630() throws ParseException { 157 170 ParseResult result = TagCheck.readMapCSS(new StringReader( 158 "node[crossing=zebra] {fixRemove: \"crossing=zebra\";}") );171 "node[crossing=zebra] {fixRemove: \"crossing=zebra\";}"), null); 159 172 assertTrue(result.parseChecks.isEmpty()); 160 173 assertEquals(1, result.parseErrors.size()); 161 174 } … … public void testPreprocessing() throws ParseException { 181 194 public void testInit() throws Exception { 182 195 MapCSSTagChecker c = new MapCSSTagChecker(); 183 196 c.initialize(); 197 } 184 198 199 /** 200 * Unit test for all {@link MapCSSTagChecker.TagTest} assertions. 201 * @throws Exception if an error occurs 202 */ 203 @Test 204 public void testAssertions() throws Exception { 205 MapCSSTagChecker c = new MapCSSTagChecker(); 185 206 Set<String> assertionErrors = new LinkedHashSet<>(); 186 for (Set<TagCheck> schecks : c.checks.values()) { 187 assertionErrors.addAll(c.checkAsserts(schecks)); 207 208 // initialize 209 for (ExtendedSourceEntry entry : ValidatorPrefHelper.INSTANCE.getDefault()) { 210 c.addMapCSS(entry.url, assertionErrors::add); 188 211 } 212 189 213 for (String msg : assertionErrors) { 190 214 Logging.error(msg); 191 215 } … … public void testAssertInsideCountry() throws ParseException { 204 228 " assertMatch: \"node amenity=parking\";\n" + 205 229 " assertNoMatch: \"node amenity=restaurant\";\n" + 206 230 "}"); 207 Set<String> errors = test.checkAsserts(test.checks.get("test")); 208 assertTrue(errors.toString(), errors.isEmpty()); 231 assertNotNull(test); 209 232 } 210 233 211 234 /** … … public void testTicket17058() throws ParseException { 220 243 " assertMatch: \"way name=Hauptstraße\";\n" + 221 244 " assertNoMatch: \"way name=Hauptstrasse\";\n" + 222 245 "}"); 223 Set<String> errors = test.checkAsserts(test.checks.get("test")); 224 assertTrue(errors.toString(), errors.isEmpty()); 246 assertNotNull(test); 225 247 } 226 248 227 249 /** … … public void testTicket13762() throws ParseException { 233 255 final ParseResult parseResult = TagCheck.readMapCSS(new StringReader("" + 234 256 "meta[lang=de] {\n" + 235 257 " title: \"Deutschlandspezifische Regeln\";" + 236 "}") );258 "}"), null); 237 259 assertTrue(parseResult.parseErrors.isEmpty()); 238 260 } 239 261 -
test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.java
diff --git a/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.java b/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.java index 613123dce..5eca7aa8d 100644
a b ChildOrParentSelector parse(String css) { 73 73 MapCSSStyleSource source = new MapCSSStyleSource(css); 74 74 source.loadStyleSource(); 75 75 assertEquals(1, source.rules.size()); 76 return (ChildOrParentSelector) source.rules.get(0).selector ;76 return (ChildOrParentSelector) source.rules.get(0).selectors.get(0); 77 77 } 78 78 79 79 @Test -
test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.java
diff --git a/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.java b/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.java index 07908488d..0d02b9ea2 100644
a b public void testParentTags() throws Exception { 451 451 source.loadStyleSource(); 452 452 assertEquals(1, source.rules.size()); 453 453 Environment e = new Environment(n, new MultiCascade(), Environment.DEFAULT_LAYER, null); 454 assertTrue(source.rules.get(0). selector.matches(e));454 assertTrue(source.rules.get(0).matches(e)); 455 455 source.rules.get(0).declaration.execute(e); 456 456 assertEquals("x2;x10", e.getCascade(Environment.DEFAULT_LAYER).get("refs", null, String.class)); 457 457 } … … public void testSort() throws Exception { 466 466 source.loadStyleSource(); 467 467 assertEquals(1, source.rules.size()); 468 468 Environment e = new Environment(way1, new MultiCascade(), Environment.DEFAULT_LAYER, null); 469 assertTrue(source.rules.get(0). selector.matches(e));469 assertTrue(source.rules.get(0).matches(e)); 470 470 source.rules.get(0).declaration.execute(e); 471 471 assertEquals(Functions.join(",", "Alpha", "Beta"), e.getCascade(Environment.DEFAULT_LAYER).get("sorted", null, String.class)); 472 472 473 473 source = new MapCSSStyleSource("way[ref] {sorted: join_list(\",\", sort_list(split(\";\", tag(\"ref\"))));}"); 474 474 source.loadStyleSource(); 475 475 e = new Environment(way1, new MultiCascade(), Environment.DEFAULT_LAYER, null); 476 assertTrue(source.rules.get(0). selector.matches(e));476 assertTrue(source.rules.get(0).matches(e)); 477 477 source.rules.get(0).declaration.execute(e); 478 478 assertEquals(Functions.join(",", "A8", "A9"), e.getCascade(Environment.DEFAULT_LAYER).get("sorted", null, String.class)); 479 479 } … … public void testCountRoles() throws Exception { 531 531 source.loadStyleSource(); 532 532 assertEquals(1, source.rules.size()); 533 533 e = new Environment(rel1, new MultiCascade(), Environment.DEFAULT_LAYER, null); 534 assertTrue(source.rules.get(0). selector.matches(e));534 assertTrue(source.rules.get(0).matches(e)); 535 535 source.rules.get(0).declaration.execute(e); 536 536 assertEquals((Integer) 1, e.getCascade(Environment.DEFAULT_LAYER).get("roles", null, Integer.class)); 537 537 } -
test/unit/org/openstreetmap/josm/tools/UtilsTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/UtilsTest.java b/test/unit/org/openstreetmap/josm/tools/UtilsTest.java index 3f4096f2f..5443b7892 100644
a b 13 13 import java.util.ArrayList; 14 14 import java.util.Arrays; 15 15 import java.util.Collections; 16 import java.util.HashMap; 16 17 import java.util.LinkedList; 17 18 import java.util.List; 18 19 import java.util.Locale; 20 import java.util.Map; 21 import java.util.TreeMap; 19 22 import java.util.regex.Pattern; 20 23 21 24 import org.junit.Rule; … … public void testToUnmodifiableList() { 573 576 assertEquals(Arrays.asList("foo", "bar", "baz"), Utils.toUnmodifiableList(new ArrayList<>(Arrays.asList("foo", "bar", "baz")))); 574 577 assertEquals(Arrays.asList("foo", "bar", "baz"), Utils.toUnmodifiableList(new LinkedList<>(Arrays.asList("foo", "bar", "baz")))); 575 578 } 579 580 /** 581 * Test of {@link Utils#toUnmodifiableMap} 582 */ 583 @Test 584 public void testToUnmodifiableMap() { 585 assertSame(Collections.emptyMap(), Utils.toUnmodifiableMap(null)); 586 assertSame(Collections.emptyMap(), Utils.toUnmodifiableMap(Collections.emptyMap())); 587 assertSame(Collections.emptyMap(), Utils.toUnmodifiableMap(new HashMap<>())); 588 assertSame(Collections.emptyMap(), Utils.toUnmodifiableMap(new TreeMap<>())); 589 assertEquals(Collections.singletonMap("foo", "bar"), Utils.toUnmodifiableMap(new HashMap<>(Collections.singletonMap("foo", "bar")))); 590 assertEquals(Collections.singletonMap("foo", "bar"), Utils.toUnmodifiableMap(new TreeMap<>(Collections.singletonMap("foo", "bar")))); 591 final Map<String, String> map4 = new HashMap<>(); 592 map4.put("jjj", "foo"); 593 map4.put("ooo", "bar"); 594 map4.put("sss", "baz"); 595 map4.put("mmm", ":-)"); 596 assertEquals(map4, Utils.toUnmodifiableMap(map4)); 597 } 576 598 }