Ticket #12355: patch-add-tag-map.patch

File patch-add-tag-map.patch, 11.7 KB (added by michael2402, 10 years ago)
  • src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java

    diff --git a/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java b/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
    index bb7127c..f384c67 100644
    a b import java.util.Arrays;  
    88import java.util.Collection;
    99import java.util.Collections;
    1010import java.util.Date;
    11 import java.util.HashMap;
    1211import java.util.HashSet;
    1312import java.util.Map;
    1413import java.util.Map.Entry;
    public abstract class AbstractPrimitive implements IPrimitive {  
    498497     * @see #visitKeys(KeyValueVisitor)
    499498     */
    500499    @Override
    501     public Map<String, String> getKeys() {
    502         String[] keys = this.keys;
    503         final Map<String, String> result = new HashMap<>(
    504                 Utils.hashMapInitialCapacity(keys == null ? 0 : keys.length / 2));
    505         if (keys != null) {
    506             for (int i = 0; i < keys.length; i += 2) {
    507                 result.put(keys[i], keys[i + 1]);
    508             }
    509         }
    510         return result;
     500    public TagMap getKeys() {
     501        return new TagMap(keys);
    511502    }
    512503
    513504    /**
    public abstract class AbstractPrimitive implements IPrimitive {  
    552543    }
    553544
    554545    /**
     546     * Copy the keys from a TagMap.
     547     * @param keys The new key map.
     548     */
     549    public void setKeys(TagMap keys) {
     550        Map<String, String> originalKeys = getKeys();
     551        String[] arr = keys.getTagsArray();
     552        if (arr.length == 0) {
     553            this.keys = null;
     554        } else {
     555            this.keys = arr;
     556        }
     557        keysChangedImpl(originalKeys);
     558    }
     559
     560    /**
    555561     * Set the given value to the given key. If key is null, does nothing. If value is null,
    556562     * removes the key and behaves like {@link #remove(String)}.
    557563     *
  • src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
    index 9d5cb5d..b0997ce 100644
    a b public abstract class OsmPrimitive extends AbstractPrimitive implements Comparab  
    914914     ------------*/
    915915
    916916    @Override
     917    public final void setKeys(TagMap keys) {
     918        boolean locked = writeLock();
     919        try {
     920            super.setKeys(keys);
     921        } finally {
     922            writeUnlock(locked);
     923        }
     924    }
     925
     926    @Override
    917927    public final void setKeys(Map<String, String> keys) {
    918928        boolean locked = writeLock();
    919929        try {
  • src/org/openstreetmap/josm/data/osm/Tag.java

    diff --git a/src/org/openstreetmap/josm/data/osm/Tag.java b/src/org/openstreetmap/josm/data/osm/Tag.java
    index 98d710d..0dcf43a 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm;
    33
     4import java.util.Map.Entry;
     5
    46import org.openstreetmap.josm.tools.CheckParameterUtil;
    57import org.openstreetmap.josm.tools.Utils;
    68
    import org.openstreetmap.josm.tools.Utils;  
    911 * be empty, but not null.
    1012 *
    1113 */
    12 public class Tag {
     14public class Tag implements Entry<String, String> {
    1315
    1416    private String key;
    1517    private String value;
    public class Tag {  
    5759     *
    5860     * @return the key of the tag
    5961     */
     62    @Override
    6063    public String getKey() {
    6164        return key;
    6265    }
    public class Tag {  
    6669     *
    6770     * @return the value of the tag
    6871     */
     72    @Override
    6973    public String getValue() {
    7074        return value;
    7175    }
    7276
    7377    /**
     78     * This is not supported by this implementation.
     79     * @param value ignored
     80     * @return (Does not return)
     81     * @throws UnsupportedOperationException always
     82     */
     83    @Override
     84    public String setValue(String value) {
     85        throw new UnsupportedOperationException();
     86    }
     87
     88    /**
    7489     * Replies true if the key of this tag is equal to <code>key</code>.
    7590     * If <code>key</code> is null, assumes the empty key.
    7691     *
  • new file src/org/openstreetmap/josm/data/osm/TagMap.java

    diff --git a/src/org/openstreetmap/josm/data/osm/TagMap.java b/src/org/openstreetmap/josm/data/osm/TagMap.java
    new file mode 100644
    index 0000000..1b61eef
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm;
     3
     4import java.util.AbstractMap;
     5import java.util.AbstractSet;
     6import java.util.Arrays;
     7import java.util.ConcurrentModificationException;
     8import java.util.Iterator;
     9import java.util.NoSuchElementException;
     10import java.util.Set;
     11
     12/**
     13 * This class provides a read/write map that uses the same format as {@link AbstractPrimitive#keys}.
     14 * It offers good performance for few keys.
     15 * It uses copy on write, so there cannot be a {@link ConcurrentModificationException} while iterating through it.
     16 *
     17 * @author Michael Zangl
     18 */
     19public class TagMap extends AbstractMap<String, String> {
     20    /**
     21     * We use this array every time we want to represent an empty map.
     22     * This saves us the burden of checking for null every time but saves some object allocations.
     23     */
     24    private static final String[] EMPTY_TAGS = new String[0];
     25
     26    /**
     27     * An iterator that iterates over the tags in this map. The iterator always represents the state of the map when it was created.
     28     * Further changes to the map won't change the tags that we iterate over but they also won't raise any exceptions.
     29     * @author Michael Zangl
     30     */
     31    private static class TagEntryInterator implements Iterator<Entry<String, String>> {
     32        /**
     33         * The current state of the tags we iterate over.
     34         */
     35        private final String[] tags;
     36        /**
     37         * Current tag index. Always a multiple of 2.
     38         */
     39        private int currentIndex = 0;
     40
     41        /**
     42         * Create a new {@link TagEntryInterator}
     43         * @param tags The tags array. It is never changed but should also not be changed by you.
     44         */
     45        TagEntryInterator(String[] tags) {
     46            super();
     47            this.tags = tags;
     48        }
     49
     50        @Override
     51        public boolean hasNext() {
     52            return currentIndex < tags.length;
     53        }
     54
     55        @Override
     56        public Entry<String, String> next() {
     57            if (!hasNext()) {
     58                throw new NoSuchElementException();
     59            }
     60
     61            Tag tag = new Tag(tags[currentIndex], tags[currentIndex + 1]);
     62            currentIndex += 2;
     63            return tag;
     64        }
     65
     66        @Override
     67        public void remove() {
     68            throw new UnsupportedOperationException();
     69        }
     70
     71    }
     72
     73    /**
     74     * This is the entry set of this map. It represents the state when it was created.
     75     * @author Michael Zangl
     76     */
     77    private static class TagEntrySet extends AbstractSet<Entry<String, String>> {
     78        private final String[] tags;
     79
     80        /**
     81         * Create a new {@link TagEntrySet}
     82         * @param tags The tags array. It is never changed but should also not be changed by you.
     83         */
     84        TagEntrySet(String[] tags) {
     85            super();
     86            this.tags = tags;
     87        }
     88
     89        @Override
     90        public Iterator<Entry<String, String>> iterator() {
     91            return new TagEntryInterator(tags);
     92        }
     93
     94        @Override
     95        public int size() {
     96            return tags.length / 2;
     97        }
     98
     99    }
     100
     101    /**
     102     * The tags field. This field is guarded using RCU.
     103     */
     104    private volatile String[] tags;
     105
     106    /**
     107     * Creates a new, empty tag map.
     108     */
     109    public TagMap() {
     110        this(null);
     111    }
     112
     113    /**
     114     * Creates a new read only tag map using a key/value/key/value/... array.
     115     * <p>
     116     * The array that is passed as parameter may not be modified after passing it to this map.
     117     * @param tags The tags array. It is not modified by this map.
     118     */
     119    public TagMap(String[] tags) {
     120        if (tags == null || tags.length == 0) {
     121            this.tags = EMPTY_TAGS;
     122        } else {
     123            if (tags.length % 2 != 0) {
     124                throw new IllegalArgumentException("tags array length needs to be multiple of two.");
     125            }
     126            this.tags = tags;
     127        }
     128    }
     129
     130    @Override
     131    public Set<Entry<String, String>> entrySet() {
     132        return new TagEntrySet(tags);
     133    }
     134
     135    @Override
     136    public boolean containsKey(Object key) {
     137        return indexOfKey(tags, key) >= 0;
     138    }
     139
     140    @Override
     141    public String get(Object key) {
     142        String[] tags = this.tags;
     143        int index = indexOfKey(tags, key);
     144        return index < 0 ? null : tags[index + 1];
     145    }
     146
     147    @Override
     148    public boolean containsValue(Object value) {
     149        String[] tags = this.tags;
     150        for (int i = 1; i < tags.length; i += 2) {
     151            if (value.equals(tags[i])) {
     152                return true;
     153            }
     154        }
     155        return false;
     156    }
     157
     158    @Override
     159    public synchronized String put(String key, String value) {
     160        if (key == null) {
     161            throw new NullPointerException();
     162        }
     163        if (value == null) {
     164            throw new NullPointerException();
     165        }
     166        int index = indexOfKey(tags, key);
     167        int newTagArrayLength = tags.length;
     168        if (index < 0) {
     169            index = newTagArrayLength;
     170            newTagArrayLength += 2;
     171        }
     172
     173        String[] newTags = Arrays.copyOf(tags, newTagArrayLength);
     174        String old = newTags[index + 1];
     175        newTags[index] = key;
     176        newTags[index + 1] = value;
     177        tags = newTags;
     178        return old;
     179    }
     180
     181    @Override
     182    public synchronized String remove(Object key) {
     183        int index = indexOfKey(tags, key);
     184        if (index < 0) {
     185            return null;
     186        }
     187        String old = tags[index + 1];
     188        int newLength = tags.length - 2;
     189        if (newLength == 0) {
     190            tags = EMPTY_TAGS;
     191        } else {
     192            String[] newTags = new String[newLength];
     193            System.arraycopy(tags, 0, newTags, 0, index);
     194            System.arraycopy(tags, index + 2, newTags, index, newLength - index);
     195            tags = newTags;
     196        }
     197
     198        return old;
     199    }
     200
     201    @Override
     202    public synchronized void clear() {
     203        tags = EMPTY_TAGS;
     204    }
     205
     206    @Override
     207    public int size() {
     208        return tags.length / 2;
     209    }
     210
     211    /**
     212     * Finds a key in an array that is structured like the {@link #tags} array and returns the position.
     213     * <p>
     214     * We allow the parameter to be passed to allow for better synchronization.
     215     *
     216     * @param tags The tags array to search through.
     217     * @param key The key to search.
     218     * @return The index of the key (a multiple of two) or -1 if it was not found.
     219     */
     220    private static int indexOfKey(String[] tags, Object key) {
     221        for (int i = 0; i < tags.length; i += 2) {
     222            if (tags[i].equals(key)) {
     223                return i;
     224            }
     225        }
     226        return -1;
     227    }
     228
     229    @Override
     230    public String toString() {
     231        StringBuilder stringBuilder = new StringBuilder();
     232        stringBuilder.append("TagMap[");
     233        boolean first = true;
     234        for (java.util.Map.Entry<String, String> e : entrySet()) {
     235            if (!first) {
     236                stringBuilder.append(",");
     237            }
     238            stringBuilder.append(e.getKey());
     239            stringBuilder.append("=");
     240            stringBuilder.append(e.getValue());
     241            first = false;
     242        }
     243        stringBuilder.append("]");
     244        return stringBuilder.toString();
     245    }
     246
     247    /**
     248     * Gets the backing tags array. Do not modify this array.
     249     * @return The tags array.
     250     */
     251    String[] getTagsArray() {
     252        return tags;
     253    }
     254}