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

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

fix #15766, see #15688 - fix performance regression introduced in r13229 when drawing a way of many nodes while the filter dialog is open

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