001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.text.MessageFormat;
007import java.util.Arrays;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.Date;
011import java.util.HashSet;
012import java.util.Map;
013import java.util.Map.Entry;
014import java.util.Objects;
015import java.util.Set;
016import java.util.concurrent.TimeUnit;
017import java.util.concurrent.atomic.AtomicLong;
018
019import org.openstreetmap.josm.tools.LanguageInfo;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023* Abstract class to represent common features of the datatypes primitives.
024*
025* @since 4099
026*/
027public abstract class AbstractPrimitive implements IPrimitive {
028
029    private static final AtomicLong idCounter = new AtomicLong(0);
030
031    /**
032     * Generates a new primitive unique id.
033     * @return new primitive unique (negative) id
034     */
035    static long generateUniqueId() {
036        return idCounter.decrementAndGet();
037    }
038
039    /**
040     * Returns the current primitive unique id.
041     * @return the current primitive unique (negative) id (last generated)
042     * @since 12536
043     */
044    public static long currentUniqueId() {
045        return idCounter.get();
046    }
047
048    /**
049     * Advances the current primitive unique id to skip a range of values.
050     * @param newId new unique id
051     * @throws IllegalArgumentException if newId is greater than current unique id
052     * @since 12536
053     */
054    public static void advanceUniqueId(long newId) {
055        if (newId > currentUniqueId()) {
056            throw new IllegalArgumentException("Cannot modify the id counter backwards");
057        }
058        idCounter.set(newId);
059    }
060
061    /**
062     * This flag shows, that the properties have been changed by the user
063     * and on upload the object will be send to the server.
064     */
065    protected static final short FLAG_MODIFIED = 1 << 0;
066
067    /**
068     * This flag is false, if the object is marked
069     * as deleted on the server.
070     */
071    protected static final short FLAG_VISIBLE = 1 << 1;
072
073    /**
074     * An object that was deleted by the user.
075     * Deleted objects are usually hidden on the map and a request
076     * for deletion will be send to the server on upload.
077     * An object usually cannot be deleted if it has non-deleted
078     * objects still referring to it.
079     */
080    protected static final short FLAG_DELETED = 1 << 2;
081
082    /**
083     * A primitive is incomplete if we know its id and type, but nothing more.
084     * Typically some members of a relation are incomplete until they are
085     * fetched from the server.
086     */
087    protected static final short FLAG_INCOMPLETE = 1 << 3;
088
089    /**
090     * An object can be disabled by the filter mechanism.
091     * Then it will show in a shade of gray on the map or it is completely
092     * hidden from the view.
093     * Disabled objects usually cannot be selected or modified
094     * while the filter is active.
095     */
096    protected static final short FLAG_DISABLED = 1 << 4;
097
098    /**
099     * 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    /**
696     * Gets the number of keys
697     * @return The number of keys set for this primitive.
698     */
699    public final int getNumKeys() {
700        String[] keys = this.keys;
701        return keys == null ? 0 : keys.length / 2;
702    }
703
704    @Override
705    public final Collection<String> keySet() {
706        final String[] keys = this.keys;
707        if (keys == null) {
708            return Collections.emptySet();
709        }
710        if (keys.length == 1) {
711            return Collections.singleton(keys[0]);
712        }
713
714        final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
715        for (int i = 0; i < keys.length; i += 2) {
716            result.add(keys[i]);
717        }
718        return result;
719    }
720
721    /**
722     * Replies true, if the map of key/value pairs of this primitive is not empty.
723     *
724     * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
725     */
726    @Override
727    public final boolean hasKeys() {
728        return keys != null;
729    }
730
731    /**
732     * Replies true if this primitive has a tag with key <code>key</code>.
733     *
734     * @param key the key
735     * @return true, if this primitive has a tag with key <code>key</code>
736     */
737    @Override
738    public boolean hasKey(String key) {
739        return key != null && indexOfKey(keys, key) >= 0;
740    }
741
742    /**
743     * Replies true if this primitive has a tag any of the <code>keys</code>.
744     *
745     * @param keys the keys
746     * @return true, if this primitive has a tag with any of the <code>keys</code>
747     * @since 11587
748     */
749    public boolean hasKey(String... keys) {
750        return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
751    }
752
753    /**
754     * What to do, when the tags have changed by one of the tag-changing methods.
755     * @param originalKeys original tags
756     */
757    protected abstract void keysChangedImpl(Map<String, String> originalKeys);
758
759    @Override
760    public String getName() {
761        return get("name");
762    }
763
764    @Override
765    public String getLocalName() {
766        for (String s : LanguageInfo.getLanguageCodes(null)) {
767            String val = get("name:" + s);
768            if (val != null)
769                return val;
770        }
771
772        return getName();
773    }
774
775    /**
776     * Tests whether this primitive contains a tag consisting of {@code key} and {@code value}.
777     * @param key the key forming the tag.
778     * @param value value forming the tag.
779     * @return true if primitive contains a tag consisting of {@code key} and {@code value}.
780     */
781    public boolean hasTag(String key, String value) {
782        return Objects.equals(value, get(key));
783    }
784
785    /**
786     * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
787     * @param key the key forming the tag.
788     * @param values one or many values forming the tag.
789     * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
790     */
791    public boolean hasTag(String key, String... values) {
792        return hasTag(key, Arrays.asList(values));
793    }
794
795    /**
796     * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
797     * @param key the key forming the tag.
798     * @param values one or many values forming the tag.
799     * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
800     */
801    public boolean hasTag(String key, Collection<String> values) {
802        return values.contains(get(key));
803    }
804
805    /**
806     * Tests whether this primitive contains a tag consisting of {@code key} and a value different from {@code value}.
807     * @param key the key forming the tag.
808     * @param value value not forming the tag.
809     * @return true if primitive contains a tag consisting of {@code key} and a value different from {@code value}.
810     * @since 11608
811     */
812    public boolean hasTagDifferent(String key, String value) {
813        String v = get(key);
814        return v != null && !v.equals(value);
815    }
816
817    /**
818     * Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
819     * @param key the key forming the tag.
820     * @param values one or many values forming the tag.
821     * @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
822     * @since 11608
823     */
824    public boolean hasTagDifferent(String key, String... values) {
825        return hasTagDifferent(key, Arrays.asList(values));
826    }
827
828    /**
829     * Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
830     * @param key the key forming the tag.
831     * @param values one or many values forming the tag.
832     * @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
833     * @since 11608
834     */
835    public boolean hasTagDifferent(String key, Collection<String> values) {
836        String v = get(key);
837        return v != null && !values.contains(v);
838    }
839}