Ticket #18802: 18802.patch

File 18802.patch, 98.3 KB (added by simon04, 3 months 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 { 
    342342         */
    343343        private List<TagInfoTag> convertStyleSheet() {
    344344            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())
    350347                    .filter(ConditionFactory.SimpleKeyValueCondition.class::isInstance)
    351348                    .map(ConditionFactory.SimpleKeyValueCondition.class::cast)
    352349                    .map(condition -> condition.asTag(null))
    Environment applyStylesheet(OsmPrimitive osm) { 
    393390                Environment env = new Environment(osm, mc, null, styleSource);
    394391                for (MapCSSRule r : styleSource.rules) {
    395392                    env.clearSelectorMatchingInformation();
    396                     if (r.selector.matches(env)) {
     393                    if (r.matches(env)) {
    397394                        // ignore selector range
    398395                        if (env.layer == null) {
    399396                            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  
    99import java.io.InputStream;
    1010import java.io.Reader;
    1111import java.io.StringReader;
    12 import java.lang.reflect.Method;
    13 import java.text.MessageFormat;
    1412import java.util.ArrayList;
    1513import java.util.Collection;
    16 import java.util.Collections;
    1714import java.util.HashMap;
    1815import java.util.HashSet;
    1916import java.util.Iterator;
    20 import java.util.LinkedHashMap;
    21 import java.util.LinkedHashSet;
    2217import java.util.LinkedList;
    2318import java.util.List;
    24 import java.util.Locale;
    2519import java.util.Map;
    2620import java.util.Objects;
    2721import java.util.Optional;
    2822import java.util.Set;
     23import java.util.function.Consumer;
    2924import java.util.function.Predicate;
    3025import java.util.regex.Matcher;
    3126import java.util.regex.Pattern;
     27import java.util.stream.Stream;
    3228
    3329import org.openstreetmap.josm.command.ChangePropertyCommand;
    3430import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
    3531import org.openstreetmap.josm.command.Command;
    3632import org.openstreetmap.josm.command.DeleteCommand;
    3733import org.openstreetmap.josm.command.SequenceCommand;
    38 import org.openstreetmap.josm.data.coor.LatLon;
    39 import org.openstreetmap.josm.data.osm.DataSet;
    4034import org.openstreetmap.josm.data.osm.IPrimitive;
    4135import org.openstreetmap.josm.data.osm.OsmPrimitive;
    42 import org.openstreetmap.josm.data.osm.OsmUtils;
    43 import org.openstreetmap.josm.data.osm.Relation;
    4436import org.openstreetmap.josm.data.osm.Tag;
    45 import org.openstreetmap.josm.data.osm.Way;
    4637import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
    4738import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    4839import org.openstreetmap.josm.data.validation.OsmValidator;
     
    5344import org.openstreetmap.josm.gui.mappaint.Keyword;
    5445import org.openstreetmap.josm.gui.mappaint.MultiCascade;
    5546import 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;
    5847import 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;
    6148import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
    62 import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
    6349import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
    64 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule.Declaration;
     50import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleIndex;
    6551import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
    66 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex;
    6752import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
    68 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.AbstractSelector;
    6953import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
    70 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;
    7154import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    7255import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
    7356import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
     
    7861import org.openstreetmap.josm.io.UTFInputStreamReader;
    7962import org.openstreetmap.josm.spi.preferences.Config;
    8063import 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;
    8464import org.openstreetmap.josm.tools.I18n;
    8565import org.openstreetmap.josm.tools.Logging;
    8666import org.openstreetmap.josm.tools.MultiMap;
    87 import org.openstreetmap.josm.tools.Territories;
    8867import org.openstreetmap.josm.tools.Utils;
    8968
    9069/**
     
    9271 * @since 6506
    9372 */
    9473public class MapCSSTagChecker extends Test.TagTest {
    95     MapCSSTagCheckerIndex indexData;
     74    MapCSSStyleIndex indexData;
     75    final Map<MapCSSRule, TagCheck> ruleToCheckMap = new HashMap<>();
    9676    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;
    13779
    13880    /**
    13981     * The preference key for tag checker source entries.
    public ParseResult(List<TagCheck> parseChecks, Collection<Throwable> parseErrors 
    284226     */
    285227    public static class TagCheck implements Predicate<OsmPrimitive> {
    286228        /** The selector of this {@code TagCheck} */
    287         protected final GroupedMapCSSRule rule;
     229        protected final MapCSSRule rule;
    288230        /** Commands to apply in order to fix a matching primitive */
    289         protected final List<FixCommand> fixCommands = new ArrayList<>();
     231        protected final List<FixCommand> fixCommands;
    290232        /** 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;
    292234        /** An {@link org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.AssignmentInstruction}-{@link Severity} pair.
    293235         * 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;
    297237        /** MapCSS Classes to set on matching primitives */
    298         protected final Set<String> setClassExpressions = new HashSet<>();
     238        protected final Collection<String> setClassExpressions;
    299239        /** Denotes whether the object should be deleted for fixing it */
    300240        protected boolean deletion;
    301241        /** A string used to group similar tests */
    302242        protected String group;
    303243
    304         TagCheck(GroupedMapCSSRule rule) {
     244        TagCheck(MapCSSRule rule) {
    305245            this.rule = rule;
     246            this.fixCommands = new ArrayList<>();
     247            this.alternatives = new ArrayList<>();
     248            this.errors = new HashMap<>();
     249            this.setClassExpressions = new HashSet<>();
    306250        }
    307251
    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        }
    309261
    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);
    321264        }
    322265
    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 {
    324269            final TagCheck check = new TagCheck(rule);
     270            final Map<String, Boolean> assertions = new HashMap<>();
    325271            for (Instruction i : rule.declaration.instructions) {
    326272                if (i instanceof Instruction.AssignmentInstruction) {
    327273                    final Instruction.AssignmentInstruction ai = (Instruction.AssignmentInstruction) i;
    static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 
    338284                                : ai.val instanceof Keyword
    339285                                ? ((Keyword) ai.val).val
    340286                                : 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);
    348296                        } else if ("fixAdd".equals(ai.key)) {
    349297                            check.fixCommands.add(FixCommand.fixAdd(ai.val));
    350298                        } else if ("fixRemove".equals(ai.key)) {
    static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 
    361309                        } else if (val != null && "suggestAlternative".equals(ai.key)) {
    362310                            check.alternatives.add(val);
    363311                        } else if (val != null && "assertMatch".equals(ai.key)) {
    364                             check.assertions.put(val, Boolean.TRUE);
     312                            assertions.put(val, Boolean.TRUE);
    365313                        } else if (val != null && "assertNoMatch".equals(ai.key)) {
    366                             check.assertions.put(val, Boolean.FALSE);
     314                            assertions.put(val, Boolean.FALSE);
    367315                        } else if (val != null && "group".equals(ai.key)) {
    368316                            check.group = val;
    369317                        } else if (ai.key.startsWith("-")) {
    static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataExc 
    384332                        "More than one "+POSSIBLE_THROWS+" given! You should specify a single validation error message for "
    385333                                + rule.selectors);
    386334            }
    387             return check;
     335            if (assertionConsumer != null) {
     336                MapCSSTagCheckerAsserts.checkAsserts(check, assertions, assertionConsumer);
     337            }
     338            return check.toImmutable();
    388339        }
    389340
    390         static ParseResult readMapCSS(Reader css) throws ParseException {
     341        static ParseResult readMapCSS(Reader css, AssertionConsumer assertionConsumer) throws ParseException {
    391342            CheckParameterUtil.ensureParameterNotNull(css, "css");
    392343
    393344            final MapCSSStyleSource source = new MapCSSStyleSource("");
    static ParseResult readMapCSS(Reader css) throws ParseException { 
    397348            }
    398349            // Ignore "meta" rule(s) from external rules of JOSM wiki
    399350            source.removeMetaRules();
    400             // group rules with common declaration block
    401             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             }
    411351            List<TagCheck> parseChecks = new ArrayList<>();
    412             for (Map.Entry<Declaration, List<Selector>> map : g.entrySet()) {
     352            for (MapCSSRule rule : source.rules) {
    413353                try {
    414                     parseChecks.add(TagCheck.ofMapCSSRule(
    415                             new GroupedMapCSSRule(map.getValue(), map.getKey())));
     354                    parseChecks.add(TagCheck.ofMapCSSRule(rule, assertionConsumer));
    416355                } catch (IllegalDataException e) {
    417356                    Logging.error("Cannot add MapCss rule: "+e.getMessage());
    418357                    source.logError(e);
    Selector whichSelectorMatchesEnvironment(Environment env) { 
    443382
    444383        /**
    445384         * 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}.
    447386         * @param matchingSelector matching selector
    448387         * @param index index
    449388         * @param type selector type ("key", "value" or "tag")
    450389         * @param p OSM primitive
    451390         * @return argument value, can be {@code null}
    452391         */
    453         static String determineArgument(OptimizedGeneralSelector matchingSelector, int index, String type, OsmPrimitive p) {
     392        static String determineArgument(GeneralSelector matchingSelector, int index, String type, OsmPrimitive p) {
    454393            try {
    455394                final Condition c = matchingSelector.getConditions().get(index);
    456395                final Tag tag = c instanceof Condition.ToTagConvertable
    static String determineArgument(OptimizedGeneralSelector matchingSelector, int i 
    482421        static String insertArguments(Selector matchingSelector, String s, OsmPrimitive p) {
    483422            if (s != null && matchingSelector instanceof Selector.ChildOrParentSelector) {
    484423                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)) {
    486425                return s;
    487426            }
    488427            final Matcher m = Pattern.compile("\\{(\\d+)\\.(key|value|tag)\\}").matcher(s);
    489428            final StringBuffer sb = new StringBuffer();
    490429            while (m.find()) {
    491                 final String argument = determineArgument((Selector.OptimizedGeneralSelector) matchingSelector,
     430                final String argument = determineArgument((GeneralSelector) matchingSelector,
    492431                        Integer.parseInt(m.group(1)), m.group(2), p);
    493432                try {
    494433                    // Perform replacement with null-safe + regex-safe handling
    public String toString() { 
    635574            return res;
    636575        }
    637576
    638         /**
    639          * Returns the set of tagchecks on which this check depends on.
    640          * @param schecks the collection of tagcheks to search in
    641          * @return the set of tagchecks on which this check depends on
    642          * @since 7881
    643          */
    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 selectors
    666          * @since 7881
    667          */
    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         }
    681577    }
    682578
    683579    static class MapCSSTagCheckerAndRule extends MapCSSTagChecker {
    684         public final GroupedMapCSSRule rule;
     580        public final MapCSSRule rule;
    685581
    686         MapCSSTagCheckerAndRule(GroupedMapCSSRule rule) {
     582        MapCSSTagCheckerAndRule(MapCSSRule rule) {
    687583            this.rule = rule;
    688584        }
    689585
    public String toString() { 
    693589        }
    694590    }
    695591
     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
    696604    /**
    697605     * Obtains all {@link TestError}s for the {@link OsmPrimitive} {@code p}.
    698606     * @param p The OSM primitive
    public String toString() { 
    702610    public synchronized Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity) {
    703611        final List<TestError> res = new ArrayList<>();
    704612        if (indexData == null) {
    705             indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverity, MapCSSTagCheckerIndex.ALL_TESTS);
     613            indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverity, ALL_TESTS);
    706614        }
    707615
    708         MapCSSRuleIndex matchingRuleIndex = indexData.get(p);
    709616
    710617        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 index
    712         Declaration lastDeclUsed = null;
    713618
    714         Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(p);
     619        Iterator<MapCSSRule> candidates = indexData.getRuleCandidates(p);
    715620        while (candidates.hasNext()) {
    716621            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));
    720632                if (check != null) {
    721                     if (r.declaration == lastDeclUsed)
    722                         continue; // don't apply one declaration more than once
    723                     lastDeclUsed = r.declaration;
    724 
    725633                    r.declaration.execute(env);
    726634                    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))) {
    728636                            addIfNotSimilar(e, res);
    729637                        }
    730638                    }
    private static boolean highlightedIsEqual(Collection<?> highlighted, Collection< 
    774682        return false;
    775683    }
    776684
    777     private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,
     685    static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,
    778686            Collection<Set<TagCheck>> checksCol) {
    779687        final List<TestError> r = new ArrayList<>();
    780688        final Environment env = new Environment(p, new MultiCascade(), Environment.DEFAULT_LAYER, null);
    public void check(OsmPrimitive p) { 
    812720        }
    813721    }
    814722
     723    /**
     724     * A handler for assertion error messages (for not fulfilled "assertMatch", "assertNoMatch").
     725     */
     726    @FunctionalInterface
     727    interface AssertionConsumer extends Consumer<String> {
     728    }
     729
    815730    /**
    816731     * Adds a new MapCSS config file from the given URL.
    817732     * @param url The unique URL of the MapCSS config file
    public void check(OsmPrimitive p) { 
    821736     * @since 7275
    822737     */
    823738    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 {
    824745        CheckParameterUtil.ensureParameterNotNull(url, "url");
    825746        ParseResult result;
    826747        try (CachedFile cache = new CachedFile(url);
    public synchronized ParseResult addMapCSS(String url) throws ParseException, IOE 
    829750             Reader reader = new BufferedReader(UTFInputStreamReader.create(s))) {
    830751            if (zip != null)
    831752                I18n.addTexts(cache.getFile());
    832             result = TagCheck.readMapCSS(reader);
     753            result = TagCheck.readMapCSS(reader, assertionConsumer);
    833754            checks.remove(url);
    834755            checks.putAll(url, result.parseChecks);
    835756            indexData = null;
    836             // Check assertions, useful for development of local files
    837             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             }
    842757        }
    843758        return result;
    844759    }
    public synchronized void initialize() throws Exception { 
    870785                Logging.warn(ex);
    871786            }
    872787        }
    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();
    968789    }
    969790
    970791    /**
    public synchronized void startTest(ProgressMonitor progressMonitor) { 
    988809        super.startTest(progressMonitor);
    989810        super.setShowElements(true);
    990811        if (indexData == null) {
    991             indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), MapCSSTagCheckerIndex.ALL_TESTS);
     812            indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ALL_TESTS);
    992813        }
    993814        tested.clear();
    994815    }
    public synchronized void endTest() { 
    1001822
    1002823            // rebuild index with a reduced set of rules (those that use ChildOrParentSelector) and thus may have left selectors
    1003824            // matching the previously tested elements
    1004             indexData = new MapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), MapCSSTagCheckerIndex.ONLY_SELECTED_TESTS);
     825            indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ONLY_SELECTED_TESTS);
    1005826
    1006827            Set<OsmPrimitive> surrounding = new HashSet<>();
    1007828            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.
     2package org.openstreetmap.josm.data.validation.tests;
     3
     4import java.lang.reflect.Method;
     5import java.text.MessageFormat;
     6import java.util.ArrayList;
     7import java.util.Collection;
     8import java.util.Collections;
     9import java.util.HashSet;
     10import java.util.List;
     11import java.util.Map;
     12import java.util.Optional;
     13import java.util.Set;
     14import java.util.stream.Collectors;
     15
     16import org.openstreetmap.josm.command.Command;
     17import org.openstreetmap.josm.data.coor.LatLon;
     18import org.openstreetmap.josm.data.osm.DataSet;
     19import org.openstreetmap.josm.data.osm.OsmPrimitive;
     20import org.openstreetmap.josm.data.osm.OsmUtils;
     21import org.openstreetmap.josm.data.osm.Relation;
     22import org.openstreetmap.josm.data.osm.Way;
     23import org.openstreetmap.josm.data.validation.TestError;
     24import org.openstreetmap.josm.gui.mappaint.Environment;
     25import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory;
     26import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
     27import org.openstreetmap.josm.gui.mappaint.mapcss.Functions;
     28import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
     29import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
     30import org.openstreetmap.josm.tools.DefaultGeoProperty;
     31import org.openstreetmap.josm.tools.GeoProperty;
     32import org.openstreetmap.josm.tools.GeoPropertyIndex;
     33import org.openstreetmap.josm.tools.Logging;
     34import org.openstreetmap.josm.tools.Territories;
     35
     36/**
     37 * Utility class for checking rule assertions of {@link MapCSSTagChecker.TagCheck}.
     38 */
     39final 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() { 
    359359         * @throws PatternSyntaxException if the value syntax is invalid
    360360         */
    361361        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);
    363363            CheckParameterUtil.ensureThat(!considerValAsKey, "considerValAsKey is not supported");
    364364            CheckParameterUtil.ensureThat(SUPPORTED_OPS.contains(op), "Op must be REGEX or NREGEX");
    365365            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.
     2package org.openstreetmap.josm.gui.mappaint.mapcss;
     3
     4import java.util.List;
     5import java.util.Objects;
     6
     7import org.openstreetmap.josm.gui.mappaint.Environment;
     8import org.openstreetmap.josm.gui.mappaint.StyleSource;
     9import org.openstreetmap.josm.tools.Utils;
     10
     11/**
     12 * A declaration is a list of {@link Instruction}s
     13 */
     14public 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  
    1515 *
    1616 * For example a simple assignment like <code>width: 3;</code>, but may also
    1717 * 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.
    1919 */
    2020@FunctionalInterface
    2121public 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; 
    2525import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory;
    2626import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
    2727import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.Op;
     28import org.openstreetmap.josm.gui.mappaint.mapcss.Declaration;
    2829import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
    2930import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
    3031import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.NullExpression;
    import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction; 
    3233import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
    3334import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
    3435import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
    35 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule.Declaration;
    3636import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
    3737import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
    3838import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
    void rule(): 
    649649{
    650650    selectors=selectors()
    651651    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));
    656654    }
    657655}
    658656
  • 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  
    22package org.openstreetmap.josm.gui.mappaint.mapcss;
    33
    44import java.util.List;
    5 import java.util.Objects;
    65import java.util.stream.Collectors;
    76
    87import org.openstreetmap.josm.gui.mappaint.Environment;
    9 import org.openstreetmap.josm.gui.mappaint.StyleSource;
    108import org.openstreetmap.josm.tools.Utils;
    119
    1210/**
     
    2119    /**
    2220     * The selector. If it matches, this rule should be applied
    2321     */
    24     public final Selector selector;
     22    public final List<Selector> selectors;
    2523    /**
    2624     * The instructions for this selector
    2725     */
    2826    public final Declaration declaration;
    2927
    3028    /**
    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
    3232     */
    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;
    8436    }
    8537
    8638    /**
    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
    9046     */
    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));
    9449    }
    9550
    9651    /**
    9752     * <p>Executes the instructions against the environment {@code env}</p>
    9853     *
    9954     * @param env the environment
     55     * @see Declaration#execute
    10056     */
    10157    public void execute(Environment env) {
    10258        declaration.execute(env);
    public int compareTo(MapCSSRule o) { 
    10965
    11066    @Override
    11167    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()
    11371                .map(String::valueOf)
    11472                .collect(Collectors.joining("\n  ", " {\n  ", "\n}"));
     73        return selectorsString + declarationString;
    11574    }
    11675}
    11776
  • .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.
     2package org.openstreetmap.josm.gui.mappaint.mapcss;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.text.MessageFormat;
     7import java.util.Iterator;
     8import java.util.Map;
     9import java.util.stream.Collectors;
     10import java.util.stream.Stream;
     11
     12import org.openstreetmap.josm.data.osm.INode;
     13import org.openstreetmap.josm.data.osm.IPrimitive;
     14import org.openstreetmap.josm.data.osm.IRelation;
     15import org.openstreetmap.josm.data.osm.IWay;
     16import org.openstreetmap.josm.data.osm.OsmUtils;
     17import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex;
     18import org.openstreetmap.josm.tools.JosmRuntimeException;
     19import org.openstreetmap.josm.tools.Logging;
     20
     21/**
     22 * Store indexes of {@link MapCSSRule}s using {@link MapCSSRuleIndex} differentiated by {@linkplain Selector#getBase() base}
     23 */
     24public 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  
    1313import java.io.StringReader;
    1414import java.lang.reflect.Field;
    1515import java.nio.charset.StandardCharsets;
    16 import java.text.MessageFormat;
    1716import java.util.ArrayList;
    1817import java.util.BitSet;
    1918import java.util.Collections;
     
    2524import java.util.Map;
    2625import java.util.Map.Entry;
    2726import java.util.NoSuchElementException;
     27import java.util.Optional;
    2828import java.util.Set;
    2929import java.util.concurrent.locks.ReadWriteLock;
    3030import java.util.concurrent.locks.ReentrantReadWriteLock;
     
    3333import java.util.zip.ZipFile;
    3434
    3535import org.openstreetmap.josm.data.Version;
    36 import org.openstreetmap.josm.data.osm.INode;
    3736import org.openstreetmap.josm.data.osm.IPrimitive;
    38 import org.openstreetmap.josm.data.osm.IRelation;
    39 import org.openstreetmap.josm.data.osm.IWay;
    4037import org.openstreetmap.josm.data.osm.KeyValueVisitor;
    4138import org.openstreetmap.josm.data.osm.Node;
    42 import org.openstreetmap.josm.data.osm.OsmUtils;
    4339import org.openstreetmap.josm.data.osm.Tagged;
    4440import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
    4541import org.openstreetmap.josm.gui.mappaint.Cascade;
     
    5551import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
    5652import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition;
    5753import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition;
    58 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
    5954import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
    60 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;
    6155import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    6256import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
    6357import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
     
    8882     */
    8983    public final List<MapCSSRule> rules = new ArrayList<>();
    9084    /**
    91      * Rules for nodes
     85     * Index of rules in this style file
    9286     */
    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();
    11488
    11589    private Color backgroundColorOverride;
    11690    private String css;
    public void initIndex() { 
    291265            Collections.sort(rules);
    292266            for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
    293267                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()) {
    314271                        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                        }
    315286                    }
    316287                }
    317288            }
    public void loadStyleSource(boolean metadataOnly) { 
    423394        try {
    424395            init();
    425396            rules.clear();
    426             nodeRules.clear();
    427             wayRules.clear();
    428             wayNoAreaRules.clear();
    429             relationRules.clear();
    430             multipolygonRules.clear();
    431             canvasRules.clear();
     397            ruleIndex.clear();
    432398            // remove "areaStyle" pseudo classes intended only for validator (causes StackOverflowError otherwise), see #16183
    433399            removeAreaStylePseudoClass = url == null || !url.contains("validator"); // resource://data/validator/ or xxx.validator.mapcss
    434400            try (InputStream in = getSourceInputStream()) {
    public void loadStyleSource(boolean metadataOnly) { 
    466432                return;
    467433            }
    468434            // 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());
    520436            loaded = true;
    521437        } finally {
    522438            STYLE_SOURCE_LOCK.writeLock().unlock();
    private void loadSettings() { 
    599515
    600516        // Parse rules
    601517        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) {
    604520                if (Selector.BASE_SETTING.equals(gs.getBase())) {
    605                     loadSettings(r, gs, env);
     521                    loadSettings(r, ((GeneralSelector) gs), env);
    606522                } else if (Selector.BASE_SETTINGS.equals(gs.getBase())) {
    607                     loadSettings(r, gs, envGroups);
     523                    loadSettings(r, ((GeneralSelector) gs), envGroups);
    608524                }
    609525            }
    610526        }
    private Cascade constructSpecial(String type) { 
    650566        Environment env = new Environment(n, mc, "default", this);
    651567
    652568        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);
    661574            }
    662575        }
    663576        return mc.getCascade("default");
    public Color getBackgroundColorOverride() { 
    670583
    671584    @Override
    672585    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         }
    693586
    694587        Environment env = new Environment(osm, mc, null, this);
    695588        // the declaration indices are sorted, so it suffices to save the last used index
    696589        int lastDeclUsed = -1;
    697590
    698         Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(osm);
     591        Iterator<MapCSSRule> candidates = ruleIndex.getRuleCandidates(osm);
    699592        while (candidates.hasNext()) {
    700593            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                }
    706601                if (s.getRange().contains(scale)) {
    707602                    mc.range = Range.cut(mc.range, s.getRange());
    708603                } else {
    public boolean evalSupportsDeclCondition(String feature, Object val) { 
    757652     * @since 13633
    758653     */
    759654    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()));
    769656    }
    770657
    771658    /**
  • 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  
    3131import org.openstreetmap.josm.gui.mappaint.Range;
    3232import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
    3333import org.openstreetmap.josm.tools.CheckParameterUtil;
     34import org.openstreetmap.josm.tools.CompositeList;
    3435import org.openstreetmap.josm.tools.Geometry;
    3536import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    3637import org.openstreetmap.josm.tools.Logging;
     
    105106     */
    106107    Range getRange();
    107108
     109    String getBase();
     110
    108111    /**
    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
    119114     */
    120     Selector optimizedBaseCheck();
     115    List<Condition> getConditions();
    121116
    122117    /**
    123118     * The type of child of parent selector.
    public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrP 
    166161            this.type = type;
    167162        }
    168163
     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
    169175        /**
    170176         * <p>Finds the first referrer matching {@link #left}</p>
    171177         *
    private void visitBBox(Environment e, AbstractFinder finder) { 
    396402            boolean withNodes = finder instanceof ContainsFinder;
    397403            if (e.osm.getDataSet() == null) {
    398404                // 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)) {
    401407                    finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
    402408                }
    403                 if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) {
     409                if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) {
    404410                    finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
    405411                }
    406                 if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) {
     412                if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) {
    407413                    finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox()));
    408414                }
    409415            } else {
    public boolean matches(Environment e) { 
    454460
    455461            } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {
    456462                e.parent = e.osm;
    457                 if (right instanceof OptimizedGeneralSelector
     463                if (right instanceof GeneralSelector
    458464                        && e.osm.getDataSet() != null
    459                         && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
     465                        && ((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
    460466                    final CrossingFinder crossingFinder = new CrossingFinder(e);
    461467                    crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
    462468                }
    public Range getRange() { 
    533539            return right.getRange();
    534540        }
    535541
    536         @Override
    537         public Selector optimizedBaseCheck() {
    538             return new ChildOrParentSelector(left, link, right.optimizedBaseCheck(), type);
    539         }
    540 
    541542        @Override
    542543        public String toString() {
    543544            return left.toString() + ' ' + (ChildOrParentSelectorType.PARENT == type ? '<' : '>') + link + ' ' + right;
    public boolean matches(Environment env) { 
    576577            return true;
    577578        }
    578579
    579         /**
    580          * Returns the list of conditions.
    581          * @return the list of conditions
    582          */
     580        @Override
    583581        public List<Condition> getConditions() {
    584582            return conds;
    585583        }
    public boolean matches(Environment env) { 
    602600        }
    603601
    604602        @Override
    605         public Subpart getSubpart() {
    606             throw new UnsupportedOperationException("Not supported yet.");
     603        public String getBase() {
     604            throw new UnsupportedOperationException();
    607605        }
    608606
    609607        @Override
    610         public Range getRange() {
    611             throw new UnsupportedOperationException("Not supported yet.");
     608        public Subpart getSubpart() {
     609            throw new UnsupportedOperationException();
    612610        }
    613611
    614612        @Override
    615         public Selector optimizedBaseCheck() {
     613        public Range getRange() {
    616614            throw new UnsupportedOperationException();
    617615        }
    618616
    public String toString() { 
    625623    /**
    626624     * General selector. See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Selectors">wiki</a>
    627625     */
    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 {
    642627
    643         @Override
    644         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#optimizedBaseCheck
    652      */
    653     class OptimizedGeneralSelector extends AbstractSelector {
    654628        public final String base;
    655629        public final Range range;
    656630        public final Subpart subpart;
    657631
    658         public OptimizedGeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) {
     632        public GeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) {
    659633            super(conds);
    660634            this.base = checkBase(base);
    661635            this.range = Objects.requireNonNull(range, "range");
    662636            this.subpart = subpart != null ? subpart : Subpart.DEFAULT_SUBPART;
    663637        }
    664638
    665         public OptimizedGeneralSelector(GeneralSelector s) {
    666             this(s.base, s.range, s.conds, s.subpart);
    667         }
    668 
    669639        @Override
    670640        public Subpart getSubpart() {
    671641            return subpart;
    public Range getRange() { 
    676646            return range;
    677647        }
    678648
     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
    679658        /**
    680659         * Set base and check if this is a known value.
    681660         * @param base value for base
    private static String checkBase(String base) { 
    698677            }
    699678        }
    700679
     680        @Override
    701681        public String getBase() {
    702682            return base;
    703683        }
    public boolean matchesBase(Environment e) { 
    734714            return matchesBase(e.osm);
    735715        }
    736716
    737         @Override
    738         public Selector optimizedBaseCheck() {
    739             throw new UnsupportedOperationException();
    740         }
    741 
    742717        public static Range fromLevel(int a, int b) {
    743718            // for input validation in Range constructor below
    744719            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  
    4747import java.util.Iterator;
    4848import java.util.List;
    4949import java.util.Locale;
     50import java.util.Map;
    5051import java.util.Optional;
    5152import java.util.concurrent.ExecutionException;
    5253import java.util.concurrent.Executor;
     
    6869import javax.script.ScriptEngine;
    6970import javax.script.ScriptEngineManager;
    7071
     72import com.kitfox.svg.xml.XMLParseUtil;
    7173import org.openstreetmap.josm.spi.preferences.Config;
    7274
    7375/**
    public B get(int index) { 
    767769        }
    768770    }
    769771
     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
    770787    /**
    771788     * Returns the first not empty string in the given candidates, otherwise the default string.
    772789     * @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 { 
    197197            }
    198198            nc.zoomTo(ProjectionRegistry.getProjection().latlon2eastNorth(center), scale);
    199199            if (checkScale) {
    200                 int lvl = Selector.OptimizedGeneralSelector.scale2level(nc.getDist100Pixel());
     200                int lvl = Selector.GeneralSelector.scale2level(nc.getDist100Pixel());
    201201                Assert.assertEquals(17, lvl);
    202202            }
    203203
  • 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  
    1515import java.util.List;
    1616import java.util.Set;
    1717
     18import org.junit.Before;
    1819import org.junit.Rule;
    1920import org.junit.Test;
    2021import org.openstreetmap.josm.TestUtils;
     
    2728import org.openstreetmap.josm.data.osm.IPrimitive;
    2829import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2930import org.openstreetmap.josm.data.osm.OsmUtils;
     31import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
     32import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    3033import org.openstreetmap.josm.data.validation.Severity;
    3134import org.openstreetmap.josm.data.validation.TestError;
    3235import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.ParseResult;
     
    5255    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    5356    public JOSMTestRules test = new JOSMTestRules().projection().territories().preferences();
    5457
     58    /**
     59     * Setup test.
     60     */
     61    @Before
     62    public void setUp() {
     63        MapCSSTagCheckerAsserts.clear();
     64    }
     65
    5566    static MapCSSTagChecker buildTagChecker(String css) throws ParseException {
    5667        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());
    5871        return test;
    5972    }
    6073
    public void testNaturalMarsh() throws ParseException { 
    7184                "   fixRemove: \"{0.key}\";\n" +
    7285                "   fixAdd: \"natural=wetland\";\n" +
    7386                "   fixAdd: \"wetland=marsh\";\n" +
    74                 "}"));
     87                "}"), null);
    7588        final List<TagCheck> checks = result.parseChecks;
    7689        assertEquals(1, checks.size());
    7790        assertTrue(result.parseErrors.isEmpty());
    public void testTicket10913() throws ParseException { 
    110123                "throwError: \"error\";" +
    111124                "fixChangeKey: \"highway => construction\";\n" +
    112125                "fixAdd: \"highway=construction\";\n" +
    113                 "}")).parseChecks.get(0);
     126                "}"), null).parseChecks.get(0);
    114127        final Command command = check.fixPrimitive(p);
    115128        assertTrue(command instanceof SequenceCommand);
    116129        final Iterator<PseudoCommand> it = command.getChildren().iterator();
    public void testTicket10859() throws ParseException { 
    155168    @Test
    156169    public void testTicket13630() throws ParseException {
    157170        ParseResult result = TagCheck.readMapCSS(new StringReader(
    158                 "node[crossing=zebra] {fixRemove: \"crossing=zebra\";}"));
     171                "node[crossing=zebra] {fixRemove: \"crossing=zebra\";}"), null);
    159172        assertTrue(result.parseChecks.isEmpty());
    160173        assertEquals(1, result.parseErrors.size());
    161174    }
    public void testPreprocessing() throws ParseException { 
    181194    public void testInit() throws Exception {
    182195        MapCSSTagChecker c = new MapCSSTagChecker();
    183196        c.initialize();
     197    }
    184198
     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();
    185206        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);
    188211        }
     212
    189213        for (String msg : assertionErrors) {
    190214            Logging.error(msg);
    191215        }
    public void testAssertInsideCountry() throws ParseException { 
    204228                "  assertMatch: \"node amenity=parking\";\n" +
    205229                "  assertNoMatch: \"node amenity=restaurant\";\n" +
    206230                "}");
    207         Set<String> errors = test.checkAsserts(test.checks.get("test"));
    208         assertTrue(errors.toString(), errors.isEmpty());
     231        assertNotNull(test);
    209232    }
    210233
    211234    /**
    public void testTicket17058() throws ParseException { 
    220243                "  assertMatch: \"way name=Hauptstraße\";\n" +
    221244                "  assertNoMatch: \"way name=Hauptstrasse\";\n" +
    222245                "}");
    223         Set<String> errors = test.checkAsserts(test.checks.get("test"));
    224         assertTrue(errors.toString(), errors.isEmpty());
     246        assertNotNull(test);
    225247    }
    226248
    227249    /**
    public void testTicket13762() throws ParseException { 
    233255        final ParseResult parseResult = TagCheck.readMapCSS(new StringReader("" +
    234256                "meta[lang=de] {\n" +
    235257                "    title: \"Deutschlandspezifische Regeln\";" +
    236                 "}"));
     258                "}"), null);
    237259        assertTrue(parseResult.parseErrors.isEmpty());
    238260    }
    239261
  • 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) { 
    7373         MapCSSStyleSource source = new MapCSSStyleSource(css);
    7474         source.loadStyleSource();
    7575         assertEquals(1, source.rules.size());
    76          return (ChildOrParentSelector) source.rules.get(0).selector;
     76         return (ChildOrParentSelector) source.rules.get(0).selectors.get(0);
    7777    }
    7878
    7979    @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 { 
    451451        source.loadStyleSource();
    452452        assertEquals(1, source.rules.size());
    453453        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));
    455455        source.rules.get(0).declaration.execute(e);
    456456        assertEquals("x2;x10", e.getCascade(Environment.DEFAULT_LAYER).get("refs", null, String.class));
    457457    }
    public void testSort() throws Exception { 
    466466        source.loadStyleSource();
    467467        assertEquals(1, source.rules.size());
    468468        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));
    470470        source.rules.get(0).declaration.execute(e);
    471471        assertEquals(Functions.join(",", "Alpha", "Beta"), e.getCascade(Environment.DEFAULT_LAYER).get("sorted", null, String.class));
    472472
    473473        source = new MapCSSStyleSource("way[ref] {sorted: join_list(\",\", sort_list(split(\";\", tag(\"ref\"))));}");
    474474        source.loadStyleSource();
    475475        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));
    477477        source.rules.get(0).declaration.execute(e);
    478478        assertEquals(Functions.join(",", "A8", "A9"), e.getCascade(Environment.DEFAULT_LAYER).get("sorted", null, String.class));
    479479    }
    public void testCountRoles() throws Exception { 
    531531        source.loadStyleSource();
    532532        assertEquals(1, source.rules.size());
    533533        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));
    535535        source.rules.get(0).declaration.execute(e);
    536536        assertEquals((Integer) 1, e.getCascade(Environment.DEFAULT_LAYER).get("roles", null, Integer.class));
    537537    }
  • 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  
    1313import java.util.ArrayList;
    1414import java.util.Arrays;
    1515import java.util.Collections;
     16import java.util.HashMap;
    1617import java.util.LinkedList;
    1718import java.util.List;
    1819import java.util.Locale;
     20import java.util.Map;
     21import java.util.TreeMap;
    1922import java.util.regex.Pattern;
    2023
    2124import org.junit.Rule;
    public void testToUnmodifiableList() { 
    573576        assertEquals(Arrays.asList("foo", "bar", "baz"), Utils.toUnmodifiableList(new ArrayList<>(Arrays.asList("foo", "bar", "baz"))));
    574577        assertEquals(Arrays.asList("foo", "bar", "baz"), Utils.toUnmodifiableList(new LinkedList<>(Arrays.asList("foo", "bar", "baz"))));
    575578    }
     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    }
    576598}