Index: trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java	(revision 8623)
+++ trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java	(revision 8623)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm;
+
+/**
+ * Timer utilities for performance tests.
+ * @author Michael Zangl
+ */
+public class PerformanceTestUtils {
+    /**
+     * A timer that measures the time from it's creation to the {@link #done()} call.
+    * @author Michael Zangl
+     */
+    public static class PerformanceTestTimer {
+        private String name;
+        private long time;
+
+        protected PerformanceTestTimer(String name) {
+            this.name = name;
+            time = System.nanoTime();
+        }
+
+        /**
+         * Prints the time since this timer was created.
+         */
+        public void done() {
+            long dTime = System.nanoTime() - time;
+            System.out.println("TIMER " + name + ": " + dTime / 1000000 + "ms");
+        }
+    }
+
+    private PerformanceTestUtils() {
+    }
+
+    /**
+     * Starts a new performance timer.
+     * @param name The name/description of the timer.
+     * @return A {@link PerformanceTestTimer} object of which you can call {@link PerformanceTestTimer#done()} when done.
+     */
+    public static PerformanceTestTimer startTimer(String name) {
+        System.gc();
+        System.runFinalization();
+        return new PerformanceTestTimer(name);
+    }
+}
Index: trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java	(revision 8623)
+++ trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java	(revision 8623)
@@ -0,0 +1,204 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.PerformanceTestUtils;
+import org.openstreetmap.josm.PerformanceTestUtils.PerformanceTestTimer;
+import org.openstreetmap.josm.data.osm.OsmDataGenerator.KeyValueDataGenerator;
+
+/**
+ * This test measures the performance of {@link OsmPrimitive#get(String)} and related.
+ * @author Michael Zangl
+ */
+public class KeyValuePerformanceTest {
+    private static final int PUT_RUNS = 10000;
+    private static final int GET_RUNS = 100000;
+    private static final int TEST_STRING_COUNT = 10000;
+    private static final int STRING_INTERN_TESTS = 5000000;
+    private static final double[] TAG_NODE_RATIOS = new double[] { .05, .3, 3, 20, 200 };
+    private ArrayList<String> testStrings = new ArrayList<>();
+    private Random random;
+
+    /**
+     * Prepare the test.
+     */
+    @BeforeClass
+    public static void createJOSMFixture() {
+        JOSMFixture.createPerformanceTestFixture().init(true);
+    }
+
+    /**
+     * See if there is a big difference between Strings that are interned and those that are not.
+     */
+    @Test
+    public void meassureStringEqualsIntern() {
+        String str1Interned = "string1";
+        String str1InternedB = "string1";
+        String str1 = new String(str1Interned);
+        String str1B = new String(str1Interned);
+        String str2Interned = "string2";
+        String str2 = new String(str2Interned);
+
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            // warm up
+            assertTrue(str1.equals(str1B));
+        }
+
+        PerformanceTestTimer timer = PerformanceTestUtils.startTimer("Assertion overhead.");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertTrue(true);
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1.equals(str2) succeeds (without intern)");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertTrue(str1.equals(str1B));
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1 == str2 succeeds");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertTrue(str1Interned == str1InternedB);
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1 == str2.intern() succeeds");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertTrue(str1Interned == str1.intern());
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1 == str2.intern() succeeds for interned string");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertTrue(str1Interned == str1InternedB.intern());
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1.equals(str2) = fails (without intern)");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertFalse(str1.equals(str2));
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1 == str2 fails");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertFalse(str1Interned == str2Interned);
+        }
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer("str1 == str2.intern() fails");
+        for (int i = 0; i < STRING_INTERN_TESTS; i++) {
+            assertFalse(str1Interned == str2.intern());
+        }
+        timer.done();
+    }
+
+    /**
+     * Generate an array of test strings.
+     */
+    @Before
+    public void generateTestStrings() {
+        testStrings.clear();
+        random = new Random(123);
+        for (int i = 0; i < TEST_STRING_COUNT; i++) {
+            testStrings.add(RandomStringUtils.random(10, 0, 0, true, true, null, random));
+        }
+    }
+
+    /**
+     * Measure the speed of {@link OsmPrimitive#put(String, String)}
+     */
+    @Test
+    public void testKeyValuePut() {
+        for (double tagNodeRatio : TAG_NODE_RATIOS) {
+            int nodeCount = (int) (PUT_RUNS / tagNodeRatio);
+            KeyValueDataGenerator generator = OsmDataGenerator.getKeyValue(nodeCount, 0);
+            generator.generateDataSet();
+
+            PerformanceTestTimer timer = PerformanceTestUtils
+                    .startTimer("OsmPrimitive#put(String, String) with put/node ratio " + tagNodeRatio);
+
+            for (int i = 0; i < PUT_RUNS; i++) {
+                String key = generator.randomKey();
+                String value = generator.randomValue();
+                generator.randomNode().put(key, value);
+            }
+
+            timer.done();
+        }
+    }
+
+    /**
+     * Measure the speed of {@link OsmPrimitive#get(String)}
+     */
+    @Test
+    public void testKeyValueGet() {
+        for (double tagNodeRatio : TAG_NODE_RATIOS) {
+            KeyValueDataGenerator generator = OsmDataGenerator.getKeyValue(tagNodeRatio);
+            generator.generateDataSet();
+
+            PerformanceTestTimer timer = PerformanceTestUtils
+                    .startTimer("OsmPrimitive#get(String) with tag/node ratio " + tagNodeRatio);
+            for (int i = 0; i < GET_RUNS; i++) {
+                String key = generator.randomKey();
+                // to make comparison easier.
+                generator.randomValue();
+                generator.randomNode().get(key);
+            }
+            timer.done();
+        }
+    }
+
+    /**
+     * Measure the speed of {@link OsmPrimitive#getKeys()}
+     */
+    @Test
+    public void testKeyValueGetKeys() {
+        for (double tagNodeRatio : TAG_NODE_RATIOS) {
+            KeyValueDataGenerator generator = OsmDataGenerator.getKeyValue(tagNodeRatio);
+            generator.generateDataSet();
+
+            PerformanceTestTimer timer = PerformanceTestUtils.startTimer("OsmPrimitive#getKeys() with tag/node ratio "
+                    + tagNodeRatio);
+
+            for (int i = 0; i < GET_RUNS; i++) {
+                // to make comparison easier.
+                generator.randomKey();
+                generator.randomValue();
+                generator.randomNode().getKeys();
+            }
+            timer.done();
+        }
+    }
+
+    /**
+     * Measure the speed of {@link OsmPrimitive#getKeys()}.get(key)
+     */
+    @Test
+    public void testKeyValueGetKeysGet() {
+        for (double tagNodeRatio : TAG_NODE_RATIOS) {
+            KeyValueDataGenerator generator = OsmDataGenerator.getKeyValue(tagNodeRatio);
+            generator.generateDataSet();
+
+            PerformanceTestTimer timer = PerformanceTestUtils
+                    .startTimer("OsmPrimitive#getKeys().get(key) with tag/node ratio " + tagNodeRatio);
+            for (int i = 0; i < GET_RUNS; i++) {
+                String key = generator.randomKey();
+                // to make comparison easier.
+                generator.randomValue();
+                generator.randomNode().getKeys().get(key);
+            }
+            timer.done();
+        }
+    }
+}
Index: trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java	(revision 8623)
+++ trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java	(revision 8623)
@@ -0,0 +1,214 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * This is an utility class that allows you to generate OSM test data.
+ * @author Michael Zangl
+ */
+public class OsmDataGenerator {
+    private static final int DEFAULT_KEY_VALUE_RATIO = 3;
+    private static final int DEFAULT_NODE_COUNT = 1000;
+    private static final String DATA_DIR = "data_nodist" + File.separator + "osmfiles";
+
+    /**
+     * A generator that generates test data by filling a data set.
+     * @author Michael Zangl
+     */
+    public static abstract class DataGenerator {
+        private String datasetName;
+        protected final Random random;
+        private DataSet ds;
+
+        /**
+         * Create a new generator.
+         * @param datasetName The name for the generator. Only used for human readability.
+         */
+        protected DataGenerator(String datasetName) {
+            this.datasetName = datasetName;
+            this.random = new Random(1234);
+        }
+
+        /**
+         * Generates the data set. If this method is called twice, the same dataset is returned.
+         * @return The generated data set.
+         */
+        public DataSet generateDataSet() {
+            ensureInitialized();
+            return ds;
+        }
+
+        protected void ensureInitialized() {
+            if (ds == null) {
+                ds = new DataSet();
+                fillData(ds);
+            }
+        }
+
+        protected abstract void fillData(DataSet ds);
+
+        /**
+         * Create a random node and add it to the dataset.
+         * @return
+         */
+        protected Node createRandomNode(DataSet ds) {
+            Node node = new Node();
+            node.setEastNorth(new EastNorth(random.nextDouble(), random.nextDouble()));
+            ds.addPrimitive(node);
+            return node;
+        }
+
+        protected String randomString() {
+            return RandomStringUtils.random(12, 0, 0, true, true, null, random);
+        }
+
+        /**
+         * Gets a file path where this data could be stored.
+         * @return A file path.
+         */
+        public File getFile() {
+            return new File(DATA_DIR + File.separator + datasetName + ".osm");
+        }
+
+        /**
+         * Creates a new {@link OsmDataLayer} that uses the underlying dataset of this generator.
+         * @return A new data layer.
+         */
+        public OsmDataLayer createDataLayer() {
+            return new OsmDataLayer(generateDataSet(), datasetName, getFile());
+        }
+
+        @Override
+        public String toString() {
+            return "DataGenerator [datasetName=" + datasetName + "]";
+        }
+    }
+
+    /**
+     * A data generator that generates a bunch of random nodes.
+    * @author Michael Zangl
+     */
+    public static class NodeDataGenerator extends DataGenerator {
+        protected final ArrayList<Node> nodes = new ArrayList<>();
+        private final int nodeCount;
+
+        private NodeDataGenerator(String datasetName, int nodeCount) {
+            super(datasetName);
+            this.nodeCount = nodeCount;
+        }
+
+        @Override
+        public void fillData(DataSet ds) {
+            for (int i = 0; i < nodeCount; i++) {
+                nodes.add(createRandomNode(ds));
+            }
+        }
+
+        /**
+         * Gets a random node of this dataset.
+         * @return A random node.
+         */
+        public Node randomNode() {
+            ensureInitialized();
+            return nodes.get(random.nextInt(nodes.size()));
+        }
+    }
+
+    /**
+     * A data generator that generates a bunch of random nodes and fills them with keys/values.
+    * @author Michael Zangl
+     */
+    public static class KeyValueDataGenerator extends NodeDataGenerator {
+
+        private static final int VALUE_COUNT = 200;
+        private static final int KEY_COUNT = 150;
+        private final double tagNodeRation;
+        private ArrayList<String> keys;
+        private ArrayList<String> values;
+
+        private KeyValueDataGenerator(String datasetName, int nodeCount, double tagNodeRation) {
+            super(datasetName, nodeCount);
+            this.tagNodeRation = tagNodeRation;
+        }
+
+        @Override
+        public void fillData(DataSet ds) {
+            super.fillData(ds);
+            keys = new ArrayList<>();
+            for (int i = 0; i < KEY_COUNT; i++) {
+                keys.add(randomString());
+            }
+            values = new ArrayList<>();
+            for (int i = 0; i < VALUE_COUNT; i++) {
+                values.add(randomString());
+            }
+
+            double tags = nodes.size() * tagNodeRation;
+            for (int i = 0; i < tags; i++) {
+                String key = randomKey();
+                String value = randomValue();
+                nodes.get(random.nextInt(nodes.size())).put(key, value);
+            }
+        }
+
+        /**
+         * Gets a random value that was used to fill the tags.
+         * @return A random String probably used in as value somewhere.
+         */
+        public String randomValue() {
+            ensureInitialized();
+            return values.get(random.nextInt(values.size()));
+        }
+
+        /**
+         * Gets a random key that was used to fill the tags.
+         * @return A random String probably used in as key somewhere.
+         */
+        public String randomKey() {
+            ensureInitialized();
+            return keys.get(random.nextInt(keys.size()));
+        }
+    }
+
+    /**
+     * Generate a generator that creates some nodes and adds random keys and values to it.
+     * @return The generator
+     */
+    public static KeyValueDataGenerator getKeyValue() {
+        return getKeyValue(DEFAULT_KEY_VALUE_RATIO);
+    }
+
+    /**
+     * Generate a generator that creates some nodes and adds random keys and values to it.
+     * @param tagNodeRation How many tags to add per node (on average).
+     * @return The generator
+     */
+    public static KeyValueDataGenerator getKeyValue(double tagNodeRation) {
+        return getKeyValue(DEFAULT_NODE_COUNT, tagNodeRation);
+    }
+
+    /**
+     * Generate a generator that creates some nodes and adds random keys and values to it.
+     * @param nodeCount The number of nodes the dataset should contain.
+     * @param tagNodeRation How many tags to add per node (on average).
+     * @return The generator
+     */
+    public static KeyValueDataGenerator getKeyValue(int nodeCount, double tagNodeRation) {
+        return new KeyValueDataGenerator("key-value", nodeCount, tagNodeRation);
+    }
+
+    /**
+     * Create a generator that generates a bunch of nodes.
+     * @return The generator
+     */
+    public static DataGenerator getNodes() {
+        return new NodeDataGenerator("nodes", DEFAULT_NODE_COUNT);
+    }
+}
Index: trunk/test/performance/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSourceFilterTest.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSourceFilterTest.java	(revision 8623)
+++ trunk/test/performance/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSourceFilterTest.java	(revision 8623)
@@ -0,0 +1,145 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.PerformanceTestUtils;
+import org.openstreetmap.josm.PerformanceTestUtils.PerformanceTestTimer;
+import org.openstreetmap.josm.data.osm.OsmDataGenerator;
+import org.openstreetmap.josm.data.osm.OsmDataGenerator.KeyValueDataGenerator;
+import org.openstreetmap.josm.gui.mappaint.MultiCascade;
+
+/**
+ * Tests how fast {@link MapCSSStyleSource} finds the right style candidates for one object.
+ * @author Michael Zangl
+ */
+public class MapCSSStyleSourceFilterTest {
+
+    private static final int TEST_RULE_COUNT = 10000;
+
+    private class CssGenerator {
+        StringBuilder sb = new StringBuilder();
+        private KeyValueDataGenerator generator;
+
+        /**
+         * Create a new CSS generator.
+         * @param generator A generator to get the keys from.
+         */
+        public CssGenerator(KeyValueDataGenerator generator) {
+            this.generator = generator;
+        }
+
+        private CssGenerator addKeyValueRules(int count) {
+            for (int i = 0; i < count; i++) {
+                String key = generator.randomKey();
+                String value = generator.randomValue();
+                addRule("node[\"" + key + "\"=\"" + value + "\"]");
+            }
+            return this;
+        }
+
+        private CssGenerator addKeyRegexpRules(int count) {
+            for (int i = 0; i < count; i++) {
+                String key = generator.randomKey();
+                String value = generator.randomValue();
+                value = value.substring(i % value.length());
+                addRule("node[\"" + key + "\"=~/.*" + value + ".*/]");
+            }
+            return this;
+        }
+
+        public CssGenerator addHasKeyRules(int count) {
+            for (int i = 0; i < count; i++) {
+                String key = generator.randomKey();
+                addRule("node[\"" + key + "\"]");
+            }
+            return this;
+        }
+
+        public CssGenerator addIsTrueRules(int count) {
+            for (int i = 0; i < count; i++) {
+                String key = generator.randomKey();
+                addRule("node[\"" + key + "\"?]");
+            }
+            return this;
+        }
+
+        private void addRule(String selector) {
+            sb.append(selector + " {}\n");
+        }
+
+        public String getCss() {
+            return sb.toString();
+        }
+    }
+
+    private static final int APPLY_CALLS = 100000;
+
+    /**
+     * Prepare the test.
+     */
+    @BeforeClass
+    public static void createJOSMFixture() {
+        JOSMFixture.createPerformanceTestFixture().init(true);
+    }
+
+    /**
+     * Time how long it takes to evaluate [key=value] rules
+     */
+    @Test
+    public void testKeyValueRules() {
+        KeyValueDataGenerator data = OsmDataGenerator.getKeyValue();
+        data.generateDataSet();
+        CssGenerator css = new CssGenerator(data).addKeyValueRules(TEST_RULE_COUNT);
+        runTest(data, css, "only key=value rules");
+    }
+
+    /**
+     * Time how long it takes to evaluate [key] rules
+     */
+    @Test
+    public void testKeyOnlyRules() {
+        KeyValueDataGenerator data = OsmDataGenerator.getKeyValue();
+        data.generateDataSet();
+        CssGenerator css = new CssGenerator(data).addHasKeyRules(TEST_RULE_COUNT);
+        runTest(data, css, "only has key rules");
+    }
+
+    /**
+     * Time how long it takes to evaluate [key=~...] rules
+     */
+    @Test
+    public void testRegularExpressionRules() {
+        KeyValueDataGenerator data = OsmDataGenerator.getKeyValue();
+        data.generateDataSet();
+        CssGenerator css = new CssGenerator(data).addKeyRegexpRules(TEST_RULE_COUNT);
+        runTest(data, css, "regular expressions");
+    }
+
+    /**
+     * Time how long it takes to evaluate [key?] rules
+     */
+    @Test
+    public void testIsTrueRules() {
+        KeyValueDataGenerator data = OsmDataGenerator.getKeyValue();
+        data.generateDataSet();
+        CssGenerator css = new CssGenerator(data).addIsTrueRules(TEST_RULE_COUNT);
+        runTest(data, css, "is true");
+    }
+
+    private void runTest(KeyValueDataGenerator data, CssGenerator css, String description) {
+        MapCSSStyleSource source = new MapCSSStyleSource(css.getCss());
+        PerformanceTestTimer timer = PerformanceTestUtils.startTimer("MapCSSStyleSource#loadStyleSource(...) for " + description);
+        source.loadStyleSource();
+        timer.done();
+
+        timer = PerformanceTestUtils.startTimer(APPLY_CALLS + "x MapCSSStyleSource#apply(...) for " + description);
+        for (int i = 0; i < APPLY_CALLS; i++) {
+            MultiCascade mc = new MultiCascade();
+            source.apply(mc, data.randomNode(), 1, false);
+        }
+        timer.done();
+    }
+
+}
