source: josm/trunk/src/org/openstreetmap/josm/data/osm/TagMap.java@ 9649

Last change on this file since 9649 was 9649, checked in by bastiK, 8 years ago

applied #12355 - add a new TagMap as Set<String, String> (patch by michael2402)

  • Property svn:eol-style set to native
File size: 7.2 KB
Line 
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}
Note: See TracBrowser for help on using the repository browser.