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

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

see #11390 - sonar - squid:S1609 - Java 8: @FunctionalInterface annotation should be used to flag Single Abstract Method interfaces

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