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

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

extract getNumKeys() from AbstractPrimitive to Tagged

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