Changeset 10674 in josm


Ignore:
Timestamp:
2016-07-29T22:15:28+02:00 (5 years ago)
Author:
Don-vip
Message:

fix #13239, fix #13240 - Java 8: MapCSS Condition class (patches by michael2402) - gsoc-core

Location:
trunk
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/OsmUtils.java

    r8846 r10674  
    7575            throw new IllegalArgumentException("Expecting n/node/w/way/r/relation/area, but got '" + x[0] + '\'');
    7676        }
    77         for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
    78             p.put(i.getKey(), i.getValue());
     77        if (x.length > 1) {
     78            for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
     79                p.put(i.getKey(), i.getValue());
     80            }
    7981        }
    8082        return p;
  • trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java

    r10672 r10674  
    421421            try {
    422422                final Condition c = matchingSelector.getConditions().get(index);
    423                 final Tag tag = c instanceof Condition.KeyCondition
    424                         ? ((Condition.KeyCondition) c).asTag(p)
    425                         : c instanceof Condition.SimpleKeyValueCondition
    426                         ? ((Condition.SimpleKeyValueCondition) c).asTag()
    427                         : c instanceof Condition.KeyValueCondition
    428                         ? ((Condition.KeyValueCondition) c).asTag()
     423                final Tag tag = c instanceof Condition.ToTagConvertable
     424                        ? ((Condition.ToTagConvertable) c).asTag(p)
    429425                        : null;
    430426                if (tag == null) {
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java

    r10659 r10674  
    66import java.text.MessageFormat;
    77import java.util.Arrays;
    8 import java.util.Collection;
    98import java.util.EnumSet;
    109import java.util.Map;
    1110import java.util.Objects;
    1211import java.util.Set;
     12import java.util.function.BiFunction;
     13import java.util.function.IntFunction;
    1314import java.util.function.Predicate;
    1415import java.util.regex.Pattern;
     
    2829import org.openstreetmap.josm.tools.CheckParameterUtil;
    2930import org.openstreetmap.josm.tools.Predicates;
    30 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
    3131import org.openstreetmap.josm.tools.Utils;
    3232
     33/**
     34 * This is a condition that needs to be fulfilled in order to apply a MapCSS style.
     35 */
    3336@FunctionalInterface
    3437public interface Condition {
    3538
     39    /**
     40     * Checks if the condition applies in the given MapCSS {@link Environment}.
     41     * @param e The environment to check. May not be <code>null</code>.
     42     * @return <code>true</code> if the condition applies.
     43     */
    3644    boolean applies(Environment e);
    3745
     46    /**
     47     * Create a new condition that checks the key and the value of the object.
     48     * @param k The key.
     49     * @param v The reference value
     50     * @param op The operation to use when comparing the value
     51     * @param context The type of context to use.
     52     * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}.
     53     * @return The new condition.
     54     */
    3855    static Condition createKeyValueCondition(String k, String v, Op op, Context context, boolean considerValAsKey) {
    3956        switch (context) {
     
    5976    }
    6077
     78    /**
     79     * Create a condition in which the key and the value need to match a given regexp
     80     * @param k The key regexp
     81     * @param v The value regexp
     82     * @param op The operation to use when comparing the key and the value.
     83     * @return The new condition.
     84     */
    6185    static Condition createRegexpKeyRegexpValueCondition(String k, String v, Op op) {
    6286        return new RegexpKeyValueRegexpCondition(k, v, op);
    6387    }
    6488
     89    /**
     90     * Creates a condition that checks the given key.
     91     * @param k The key to test for
     92     * @param not <code>true</code> to invert the match
     93     * @param matchType The match type to check for.
     94     * @param context The context this rule is found in.
     95     * @return the new condition.
     96     */
    6597    static Condition createKeyCondition(String k, boolean not, KeyMatchType matchType, Context context) {
    6698        switch (context) {
     
    79111    }
    80112
     113    /**
     114     * Create a new pseudo class condition
     115     * @param id The id of the pseudo class
     116     * @param not <code>true</code> to invert the condition
     117     * @param context The context the class is found in.
     118     * @return The new condition
     119     */
    81120    static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
    82121        return PseudoClassCondition.createPseudoClassCondition(id, not, context);
    83122    }
    84123
     124    /**
     125     * Create a new class condition
     126     * @param id The id of the class to match
     127     * @param not <code>true</code> to invert the condition
     128     * @param context Ignored
     129     * @return The new condition
     130     */
    85131    static ClassCondition createClassCondition(String id, boolean not, Context context) {
    86132        return new ClassCondition(id, not);
    87133    }
    88134
     135    /**
     136     * Create a new condition that a expression needs to be fulfilled
     137     * @param e the expression to check
     138     * @param context Ignored
     139     * @return The new condition
     140     */
    89141    static ExpressionCondition createExpressionCondition(Expression e, Context context) {
    90142        return new ExpressionCondition(e);
     
    96148    enum Op {
    97149        /** The value equals the given reference. */
    98         EQ,
     150        EQ(Objects::equals),
    99151        /** The value does not equal the reference. */
    100         NEQ,
     152        NEQ(EQ),
    101153        /** The value is greater than or equal to the given reference value (as float). */
    102         GREATER_OR_EQUAL,
     154        GREATER_OR_EQUAL(comparisonResult -> comparisonResult >= 0),
    103155        /** The value is greater than the given reference value (as float). */
    104         GREATER,
     156        GREATER(comparisonResult -> comparisonResult > 0),
    105157        /** The value is less than or equal to the given reference value (as float). */
    106         LESS_OR_EQUAL,
     158        LESS_OR_EQUAL(comparisonResult -> comparisonResult <= 0),
    107159        /** The value is less than the given reference value (as float). */
    108         LESS,
     160        LESS(comparisonResult -> comparisonResult < 0),
    109161        /** The reference is treated as regular expression and the value needs to match it. */
    110         REGEX,
     162        REGEX((test, prototype) -> Pattern.compile(prototype).matcher(test).find()),
    111163        /** The reference is treated as regular expression and the value needs to not match it. */
    112         NREGEX,
     164        NREGEX(REGEX),
    113165        /** The reference is treated as a list separated by ';'. Spaces around the ; are ignored.
    114166         *  The value needs to be equal one of the list elements. */
    115         ONE_OF,
     167        ONE_OF((test, prototype) -> Arrays.asList(test.split("\\s*;\\s*")).contains(prototype)),
    116168        /** The value needs to begin with the reference string. */
    117         BEGINS_WITH,
     169        BEGINS_WITH((test, prototype) -> test.startsWith(prototype)),
    118170        /** The value needs to end with the reference string. */
    119         ENDS_WITH,
     171        ENDS_WITH((test, prototype) -> test.endsWith(prototype)),
    120172        /** The value needs to contain the reference string. */
    121         CONTAINS;
     173        CONTAINS((test, prototype) -> test.contains(prototype));
    122174
    123175        static final Set<Op> NEGATED_OPS = EnumSet.of(NEQ, NREGEX);
     176
     177        private final BiFunction<String, String, Boolean> function;
     178
     179        private final boolean negated;
     180
     181        /**
     182         * Create a new string operation.
     183         * @param func The function to apply during {@link #eval(String, String)}.
     184         */
     185        Op(BiFunction<String, String, Boolean> func) {
     186            this.function = func;
     187            negated = false;
     188        }
     189
     190        /**
     191         * Create a new float operation that compares two float values
     192         * @param comparatorResult A function to mapt the result of the comparison
     193         */
     194        Op(IntFunction<Boolean> comparatorResult) {
     195            this.function = (test, prototype) -> {
     196                float testFloat;
     197                try {
     198                    testFloat = Float.parseFloat(test);
     199                } catch (NumberFormatException e) {
     200                    return false;
     201                }
     202                float prototypeFloat = Float.parseFloat(prototype);
     203
     204                int res = Float.compare(testFloat, prototypeFloat);
     205                return comparatorResult.apply(res);
     206            };
     207            negated = false;
     208        }
     209
     210        /**
     211         * Create a new Op by negating an other op.
     212         * @param negate inverse operation
     213         */
     214        Op(Op negate) {
     215            this.function = (a, b) -> !negate.function.apply(a, b);
     216            negated = true;
     217        }
    124218
    125219        /**
     
    130224         */
    131225        public boolean eval(String testString, String prototypeString) {
    132             if (testString == null && !NEGATED_OPS.contains(this))
    133                 return false;
    134             switch (this) {
    135             case EQ:
    136                 return Objects.equals(testString, prototypeString);
    137             case NEQ:
    138                 return !Objects.equals(testString, prototypeString);
    139             case REGEX:
    140             case NREGEX:
    141                 final boolean contains = Pattern.compile(prototypeString).matcher(testString).find();
    142                 return REGEX.equals(this) ? contains : !contains;
    143             case ONE_OF:
    144                 return testString != null && Arrays.asList(testString.split("\\s*;\\s*")).contains(prototypeString);
    145             case BEGINS_WITH:
    146                 return testString != null && testString.startsWith(prototypeString);
    147             case ENDS_WITH:
    148                 return testString != null && testString.endsWith(prototypeString);
    149             case CONTAINS:
    150                 return testString != null && testString.contains(prototypeString);
    151             case GREATER_OR_EQUAL:
    152             case GREATER:
    153             case LESS_OR_EQUAL:
    154             case LESS:
    155                 // See below
    156                 break;
    157             default:
    158                 throw new AssertionError();
    159             }
    160 
    161             float testFloat;
    162             try {
    163                 testFloat = Float.parseFloat(testString);
    164             } catch (NumberFormatException e) {
    165                 return false;
    166             }
    167             float prototypeFloat = Float.parseFloat(prototypeString);
    168 
    169             switch (this) {
    170             case GREATER_OR_EQUAL:
    171                 return testFloat >= prototypeFloat;
    172             case GREATER:
    173                 return testFloat > prototypeFloat;
    174             case LESS_OR_EQUAL:
    175                 return testFloat <= prototypeFloat;
    176             case LESS:
    177                 return testFloat < prototypeFloat;
    178             default:
    179                 throw new AssertionError();
    180             }
     226            if (testString == null)
     227                return negated;
     228            else
     229                return function.apply(testString, prototypeString);
    181230        }
    182231    }
     
    202251     * Extra class for performance reasons.
    203252     */
    204     class SimpleKeyValueCondition implements Condition {
     253    class SimpleKeyValueCondition implements Condition, ToTagConvertable {
    205254        /**
    206255         * The key to search for.
     
    227276        }
    228277
    229         public Tag asTag() {
     278        @Override
     279        public Tag asTag(OsmPrimitive primitive) {
    230280            return new Tag(k, v);
    231281        }
     
    242292     *
    243293     */
    244     class KeyValueCondition implements Condition {
     294    class KeyValueCondition implements Condition, ToTagConvertable {
    245295        /**
    246296         * The key to search for.
     
    258308         * If this flag is set, {@link #v} is treated as a key and the value is the value set for that key.
    259309         */
    260         public boolean considerValAsKey;
     310        public final boolean considerValAsKey;
    261311
    262312        /**
     
    280330        }
    281331
    282         public Tag asTag() {
     332        @Override
     333        public Tag asTag(OsmPrimitive primitive) {
    283334            return new Tag(k, v);
    284335        }
     
    290341    }
    291342
     343    /**
     344     * This condition requires a fixed key to match a given regexp
     345     */
    292346    class KeyValueRegexpCondition extends KeyValueCondition {
    293 
    294         public final Pattern pattern;
    295347        protected static final Set<Op> SUPPORTED_OPS = EnumSet.of(Op.REGEX, Op.NREGEX);
     348
     349        final Pattern pattern;
    296350
    297351        public KeyValueRegexpCondition(String k, String v, Op op, boolean considerValAsKey) {
     
    319373    }
    320374
     375    /**
     376     * A condition that checks that a key with the matching pattern has a value with the matching pattern.
     377     */
    321378    class RegexpKeyValueRegexpCondition extends KeyValueRegexpCondition {
    322379
    323380        public final Pattern keyPattern;
    324381
     382        /**
     383         * Create a condition in which the key and the value need to match a given regexp
     384         * @param k The key regexp
     385         * @param v The value regexp
     386         * @param op The operation to use when comparing the key and the value.
     387         */
    325388        public RegexpKeyValueRegexpCondition(String k, String v, Op op) {
    326389            super(k, v, op, false);
     
    419482     * </pre>
    420483     */
    421     class KeyCondition implements Condition {
     484    class KeyCondition implements Condition, ToTagConvertable {
    422485
    423486        /**
     
    484547         * @return The tag.
    485548         */
     549        @Override
    486550        public Tag asTag(OsmPrimitive p) {
    487551            String key = label;
    488552            if (KeyMatchType.REGEX.equals(matchType)) {
    489                 final Collection<String> matchingKeys = SubclassFilteredCollection.filter(p.keySet(), containsPattern);
    490                 if (!matchingKeys.isEmpty()) {
    491                     key = matchingKeys.iterator().next();
    492                 }
     553                key = p.keySet().stream().filter(containsPattern).findAny().orElse(key);
    493554            }
    494555            return new Tag(key, p.get(key));
     
    513574        @Override
    514575        public boolean applies(Environment env) {
    515             return env != null && env.getCascade(env.layer) != null && (not ^ env.getCascade(env.layer).containsKey(id));
     576            Cascade cascade = env.getCascade(env.layer);
     577            return cascade != null && (not ^ cascade.containsKey(id));
    516578        }
    517579
     
    703765        }
    704766
     767        /**
     768         * Create a new pseudo class condition
     769         * @param id The id of the pseudo class
     770         * @param not <code>true</code> to invert the condition
     771         * @param context The context the class is found in.
     772         * @return The new condition
     773         */
    705774        public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
    706775            CheckParameterUtil.ensureThat(!"sameTags".equals(id) || Context.LINK.equals(context), "sameTags only supported in LINK context");
     
    753822    }
    754823
     824    /**
     825     * A condition that is fulfilled whenever the expression is evaluated to be true.
     826     */
    755827    class ExpressionCondition implements Condition {
    756828
     
    776848        }
    777849    }
     850
     851    /**
     852     * This is a condition that can be converted to a tag
     853     * @author Michael Zangl
     854     * @since 10674
     855     */
     856    public interface ToTagConvertable {
     857        /**
     858         * Converts the current condition to a tag
     859         * @param primitive A primitive to use as context. May be ignored.
     860         * @return A tag with the key/value of this condition.
     861         */
     862        Tag asTag(OsmPrimitive primitive);
     863    }
    778864}
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java

    r10670 r10674  
    465465        @Override
    466466        public boolean matches(Environment env) {
     467            CheckParameterUtil.ensureParameterNotNull(env, "env");
    467468            if (conds == null) return true;
    468469            for (Condition c : conds) {
  • trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java

    r9793 r10674  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm;
     3
     4import java.util.ArrayList;
     5import java.util.Collections;
    36
    47import org.openstreetmap.josm.io.XmlWriter;
     
    1114 */
    1215public final class PerformanceTestUtils {
     16    private static final int TIMES_WARMUP = 2;
     17    private static final int TIMES_RUN = 8;
     18
     19    /**
     20     * A helper class that captures the time from object creation until #done() was called.
     21     * @author Michael Zangl
     22     */
     23    public static class PerformanceTestTimerCapture {
     24        private final long time;
     25
     26        protected PerformanceTestTimerCapture() {
     27            time = System.nanoTime();
     28        }
     29
     30        /**
     31         * Get the time since this object was created.
     32         * @return The time.
     33         */
     34        public long getTimeSinceCreation() {
     35            return (System.nanoTime() - time) / 1000000;
     36        }
     37    }
     38
    1339    /**
    1440     * A timer that measures the time from it's creation to the {@link #done()} call.
    1541     * @author Michael Zangl
    1642     */
    17     public static class PerformanceTestTimer {
     43    public static class PerformanceTestTimer extends PerformanceTestTimerCapture {
    1844        private final String name;
    19         private final long time;
    20         private boolean measurementPlotsPlugin = false;
     45        private boolean measurementPlotsPlugin = true;
    2146
    2247        protected PerformanceTestTimer(String name) {
    2348            this.name = name;
    24             time = System.nanoTime();
    2549        }
    2650
     
    3761         */
    3862        public void done() {
    39             long dTime = (System.nanoTime() - time) / 1000000;
     63            long dTime = getTimeSinceCreation();
    4064            if (measurementPlotsPlugin) {
    4165                measurementPlotsPluginOutput(name + "(ms)", dTime);
     
    5074
    5175    /**
    52      * Starts a new performance timer.
     76     * Starts a new performance timer. The timer will output the measurements in a format understood by Jenkins.
     77     * <p>
     78     * The timer can only be used to meassure one value.
    5379     * @param name The name/description of the timer.
    5480     * @return A {@link PerformanceTestTimer} object of which you can call {@link PerformanceTestTimer#done()} when done.
     
    5682    @SuppressFBWarnings(value = "DM_GC", justification = "Performance test code")
    5783    public static PerformanceTestTimer startTimer(String name) {
     84        cleanSystem();
     85        return new PerformanceTestTimer(name);
     86    }
     87
     88    /**
     89     * Runs the given performance test several (approx. 10) times and prints the median run time.
     90     * @param name The name to use in the output
     91     * @param testRunner The test to run
     92     */
     93    public static void runPerformanceTest(String name, Runnable testRunner) {
     94        for (int i = 0; i < TIMES_WARMUP; i++) {
     95            cleanSystem();
     96            PerformanceTestTimerCapture capture = new PerformanceTestTimerCapture();
     97            testRunner.run();
     98            capture.getTimeSinceCreation();
     99        }
     100        ArrayList<Long> times = new ArrayList<>();
     101        for (int i = 0; i < TIMES_RUN; i++) {
     102            cleanSystem();
     103            PerformanceTestTimerCapture capture = new PerformanceTestTimerCapture();
     104            testRunner.run();
     105            times.add(capture.getTimeSinceCreation());
     106        }
     107        System.out.println(times);
     108        Collections.sort(times);
     109        // Sort out e.g. GC during test run.
     110        double avg = times.subList(2, times.size() - 2).stream().mapToLong(l -> l).average().getAsDouble();
     111        measurementPlotsPluginOutput(name, avg);
     112    }
     113
     114    private static void cleanSystem() {
    58115        System.gc();
    59116        System.runFinalization();
    60         return new PerformanceTestTimer(name);
    61117    }
    62118
     
    68124     * @param name the name / title of the measurement
    69125     * @param value the value
    70      * @see https://wiki.jenkins-ci.org/display/JENKINS/Measurement+Plots+Plugin
     126     * @see "https://wiki.jenkins-ci.org/display/JENKINS/Measurement+Plots+Plugin"
    71127     */
    72128    public static void measurementPlotsPluginOutput(String name, double value) {
  • trunk/test/performance/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSourceFilterTest.java

    r10222 r10674  
    104104        data.generateDataSet();
    105105        CssGenerator css = new CssGenerator(data).addKeyValueRules(TEST_RULE_COUNT);
    106         runTest(data, css, "only key=value rules", false);
     106        runTest(data, css, "only key=value rules");
    107107    }
    108108
     
    115115        data.generateDataSet();
    116116        CssGenerator css = new CssGenerator(data).addHasKeyRules(TEST_RULE_COUNT);
    117         runTest(data, css, "only has key rules", false);
     117        runTest(data, css, "only has key rules");
    118118    }
    119119
     
    126126        data.generateDataSet();
    127127        CssGenerator css = new CssGenerator(data).addKeyRegexpRules(TEST_RULE_COUNT);
    128         runTest(data, css, "regular expressions", true);
     128        runTest(data, css, "regular expressions");
    129129    }
    130130
     
    137137        data.generateDataSet();
    138138        CssGenerator css = new CssGenerator(data).addIsTrueRules(TEST_RULE_COUNT);
    139         runTest(data, css, "is true", false);
     139        runTest(data, css, "is true");
    140140    }
    141141
    142     private void runTest(KeyValueDataGenerator data, CssGenerator css, String description, boolean measurementPlotsPlugin) {
     142    private void runTest(KeyValueDataGenerator data, CssGenerator css, String description) {
    143143        MapCSSStyleSource source = new MapCSSStyleSource(css.getCss());
    144144        PerformanceTestTimer timer = PerformanceTestUtils.startTimer("MapCSSStyleSource#loadStyleSource(...) for " + description);
     
    146146        timer.done();
    147147
    148         if (measurementPlotsPlugin) {
    149             timer = PerformanceTestUtils.startTimer(description);
    150             timer.setMeasurementPlotsPluginOutput(true);
    151         } else {
    152             timer = PerformanceTestUtils.startTimer(APPLY_CALLS + "x MapCSSStyleSource#apply(...) for " + description);
    153         }
     148        timer = PerformanceTestUtils.startTimer(APPLY_CALLS + "x MapCSSStyleSource#apply(...) for " + description);
    154149        for (int i = 0; i < APPLY_CALLS; i++) {
    155150            MultiCascade mc = new MultiCascade();
Note: See TracChangeset for help on using the changeset viewer.