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

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

sonar - fb-contrib:SEO_SUBOPTIMAL_EXPRESSION_ORDER - Performance - Method orders expressions in a conditional in a sub optimal way

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