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

Last change on this file since 12722 was 11747, checked in by Don-vip, 7 years ago

checkstyle - NoWhiteSpaceBefore ...

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