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

Last change on this file since 14905 was 14905, checked in by GerdP, 5 years ago

fix #17440: remove confusing code
fixes several sonar lint issues "Local variables should not shadow class fields"

  • Property svn:eol-style set to native
File size: 8.3 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 int index = indexOfKey(tags, key);
179 return index < 0 ? null : tags[index + 1];
180 }
181
182 @Override
183 public boolean containsValue(Object value) {
184 for (int i = 1; i < tags.length; i += 2) {
185 if (value.equals(tags[i])) {
186 return true;
187 }
188 }
189 return false;
190 }
191
192 @Override
193 public synchronized String put(String key, String value) {
194 Objects.requireNonNull(key);
195 Objects.requireNonNull(value);
196 int index = indexOfKey(tags, key);
197 int newTagArrayLength = tags.length;
198 if (index < 0) {
199 index = newTagArrayLength;
200 newTagArrayLength += 2;
201 }
202
203 String[] newTags = Arrays.copyOf(tags, newTagArrayLength);
204 String old = newTags[index + 1];
205 newTags[index] = key;
206 newTags[index + 1] = value;
207 tags = newTags;
208 return old;
209 }
210
211 @Override
212 public synchronized String remove(Object key) {
213 int index = indexOfKey(tags, key);
214 if (index < 0) {
215 return null;
216 }
217 String old = tags[index + 1];
218 int newLength = tags.length - 2;
219 if (newLength == 0) {
220 tags = EMPTY_TAGS;
221 } else {
222 String[] newTags = new String[newLength];
223 System.arraycopy(tags, 0, newTags, 0, index);
224 System.arraycopy(tags, index + 2, newTags, index, newLength - index);
225 tags = newTags;
226 }
227
228 return old;
229 }
230
231 @Override
232 public synchronized void clear() {
233 tags = EMPTY_TAGS;
234 }
235
236 @Override
237 public int size() {
238 return tags.length / 2;
239 }
240
241 /**
242 * Gets a list of all tags contained in this map.
243 * @return The list of tags in the order they were added.
244 * @since 10604
245 */
246 public List<Tag> getTags() {
247 List<Tag> tagList = new ArrayList<>();
248 for (int i = 0; i < tags.length; i += 2) {
249 tagList.add(new Tag(tags[i], tags[i+1]));
250 }
251 return tagList;
252 }
253
254 /**
255 * Finds a key in an array that is structured like the {@link #tags} array and returns the position.
256 * <p>
257 * We allow the parameter to be passed to allow for better synchronization.
258 *
259 * @param tags The tags array to search through.
260 * @param key The key to search.
261 * @return The index of the key (a multiple of two) or -1 if it was not found.
262 */
263 private static int indexOfKey(String[] tags, Object key) {
264 for (int i = 0; i < tags.length; i += 2) {
265 if (tags[i].equals(key)) {
266 return i;
267 }
268 }
269 return -1;
270 }
271
272 @Override
273 public String toString() {
274 StringBuilder stringBuilder = new StringBuilder();
275 stringBuilder.append("TagMap[");
276 boolean first = true;
277 for (Map.Entry<String, String> e : entrySet()) {
278 if (!first) {
279 stringBuilder.append(',');
280 }
281 stringBuilder.append(e.getKey());
282 stringBuilder.append('=');
283 stringBuilder.append(e.getValue());
284 first = false;
285 }
286 stringBuilder.append(']');
287 return stringBuilder.toString();
288 }
289
290 /**
291 * Gets the backing tags array. Do not modify this array.
292 * @return The tags array.
293 */
294 String[] getTagsArray() {
295 return tags;
296 }
297}
Note: See TracBrowser for help on using the repository browser.