source: josm/trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java@ 10308

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

see #12355 - fix NPE (regression from r9649)

  • Property svn:eol-style set to native
File size: 21.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Date;
11import java.util.HashSet;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Objects;
15import java.util.Set;
16import java.util.concurrent.atomic.AtomicLong;
17
18import org.openstreetmap.josm.tools.LanguageInfo;
19import org.openstreetmap.josm.tools.Utils;
20
21/**
22* Abstract class to represent common features of the datatypes primitives.
23*
24* @since 4099
25*/
26public abstract class AbstractPrimitive implements IPrimitive {
27
28 /**
29 * This is a visitor that can be used to loop over the keys/values of this primitive.
30 *
31 * @author Michael Zangl
32 * @since 8742
33 */
34 public interface KeyValueVisitor {
35
36 /**
37 * This method gets called for every tag received.
38 *
39 * @param primitive This primitive
40 * @param key The key
41 * @param value The value
42 */
43 void visitKeyValue(AbstractPrimitive primitive, String key, String value);
44 }
45
46 private static final AtomicLong idCounter = new AtomicLong(0);
47
48 static long generateUniqueId() {
49 return idCounter.decrementAndGet();
50 }
51
52 /**
53 * This flag shows, that the properties have been changed by the user
54 * and on upload the object will be send to the server.
55 */
56 protected static final int FLAG_MODIFIED = 1 << 0;
57
58 /**
59 * This flag is false, if the object is marked
60 * as deleted on the server.
61 */
62 protected static final int FLAG_VISIBLE = 1 << 1;
63
64 /**
65 * An object that was deleted by the user.
66 * Deleted objects are usually hidden on the map and a request
67 * for deletion will be send to the server on upload.
68 * An object usually cannot be deleted if it has non-deleted
69 * objects still referring to it.
70 */
71 protected static final int FLAG_DELETED = 1 << 2;
72
73 /**
74 * A primitive is incomplete if we know its id and type, but nothing more.
75 * Typically some members of a relation are incomplete until they are
76 * fetched from the server.
77 */
78 protected static final int FLAG_INCOMPLETE = 1 << 3;
79
80 /**
81 * Put several boolean flags to one short int field to save memory.
82 * Other bits of this field are used in subclasses.
83 */
84 protected volatile short flags = FLAG_VISIBLE; // visible per default
85
86 /*-------------------
87 * OTHER PROPERTIES
88 *-------------------*/
89
90 /**
91 * Unique identifier in OSM. This is used to identify objects on the server.
92 * An id of 0 means an unknown id. The object has not been uploaded yet to
93 * know what id it will get.
94 */
95 protected long id;
96
97 /**
98 * User that last modified this primitive, as specified by the server.
99 * Never changed by JOSM.
100 */
101 protected User user;
102
103 /**
104 * Contains the version number as returned by the API. Needed to
105 * ensure update consistency
106 */
107 protected int version;
108
109 /**
110 * The id of the changeset this primitive was last uploaded to.
111 * 0 if it wasn't uploaded to a changeset yet of if the changeset
112 * id isn't known.
113 */
114 protected int changesetId;
115
116 protected int timestamp;
117
118 /**
119 * Get and write all attributes from the parameter. Does not fire any listener, so
120 * use this only in the data initializing phase
121 * @param other the primitive to clone data from
122 */
123 public void cloneFrom(AbstractPrimitive other) {
124 setKeys(other.getKeys());
125 id = other.id;
126 if (id <= 0) {
127 // reset version and changeset id
128 version = 0;
129 changesetId = 0;
130 }
131 timestamp = other.timestamp;
132 if (id > 0) {
133 version = other.version;
134 }
135 flags = other.flags;
136 user = other.user;
137 if (id > 0 && other.changesetId > 0) {
138 // #4208: sometimes we cloned from other with id < 0 *and*
139 // an assigned changeset id. Don't know why yet. For primitives
140 // with id < 0 we don't propagate the changeset id any more.
141 //
142 setChangesetId(other.changesetId);
143 }
144 }
145
146 @Override
147 public int getVersion() {
148 return version;
149 }
150
151 @Override
152 public long getId() {
153 long id = this.id;
154 return id >= 0 ? id : 0;
155 }
156
157 /**
158 * Gets a unique id representing this object.
159 *
160 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
161 */
162 @Override
163 public long getUniqueId() {
164 return id;
165 }
166
167 /**
168 * Determines if this primitive is new.
169 * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
170 */
171 @Override
172 public boolean isNew() {
173 return id <= 0;
174 }
175
176 @Override
177 public boolean isNewOrUndeleted() {
178 return (id <= 0) || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
179 }
180
181 @Override
182 public void setOsmId(long id, int version) {
183 if (id <= 0)
184 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
185 if (version <= 0)
186 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
187 this.id = id;
188 this.version = version;
189 this.setIncomplete(false);
190 }
191
192 /**
193 * Clears the metadata, including id and version known to the OSM API.
194 * The id is a new unique id. The version, changeset and timestamp are set to 0.
195 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
196 * of calling this method.
197 * @since 6140
198 */
199 public void clearOsmMetadata() {
200 // Not part of dataset - no lock necessary
201 this.id = generateUniqueId();
202 this.version = 0;
203 this.user = null;
204 this.changesetId = 0; // reset changeset id on a new object
205 this.timestamp = 0;
206 this.setIncomplete(false);
207 this.setDeleted(false);
208 this.setVisible(true);
209 }
210
211 @Override
212 public User getUser() {
213 return user;
214 }
215
216 @Override
217 public void setUser(User user) {
218 this.user = user;
219 }
220
221 @Override
222 public int getChangesetId() {
223 return changesetId;
224 }
225
226 @Override
227 public void setChangesetId(int changesetId) {
228 if (this.changesetId == changesetId)
229 return;
230 if (changesetId < 0)
231 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
232 if (isNew() && changesetId > 0)
233 throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
234
235 this.changesetId = changesetId;
236 }
237
238 @Override
239 public PrimitiveId getPrimitiveId() {
240 return new SimplePrimitiveId(getUniqueId(), getType());
241 }
242
243 public OsmPrimitiveType getDisplayType() {
244 return getType();
245 }
246
247 @Override
248 public void setTimestamp(Date timestamp) {
249 this.timestamp = (int) (timestamp.getTime() / 1000);
250 }
251
252 @Override
253 public void setRawTimestamp(int timestamp) {
254 this.timestamp = timestamp;
255 }
256
257 @Override
258 public Date getTimestamp() {
259 return new Date(timestamp * 1000L);
260 }
261
262 @Override
263 public int getRawTimestamp() {
264 return timestamp;
265 }
266
267 @Override
268 public boolean isTimestampEmpty() {
269 return timestamp == 0;
270 }
271
272 /* -------
273 /* FLAGS
274 /* ------*/
275
276 protected void updateFlags(int flag, boolean value) {
277 if (value) {
278 flags |= flag;
279 } else {
280 flags &= ~flag;
281 }
282 }
283
284 @Override
285 public void setModified(boolean modified) {
286 updateFlags(FLAG_MODIFIED, modified);
287 }
288
289 @Override
290 public boolean isModified() {
291 return (flags & FLAG_MODIFIED) != 0;
292 }
293
294 @Override
295 public boolean isDeleted() {
296 return (flags & FLAG_DELETED) != 0;
297 }
298
299 @Override
300 public boolean isUndeleted() {
301 return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
302 }
303
304 @Override
305 public boolean isUsable() {
306 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
307 }
308
309 @Override
310 public boolean isVisible() {
311 return (flags & FLAG_VISIBLE) != 0;
312 }
313
314 @Override
315 public void setVisible(boolean visible) {
316 if (isNew() && !visible)
317 throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
318 updateFlags(FLAG_VISIBLE, visible);
319 }
320
321 @Override
322 public void setDeleted(boolean deleted) {
323 updateFlags(FLAG_DELETED, deleted);
324 setModified(deleted ^ !isVisible());
325 }
326
327 /**
328 * If set to true, this object is incomplete, which means only the id
329 * and type is known (type is the objects instance class)
330 * @param incomplete incomplete flag value
331 */
332 protected void setIncomplete(boolean incomplete) {
333 updateFlags(FLAG_INCOMPLETE, incomplete);
334 }
335
336 @Override
337 public boolean isIncomplete() {
338 return (flags & FLAG_INCOMPLETE) != 0;
339 }
340
341 protected String getFlagsAsString() {
342 StringBuilder builder = new StringBuilder();
343
344 if (isIncomplete()) {
345 builder.append('I');
346 }
347 if (isModified()) {
348 builder.append('M');
349 }
350 if (isVisible()) {
351 builder.append('V');
352 }
353 if (isDeleted()) {
354 builder.append('D');
355 }
356 return builder.toString();
357 }
358
359 /*------------
360 * Keys handling
361 ------------*/
362
363 /**
364 * The key/value list for this primitive.
365 * <p>
366 * Note that the keys field is synchronized using RCU.
367 * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
368 * <p>
369 * In short this means that you should not rely on this variable being the same value when read again and your should always
370 * copy it on writes.
371 * <p>
372 * Further reading:
373 * <ul>
374 * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
375 * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
376 * http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
377 * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
378 * https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
379 * {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
380 * </ul>
381 */
382 protected volatile String[] keys;
383
384 /**
385 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
386 *
387 * @return tags of this primitive. Changes made in returned map are not mapped
388 * back to the primitive, use setKeys() to modify the keys
389 * @see #visitKeys(KeyValueVisitor)
390 */
391 @Override
392 public TagMap getKeys() {
393 return new TagMap(keys);
394 }
395
396 /**
397 * Calls the visitor for every key/value pair of this primitive.
398 *
399 * @param visitor The visitor to call.
400 * @see #getKeys()
401 * @since 8742
402 */
403 public void visitKeys(KeyValueVisitor visitor) {
404 final String[] keys = this.keys;
405 if (keys != null) {
406 for (int i = 0; i < keys.length; i += 2) {
407 visitor.visitKeyValue(this, keys[i], keys[i + 1]);
408 }
409 }
410 }
411
412 /**
413 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
414 * Old key/value pairs are removed.
415 * If <code>keys</code> is null, clears existing key/value pairs.
416 * <p>
417 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
418 * from multiple threads.
419 *
420 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
421 */
422 @Override
423 public void setKeys(Map<String, String> keys) {
424 Map<String, String> originalKeys = getKeys();
425 if (keys == null || keys.isEmpty()) {
426 this.keys = null;
427 keysChangedImpl(originalKeys);
428 return;
429 }
430 String[] newKeys = new String[keys.size() * 2];
431 int index = 0;
432 for (Entry<String, String> entry:keys.entrySet()) {
433 newKeys[index++] = entry.getKey();
434 newKeys[index++] = entry.getValue();
435 }
436 this.keys = newKeys;
437 keysChangedImpl(originalKeys);
438 }
439
440 /**
441 * Copy the keys from a TagMap.
442 * @param keys The new key map.
443 */
444 public void setKeys(TagMap keys) {
445 Map<String, String> originalKeys = getKeys();
446 if (keys == null) {
447 this.keys = null;
448 } else {
449 String[] arr = keys.getTagsArray();
450 if (arr.length == 0) {
451 this.keys = null;
452 } else {
453 this.keys = arr;
454 }
455 }
456 keysChangedImpl(originalKeys);
457 }
458
459 /**
460 * Set the given value to the given key. If key is null, does nothing. If value is null,
461 * removes the key and behaves like {@link #remove(String)}.
462 * <p>
463 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
464 * from multiple threads.
465 *
466 * @param key The key, for which the value is to be set. Can be null or empty, does nothing in this case.
467 * @param value The value for the key. If null, removes the respective key/value pair.
468 *
469 * @see #remove(String)
470 */
471 @Override
472 public void put(String key, String value) {
473 Map<String, String> originalKeys = getKeys();
474 if (key == null || Utils.strip(key).isEmpty())
475 return;
476 else if (value == null) {
477 remove(key);
478 } else if (keys == null) {
479 keys = new String[] {key, value};
480 keysChangedImpl(originalKeys);
481 } else {
482 int keyIndex = indexOfKey(keys, key);
483 int tagArrayLength = keys.length;
484 if (keyIndex < 0) {
485 keyIndex = tagArrayLength;
486 tagArrayLength += 2;
487 }
488
489 // Do not try to optimize this array creation if the key already exists.
490 // We would need to convert the keys array to be an AtomicReferenceArray
491 // Or we would at least need a volatile write after the array was modified to
492 // ensure that changes are visible by other threads.
493 String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
494 newKeys[keyIndex] = key;
495 newKeys[keyIndex + 1] = value;
496 keys = newKeys;
497 keysChangedImpl(originalKeys);
498 }
499 }
500
501 /**
502 * Scans a key/value array for a given key.
503 * @param keys The key array. It is not modified. It may be null to indicate an emtpy array.
504 * @param key The key to search for.
505 * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
506 */
507 private static int indexOfKey(String[] keys, String key) {
508 if (keys == null) {
509 return -1;
510 }
511 for (int i = 0; i < keys.length; i += 2) {
512 if (keys[i].equals(key)) {
513 return i;
514 }
515 }
516 return -1;
517 }
518
519 /**
520 * Remove the given key from the list
521 * <p>
522 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
523 * from multiple threads.
524 *
525 * @param key the key to be removed. Ignored, if key is null.
526 */
527 @Override
528 public void remove(String key) {
529 if (key == null || keys == null) return;
530 if (!hasKey(key))
531 return;
532 Map<String, String> originalKeys = getKeys();
533 if (keys.length == 2) {
534 keys = null;
535 keysChangedImpl(originalKeys);
536 return;
537 }
538 String[] newKeys = new String[keys.length - 2];
539 int j = 0;
540 for (int i = 0; i < keys.length; i += 2) {
541 if (!keys[i].equals(key)) {
542 newKeys[j++] = keys[i];
543 newKeys[j++] = keys[i+1];
544 }
545 }
546 keys = newKeys;
547 keysChangedImpl(originalKeys);
548 }
549
550 /**
551 * Removes all keys from this primitive.
552 * <p>
553 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
554 * from multiple threads.
555 */
556 @Override
557 public void removeAll() {
558 if (keys != null) {
559 Map<String, String> originalKeys = getKeys();
560 keys = null;
561 keysChangedImpl(originalKeys);
562 }
563 }
564
565 /**
566 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
567 * Replies null, if there is no value for the given key.
568 *
569 * @param key the key. Can be null, replies null in this case.
570 * @return the value for key <code>key</code>.
571 */
572 @Override
573 public final String get(String key) {
574 String[] keys = this.keys;
575 if (key == null)
576 return null;
577 if (keys == null)
578 return null;
579 for (int i = 0; i < keys.length; i += 2) {
580 if (keys[i].equals(key)) return keys[i+1];
581 }
582 return null;
583 }
584
585 /**
586 * Returns true if the {@code key} corresponds to an OSM true value.
587 * @param key OSM key
588 * @return {@code true} if the {@code key} corresponds to an OSM true value
589 * @see OsmUtils#isTrue(String)
590 */
591 public final boolean isKeyTrue(String key) {
592 return OsmUtils.isTrue(get(key));
593 }
594
595 /**
596 * Returns true if the {@code key} corresponds to an OSM false value.
597 * @param key OSM key
598 * @return {@code true} if the {@code key} corresponds to an OSM false value
599 * @see OsmUtils#isFalse(String)
600 */
601 public final boolean isKeyFalse(String key) {
602 return OsmUtils.isFalse(get(key));
603 }
604
605 public final String getIgnoreCase(String key) {
606 String[] keys = this.keys;
607 if (key == null)
608 return null;
609 if (keys == null)
610 return null;
611 for (int i = 0; i < keys.length; i += 2) {
612 if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
613 }
614 return null;
615 }
616
617 public final int getNumKeys() {
618 String[] keys = this.keys;
619 return keys == null ? 0 : keys.length / 2;
620 }
621
622 @Override
623 public final Collection<String> keySet() {
624 final String[] keys = this.keys;
625 if (keys == null) {
626 return Collections.emptySet();
627 }
628 if (keys.length == 1) {
629 return Collections.singleton(keys[0]);
630 }
631
632 final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
633 for (int i = 0; i < keys.length; i += 2) {
634 result.add(keys[i]);
635 }
636 return result;
637 }
638
639 /**
640 * Replies true, if the map of key/value pairs of this primitive is not empty.
641 *
642 * @return true, if the map of key/value pairs of this primitive is not empty; false
643 * otherwise
644 */
645 @Override
646 public final boolean hasKeys() {
647 return keys != null;
648 }
649
650 /**
651 * Replies true if this primitive has a tag with key <code>key</code>.
652 *
653 * @param key the key
654 * @return true, if his primitive has a tag with key <code>key</code>
655 */
656 public boolean hasKey(String key) {
657 return key != null && indexOfKey(keys, key) >= 0;
658 }
659
660 /**
661 * What to do, when the tags have changed by one of the tag-changing methods.
662 * @param originalKeys original tags
663 */
664 protected abstract void keysChangedImpl(Map<String, String> originalKeys);
665
666 @Override
667 public String getName() {
668 return get("name");
669 }
670
671 @Override
672 public String getLocalName() {
673 for (String s : LanguageInfo.getLanguageCodes(null)) {
674 String val = get("name:" + s);
675 if (val != null)
676 return val;
677 }
678
679 return getName();
680 }
681
682 /**
683 * Tests whether this primitive contains a tag consisting of {@code key} and {@code values}.
684 * @param key the key forming the tag.
685 * @param value value forming the tag.
686 * @return true iff primitive contains a tag consisting of {@code key} and {@code value}.
687 */
688 public boolean hasTag(String key, String value) {
689 return Objects.equals(value, get(key));
690 }
691
692 /**
693 * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
694 * @param key the key forming the tag.
695 * @param values one or many values forming the tag.
696 * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
697 */
698 public boolean hasTag(String key, String... values) {
699 return hasTag(key, Arrays.asList(values));
700 }
701
702 /**
703 * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
704 * @param key the key forming the tag.
705 * @param values one or many values forming the tag.
706 * @return true iff primitive contains a tag consisting of {@code key} and any of {@code values}.
707 */
708 public boolean hasTag(String key, Collection<String> values) {
709 return values.contains(get(key));
710 }
711}
Note: See TracBrowser for help on using the repository browser.