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