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