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

Last change on this file since 10687 was 10680, checked in by Don-vip, 8 years ago

sonar - pmd:UseVarargs - Use Varargs

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