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

Last change on this file was 19331, checked in by stoecker, 3 weeks ago

checkstyle

  • Property svn:eol-style set to native
File size: 32.1 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.time.Instant;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Objects;
19import java.util.Set;
20import java.util.function.BiPredicate;
21import java.util.stream.IntStream;
22import java.util.stream.Stream;
23
24import org.openstreetmap.josm.data.gpx.GpxConstants;
25import org.openstreetmap.josm.spi.preferences.Config;
26import org.openstreetmap.josm.tools.Utils;
27
28/**
29 * Abstract class to represent common features of the datatypes primitives.
30 *
31 * @since 4099
32 */
33public abstract class AbstractPrimitive implements IPrimitive, IFilterablePrimitive {
34
35 /**
36 * This flag shows, that the properties have been changed by the user
37 * and on upload the object will be send to the server.
38 */
39 protected static final short FLAG_MODIFIED = 1 << 0;
40
41 /**
42 * This flag is false, if the object is marked
43 * as deleted on the server.
44 */
45 protected static final short FLAG_VISIBLE = 1 << 1;
46
47 /**
48 * An object that was deleted by the user.
49 * Deleted objects are usually hidden on the map and a request
50 * for deletion will be send to the server on upload.
51 * An object usually cannot be deleted if it has non-deleted
52 * objects still referring to it.
53 */
54 protected static final short FLAG_DELETED = 1 << 2;
55
56 /**
57 * A primitive is incomplete if we know its id and type, but nothing more.
58 * Typically some members of a relation are incomplete until they are
59 * fetched from the server.
60 */
61 protected static final short FLAG_INCOMPLETE = 1 << 3;
62
63 /**
64 * An object can be disabled by the filter mechanism.
65 * Then it will show in a shade of gray on the map or it is completely
66 * hidden from the view.
67 * Disabled objects usually cannot be selected or modified
68 * while the filter is active.
69 */
70 protected static final short FLAG_DISABLED = 1 << 4;
71
72 /**
73 * This flag is only relevant if an object is disabled by the
74 * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
75 * Then it indicates, whether it is completely hidden or
76 * just shown in gray color.
77 * <p>
78 * When the primitive is not disabled, this flag should be
79 * unset as well (for efficient access).
80 */
81 protected static final short FLAG_HIDE_IF_DISABLED = 1 << 5;
82
83 /**
84 * Flag used internally by the filter mechanism.
85 */
86 protected static final short FLAG_DISABLED_TYPE = 1 << 6;
87
88 /**
89 * Flag used internally by the filter mechanism.
90 */
91 protected static final short FLAG_HIDDEN_TYPE = 1 << 7;
92
93 /**
94 * This flag is set if the primitive is a way and
95 * according to the tags, the direction of the way is important.
96 * (e.g. one way street.)
97 */
98 protected static final short FLAG_HAS_DIRECTIONS = 1 << 8;
99
100 /**
101 * If the primitive is tagged.
102 * Some trivial tags like source=* are ignored here.
103 */
104 protected static final short FLAG_TAGGED = 1 << 9;
105
106 /**
107 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
108 * It shows, that direction of the arrows should be reversed.
109 * (E.g. oneway=-1.)
110 */
111 protected static final short FLAG_DIRECTION_REVERSED = 1 << 10;
112
113 /**
114 * When hovering over ways and nodes in add mode, the
115 * "target" objects are visually highlighted. This flag indicates
116 * that the primitive is currently highlighted.
117 */
118 protected static final short FLAG_HIGHLIGHTED = 1 << 11;
119
120 /**
121 * If the primitive is annotated with a tag such as note, fixme, etc.
122 * Match the "work in progress" tags in default map style.
123 */
124 protected static final short FLAG_ANNOTATED = 1 << 12;
125
126 /**
127 * Determines if the primitive is preserved from the filter mechanism.
128 */
129 protected static final short FLAG_PRESERVED = 1 << 13;
130
131 /**
132 * Determines if the primitive has all of its referrers
133 */
134 protected static final short FLAG_ALL_REFERRERS_DOWNLOADED = 1 << 14;
135
136 /**
137 * Put several boolean flags to one short int field to save memory.
138 * Other bits of this field are used in subclasses.
139 */
140 protected volatile short flags = FLAG_VISIBLE; // visible per default
141
142 /**
143 * The mappaint cache index for this primitive.
144 * This field belongs to {@code OsmPrimitive}, but due to Java's memory layout alignment, see #20830.
145 */
146 protected short mappaintCacheIdx;
147
148 /*-------------------
149 * OTHER PROPERTIES
150 *-------------------*/
151
152 /**
153 * Unique identifier in OSM. This is used to identify objects on the server.
154 * An id of 0 means an unknown id. The object has not been uploaded yet to
155 * know what id it will get.
156 */
157 protected long id;
158
159 /**
160 * User that last modified this primitive, as specified by the server.
161 * Never changed by JOSM.
162 */
163 protected User user;
164
165 /**
166 * Contains the version number as returned by the API. Needed to
167 * ensure update consistency
168 */
169 protected int version;
170
171 /**
172 * The id of the changeset this primitive was last uploaded to.
173 * 0 if it wasn't uploaded to a changeset yet of if the changeset
174 * id isn't known.
175 */
176 protected int changesetId;
177
178 /**
179 * A time value, measured in seconds from the epoch, or in other words,
180 * a number of seconds that have passed since 1970-01-01T00:00:00Z
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 return id >= 0 ? id : 0;
220 }
221
222 /**
223 * Gets a unique id representing this object.
224 *
225 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
226 */
227 @Override
228 public long getUniqueId() {
229 return id;
230 }
231
232 /**
233 * Determines if this primitive is new.
234 * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
235 */
236 @Override
237 public boolean isNew() {
238 return id <= 0;
239 }
240
241 @Override
242 public boolean isNewOrUndeleted() {
243 return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
244 }
245
246 @Override
247 public void setOsmId(long id, int version) {
248 if (id <= 0)
249 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
250 if (version <= 0)
251 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
252 this.id = id;
253 this.version = version;
254 this.setIncomplete(false);
255 }
256
257 /**
258 * Clears the metadata, including id and version known to the OSM API.
259 * The id is a new unique id. The version, changeset and timestamp are set to 0.
260 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
261 * of calling this method.
262 * @since 6140
263 */
264 public void clearOsmMetadata() {
265 // Not part of dataset - no lock necessary
266 this.id = getIdGenerator().generateUniqueId();
267 this.version = 0;
268 this.user = null;
269 this.changesetId = 0; // reset changeset id on a new object
270 this.timestamp = 0;
271 this.setIncomplete(false);
272 this.setDeleted(false);
273 this.setVisible(true);
274 }
275
276 /**
277 * Returns the unique identifier generator.
278 * @return the unique identifier generator
279 * @since 15820
280 */
281 public abstract UniqueIdGenerator getIdGenerator();
282
283 @Override
284 public User getUser() {
285 return user;
286 }
287
288 @Override
289 public void setUser(User user) {
290 this.user = user;
291 }
292
293 @Override
294 public int getChangesetId() {
295 return changesetId;
296 }
297
298 @Override
299 public void setChangesetId(int changesetId) {
300 if (this.changesetId == changesetId)
301 return;
302 if (changesetId < 0)
303 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
304 if (changesetId > 0 && isNew())
305 throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
306
307 this.changesetId = changesetId;
308 }
309
310 @Override
311 public void setInstant(Instant timestamp) {
312 this.timestamp = (int) timestamp.getEpochSecond();
313 }
314
315 @Override
316 public void setRawTimestamp(int timestamp) {
317 this.timestamp = timestamp;
318 }
319
320 @Override
321 public Instant getInstant() {
322 return Instant.ofEpochSecond(Integer.toUnsignedLong(timestamp));
323 }
324
325 @Override
326 public int getRawTimestamp() {
327 return timestamp;
328 }
329
330 @Override
331 public boolean isTimestampEmpty() {
332 return timestamp == 0;
333 }
334
335 /* -------
336 /* FLAGS
337 /* ------*/
338
339 protected void updateFlags(short flag, boolean value) {
340 if (value) {
341 flags |= flag;
342 } else {
343 flags &= (short) ~flag;
344 }
345 }
346
347 /**
348 * Update flags
349 * @param flag The flag to update
350 * @param value The value to set
351 * @return {@code true} if the flags have changed
352 */
353 protected boolean updateFlagsChanged(short flag, boolean value) {
354 int oldFlags = flags;
355 updateFlags(flag, value);
356 return oldFlags != flags;
357 }
358
359 @Override
360 public void setModified(boolean modified) {
361 updateFlags(FLAG_MODIFIED, modified);
362 }
363
364 @Override
365 public boolean isModified() {
366 return (flags & FLAG_MODIFIED) != 0;
367 }
368
369 @Override
370 public boolean isDeleted() {
371 return (flags & FLAG_DELETED) != 0;
372 }
373
374 @Override
375 public void setReferrersDownloaded(boolean referrersDownloaded) {
376 this.updateFlags(FLAG_ALL_REFERRERS_DOWNLOADED, referrersDownloaded);
377 }
378
379 @Override
380 public boolean isUndeleted() {
381 return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
382 }
383
384 @Override
385 public boolean isUsable() {
386 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
387 }
388
389 @Override
390 public boolean isVisible() {
391 return (flags & FLAG_VISIBLE) != 0;
392 }
393
394 @Override
395 public void setVisible(boolean visible) {
396 if (!visible && isNew())
397 throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
398 updateFlags(FLAG_VISIBLE, visible);
399 }
400
401 @Override
402 public void setDeleted(boolean deleted) {
403 updateFlags(FLAG_DELETED, deleted);
404 setModified(deleted ^ !isVisible());
405 }
406
407 @Override
408 public boolean hasDirectionKeys() {
409 return (flags & FLAG_HAS_DIRECTIONS) != 0;
410 }
411
412 @Override
413 public boolean reversedDirection() {
414 return (flags & FLAG_DIRECTION_REVERSED) != 0;
415 }
416
417 @Override
418 public boolean isTagged() {
419 return (flags & FLAG_TAGGED) != 0;
420 }
421
422 @Override
423 public boolean isAnnotated() {
424 return (flags & FLAG_ANNOTATED) != 0;
425 }
426
427 @Override
428 public boolean isHighlighted() {
429 return (flags & FLAG_HIGHLIGHTED) != 0;
430 }
431
432 @Override
433 public boolean isDisabled() {
434 return (flags & FLAG_DISABLED) != 0;
435 }
436
437 @Override
438 public boolean isDisabledAndHidden() {
439 return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
440 }
441
442 @Override
443 public boolean isPreserved() {
444 return (flags & FLAG_PRESERVED) != 0;
445 }
446
447 @Override
448 public boolean isReferrersDownloaded() {
449 return isNew() || (flags & FLAG_ALL_REFERRERS_DOWNLOADED) != 0;
450 }
451
452 /**
453 * If set to true, this object is incomplete, which means only the id
454 * and type is known (type is the objects instance class)
455 * @param incomplete incomplete flag value
456 */
457 protected void setIncomplete(boolean incomplete) {
458 updateFlags(FLAG_INCOMPLETE, incomplete);
459 }
460
461 @Override
462 public boolean isIncomplete() {
463 return (flags & FLAG_INCOMPLETE) != 0;
464 }
465
466 @Override
467 public boolean getHiddenType() {
468 return (flags & FLAG_HIDDEN_TYPE) != 0;
469 }
470
471 @Override
472 public boolean getDisabledType() {
473 return (flags & FLAG_DISABLED_TYPE) != 0;
474 }
475
476 @Override
477 public boolean setDisabledState(boolean hidden) {
478 // Store as variables to avoid short circuit boolean return
479 final boolean flagDisabled = updateFlagsChanged(FLAG_DISABLED, true);
480 final boolean flagHideIfDisabled = updateFlagsChanged(FLAG_HIDE_IF_DISABLED, hidden);
481 return flagDisabled || flagHideIfDisabled;
482 }
483
484 @Override
485 public boolean unsetDisabledState() {
486 // Store as variables to avoid short circuit boolean return
487 final boolean flagDisabled = updateFlagsChanged(FLAG_DISABLED, false);
488 final boolean flagHideIfDisabled = updateFlagsChanged(FLAG_HIDE_IF_DISABLED, false);
489 return flagDisabled || flagHideIfDisabled;
490 }
491
492 @Override
493 public void setDisabledType(boolean isExplicit) {
494 updateFlags(FLAG_DISABLED_TYPE, isExplicit);
495 }
496
497 @Override
498 public void setHiddenType(boolean isExplicit) {
499 updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
500 }
501
502 @Override
503 public boolean isDrawable() {
504 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
505 }
506
507 protected String getFlagsAsString() {
508 StringBuilder builder = new StringBuilder();
509
510 if (isIncomplete()) {
511 builder.append('I');
512 }
513 if (isModified()) {
514 builder.append('M');
515 }
516 if (isVisible()) {
517 builder.append('V');
518 }
519 if (isDeleted()) {
520 builder.append('D');
521 }
522 return builder.toString();
523 }
524
525 /*------------
526 * Keys handling
527 ------------*/
528
529 /**
530 * The key/value list for this primitive.
531 * <p>
532 * Note that the keys field is synchronized using RCU.
533 * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
534 * <p>
535 * In short this means that you should not rely on this variable being the same value when read again and your should always
536 * copy it on writes.
537 * <p>
538 * Further reading:
539 * <ul>
540 * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
541 * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
542 * http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
543 * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
544 * https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
545 * {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
546 * </ul>
547 */
548 protected volatile String[] keys;
549
550 /**
551 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
552 *
553 * @return tags of this primitive. Changes made in returned map are not mapped
554 * back to the primitive, use setKeys() to modify the keys
555 * @see #visitKeys(KeyValueVisitor)
556 */
557 @Override
558 public TagMap getKeys() {
559 return new TagMap(keys);
560 }
561
562 @Override
563 public void visitKeys(KeyValueVisitor visitor) {
564 String[] tKeys = this.keys;
565 if (tKeys != null) {
566 for (int i = 0; i < tKeys.length; i += 2) {
567 visitor.visitKeyValue(this, tKeys[i], tKeys[i + 1]);
568 }
569 }
570 }
571
572 /**
573 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
574 * Old key/value pairs are removed.
575 * If <code>keys</code> is null, clears existing key/value pairs.
576 * <p>
577 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
578 * from multiple threads.
579 *
580 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
581 */
582 @Override
583 public void setKeys(Map<String, String> keys) {
584 Map<String, String> originalKeys = getKeys();
585 if (Utils.isEmpty(keys)) {
586 this.keys = null;
587 keysChangedImpl(originalKeys);
588 return;
589 }
590 String[] newKeys = new String[keys.size() * 2];
591 int index = 0;
592 for (Entry<String, String> entry:keys.entrySet()) {
593 newKeys[index++] = Objects.requireNonNull(entry.getKey());
594 newKeys[index++] = Objects.requireNonNull(entry.getValue());
595 }
596 this.keys = newKeys;
597 keysChangedImpl(originalKeys);
598 }
599
600 /**
601 * Copy the keys from a TagMap.
602 * @param keys The new key map.
603 */
604 public void setKeys(TagMap keys) {
605 Map<String, String> originalKeys = getKeys();
606 if (keys == null) {
607 this.keys = null;
608 } else {
609 String[] arr = keys.getTagsArray();
610 if (arr.length == 0) {
611 this.keys = null;
612 } else {
613 this.keys = arr;
614 }
615 }
616 keysChangedImpl(originalKeys);
617 }
618
619 /**
620 * Set the given value to the given key. If key is null, does nothing. If value is null,
621 * removes the key and behaves like {@link #remove(String)}.
622 * <p>
623 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
624 * from multiple threads.
625 *
626 * @param key The key, for which the value is to be set. Can be null or empty, does nothing in this case.
627 * @param value The value for the key. If null, removes the respective key/value pair.
628 *
629 * @see #remove(String)
630 */
631 @Override
632 public void put(String key, String value) {
633 Map<String, String> originalKeys = getKeys();
634 if (key == null || Utils.isStripEmpty(key))
635 return;
636 if (value == null) {
637 remove(key);
638 } else if (keys == null) {
639 keys = new String[] {key, value};
640 keysChangedImpl(originalKeys);
641 } else {
642 int keyIndex = indexOfKey(keys, key);
643 int tagArrayLength = keys.length;
644 if (keyIndex < 0) {
645 keyIndex = tagArrayLength;
646 tagArrayLength += 2;
647 }
648
649 // Do not try to optimize this array creation if the key already exists.
650 // We would need to convert the keys array to be an AtomicReferenceArray
651 // Or we would at least need a volatile write after the array was modified to
652 // ensure that changes are visible by other threads.
653 String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
654 newKeys[keyIndex] = key;
655 newKeys[keyIndex + 1] = value;
656 keys = newKeys;
657 keysChangedImpl(originalKeys);
658 }
659 }
660
661 @Override
662 public void putAll(Map<String, String> tags) {
663 if (tags == null || tags.isEmpty()) {
664 return;
665 }
666 // Defensive copy of keys
667 final String[] tKeys = this.keys; // Used to avoid highly unlikely NPE (from a race) during clone operation.
668 String[] newKeys = tKeys == null ? null : tKeys.clone();
669 Map<String, String> originalKeys = getKeys();
670 List<Map.Entry<String, String>> tagsToAdd = new ArrayList<>(tags.size());
671 for (Map.Entry<String, String> tag : tags.entrySet()) {
672 if (!Utils.isStripEmpty(tag.getKey())) {
673 int keyIndex = indexOfKey(newKeys, tag.getKey());
674 // Realistically, we will not hit the newKeys == null branch. If it is null, keyIndex is always < 0
675 if (keyIndex < 0 || newKeys == null) {
676 tagsToAdd.add(tag);
677 } else {
678 newKeys[keyIndex + 1] = tag.getValue();
679 }
680 }
681 }
682 if (!tagsToAdd.isEmpty()) {
683 int index = newKeys != null ? newKeys.length : 0;
684 newKeys = newKeys != null ? Arrays.copyOf(newKeys, newKeys.length + 2 * tagsToAdd.size()) : new String[2 * tagsToAdd.size()];
685 for (Map.Entry<String, String> tag : tagsToAdd) {
686 newKeys[index++] = tag.getKey();
687 newKeys[index++] = tag.getValue();
688 }
689 }
690 keys = newKeys;
691 keysChangedImpl(originalKeys);
692 }
693
694 /**
695 * Scans a key/value array for a given key.
696 * @param keys The key array. It is not modified. It may be null to indicate an empty array.
697 * @param key The key to search for.
698 * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
699 */
700 private static int indexOfKey(String[] keys, String key) {
701 if (keys == null) {
702 return -1;
703 }
704 for (int i = 0; i < keys.length; i += 2) {
705 if (keys[i].equals(key)) {
706 return i;
707 }
708 }
709 return -1;
710 }
711
712 /**
713 * Remove the given key from the list
714 * <p>
715 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
716 * from multiple threads.
717 *
718 * @param key the key to be removed. Ignored, if key is null.
719 */
720 @Override
721 public void remove(String key) {
722 if (key == null || keys == null) return;
723 if (!hasKey(key))
724 return;
725 Map<String, String> originalKeys = getKeys();
726 if (keys.length == 2) {
727 keys = null;
728 keysChangedImpl(originalKeys);
729 return;
730 }
731 String[] newKeys = new String[keys.length - 2];
732 int j = 0;
733 for (int i = 0; i < keys.length; i += 2) {
734 if (!keys[i].equals(key)) {
735 newKeys[j++] = keys[i];
736 newKeys[j++] = keys[i+1];
737 }
738 }
739 keys = newKeys;
740 keysChangedImpl(originalKeys);
741 }
742
743 /**
744 * Removes all keys from this primitive.
745 * <p>
746 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
747 * from multiple threads.
748 */
749 @Override
750 public void removeAll() {
751 if (keys != null) {
752 Map<String, String> originalKeys = getKeys();
753 keys = null;
754 keysChangedImpl(originalKeys);
755 }
756 }
757
758 protected final String doGet(String key, BiPredicate<String, String> predicate) {
759 if (key == null)
760 return null;
761 if (keys == null)
762 return null;
763 for (int i = 0; i < keys.length; i += 2) {
764 if (predicate.test(keys[i], key)) return keys[i+1];
765 }
766 return null;
767 }
768
769 /**
770 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
771 * Replies null, if there is no value for the given key.
772 *
773 * @param key the key. Can be null, replies null in this case.
774 * @return the value for key <code>key</code>.
775 */
776 @Override
777 public final String get(String key) {
778 return doGet(key, String::equals);
779 }
780
781 /**
782 * Gets a key ignoring the case of the key
783 * @param key The key to get
784 * @return The value for a key that matches the given key ignoring case.
785 */
786 public final String getIgnoreCase(String key) {
787 return doGet(key, String::equalsIgnoreCase);
788 }
789
790 @Override
791 public final int getNumKeys() {
792 return keys == null ? 0 : keys.length / 2;
793 }
794
795 @Override
796 @SuppressWarnings("PMD.UseArraysAsList") // See https://github.com/pmd/pmd/issues/5071
797 public final Collection<String> keySet() {
798 String[] tKeys = this.keys;
799 if (tKeys == null) {
800 return Collections.emptySet();
801 }
802 if (tKeys.length == 2) {
803 return Collections.singleton(tKeys[0]);
804 }
805
806 final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(tKeys.length / 2));
807 for (int i = 0; i < tKeys.length; i += 2) {
808 result.add(tKeys[i]);
809 }
810 return result;
811 }
812
813 @Override
814 public Stream<String> keys() {
815 final String[] k = this.keys;
816 if (k == null) {
817 return Stream.empty();
818 } else if (k.length == 2) {
819 return Stream.of(k[0]);
820 } else {
821 return IntStream.range(0, k.length / 2).mapToObj(i -> k[i * 2]);
822 }
823 }
824
825 /**
826 * Replies true, if the map of key/value pairs of this primitive is not empty.
827 *
828 * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
829 */
830 @Override
831 public final boolean hasKeys() {
832 return keys != null;
833 }
834
835 /**
836 * Replies true if this primitive has a tag with key <code>key</code>.
837 *
838 * @param key the key
839 * @return true, if this primitive has a tag with key <code>key</code>
840 */
841 @Override
842 public boolean hasKey(String key) {
843 return key != null && indexOfKey(keys, key) >= 0;
844 }
845
846 /**
847 * Replies true if this primitive has a tag any of the <code>keys</code>.
848 *
849 * @param keys the keys
850 * @return true, if this primitive has a tag with any of the <code>keys</code>
851 * @since 11587
852 */
853 public boolean hasKey(String... keys) {
854 if (keys != null) {
855 for (String key : keys) {
856 if (this.hasKey(key)) {
857 return true;
858 }
859 }
860 }
861 return false;
862 }
863
864 /**
865 * What to do, when the tags have changed by one of the tag-changing methods.
866 * @param originalKeys original tags
867 */
868 protected abstract void keysChangedImpl(Map<String, String> originalKeys);
869
870 /*-------------------------------------
871 * WORK IN PROGRESS, UNINTERESTING KEYS
872 *-------------------------------------*/
873
874 private static volatile Collection<String> workinprogress;
875 private static volatile Collection<String> uninteresting;
876 private static volatile Collection<String> discardable;
877
878 /**
879 * Returns a list of "uninteresting" keys that do not make an object
880 * "tagged". Entries that end with ':' are causing a whole namespace to be considered
881 * "uninteresting". Only the first level namespace is considered.
882 * Initialized by isUninterestingKey()
883 * @return The list of uninteresting keys.
884 */
885 public static Collection<String> getUninterestingKeys() {
886 if (uninteresting == null) {
887 List<String> l = new LinkedList<>(Arrays.asList(
888 "source", "source_ref", "source:", "comment", "import",
889 "watch", "watch:", "description", "attribution", GpxConstants.GPX_PREFIX));
890 l.addAll(getDiscardableKeys());
891 l.addAll(getWorkInProgressKeys());
892 uninteresting = new HashSet<>(Config.getPref().getList("tags.uninteresting", l));
893 }
894 return uninteresting;
895 }
896
897 /**
898 * Returns a list of keys which have been deemed uninteresting to the point
899 * that they can be silently removed from data which is being edited.
900 * @return The list of discardable keys.
901 */
902 public static Collection<String> getDiscardableKeys() {
903 if (discardable == null) {
904 discardable = new HashSet<>(Config.getPref().getList("tags.discardable",
905 Arrays.asList(
906 "created_by",
907 "current_id", /* prevent export of this JOSM internal information, see OsmReader */
908 "geobase:datasetName",
909 "geobase:uuid",
910 "KSJ2:ADS",
911 "KSJ2:ARE",
912 "KSJ2:AdminArea",
913 "KSJ2:COP_label",
914 "KSJ2:DFD",
915 "KSJ2:INT",
916 "KSJ2:INT_label",
917 "KSJ2:LOC",
918 "KSJ2:LPN",
919 "KSJ2:OPC",
920 "KSJ2:PubFacAdmin",
921 "KSJ2:RAC",
922 "KSJ2:RAC_label",
923 "KSJ2:RIC",
924 "KSJ2:RIN",
925 "KSJ2:WSC",
926 "KSJ2:coordinate",
927 "KSJ2:curve_id",
928 "KSJ2:curve_type",
929 "KSJ2:filename",
930 "KSJ2:lake_id",
931 "KSJ2:lat",
932 "KSJ2:long",
933 "KSJ2:river_id",
934 "LINZ:dataset",
935 "LINZ:layer",
936 "LINZ:source_version",
937 "LINZ2OSM:dataset",
938 "LINZ2OSM:layer",
939 "linz2osm:objectid",
940 "LINZ2OSM:source_version",
941 "fid",
942 "odbl",
943 "odbl:note",
944 "osmarender:nameDirection",
945 "osmarender:renderName",
946 "osmarender:renderRef",
947 "osmarender:rendernames",
948 "SK53_bulk:load",
949 "sub_sea:type",
950 "tiger:source",
951 "tiger:separated",
952 "tiger:tlid",
953 "tiger:upload_uuid",
954 "import_uuid",
955 "gnis:import_uuid",
956 "yh:LINE_NAME",
957 "yh:LINE_NUM",
958 "yh:STRUCTURE",
959 "yh:TOTYUMONO",
960 "yh:TYPE",
961 "yh:WIDTH",
962 "yh:WIDTH_RANK"
963 )));
964 }
965 return discardable;
966 }
967
968 /**
969 * Returns a list of "work in progress" keys that do not make an object
970 * "tagged" but "annotated".
971 * @return The list of work in progress keys.
972 * @since 5754
973 */
974 public static Collection<String> getWorkInProgressKeys() {
975 if (workinprogress == null) {
976 workinprogress = new HashSet<>(Config.getPref().getList("tags.workinprogress",
977 Arrays.asList("note", "fixme", "FIXME")));
978 }
979 return workinprogress;
980 }
981
982 /**
983 * Determines if key is considered "uninteresting".
984 * @param key The key to check
985 * @return true if key is considered "uninteresting".
986 */
987 public static boolean isUninterestingKey(String key) {
988 getUninterestingKeys();
989 if (uninteresting.contains(key))
990 return true;
991 int pos = key.indexOf(':');
992 if (pos > 0)
993 return uninteresting.contains(key.substring(0, pos + 1));
994 return false;
995 }
996
997 @Override
998 public Map<String, String> getInterestingTags() {
999 Map<String, String> result = new HashMap<>();
1000 if (keys != null) {
1001 for (int i = 0; i < keys.length; i += 2) {
1002 if (!isUninterestingKey(keys[i])) {
1003 result.put(keys[i], keys[i + 1]);
1004 }
1005 }
1006 }
1007 return result;
1008 }
1009}
Note: See TracBrowser for help on using the repository browser.