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

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

partial revert of r12537

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