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.Set;
015import java.util.concurrent.TimeUnit;
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    private static final AtomicLong idCounter = new AtomicLong(0);
029
030    /**
031     * Generates a new primitive unique id.
032     * @return new primitive unique (negative) id
033     */
034    static long generateUniqueId() {
035        return idCounter.decrementAndGet();
036    }
037
038    /**
039     * Returns the current primitive unique id.
040     * @return the current primitive unique (negative) id (last generated)
041     * @since 12536
042     */
043    public static long currentUniqueId() {
044        return idCounter.get();
045    }
046
047    /**
048     * Advances the current primitive unique id to skip a range of values.
049     * @param newId new unique id
050     * @throws IllegalArgumentException if newId is greater than current unique id
051     * @since 12536
052     */
053    public static void advanceUniqueId(long newId) {
054        if (newId > currentUniqueId()) {
055            throw new IllegalArgumentException("Cannot modify the id counter backwards");
056        }
057        idCounter.set(newId);
058    }
059
060    /**
061     * This flag shows, that the properties have been changed by the user
062     * and on upload the object will be send to the server.
063     */
064    protected static final short FLAG_MODIFIED = 1 << 0;
065
066    /**
067     * This flag is false, if the object is marked
068     * as deleted on the server.
069     */
070    protected static final short FLAG_VISIBLE = 1 << 1;
071
072    /**
073     * An object that was deleted by the user.
074     * Deleted objects are usually hidden on the map and a request
075     * for deletion will be send to the server on upload.
076     * An object usually cannot be deleted if it has non-deleted
077     * objects still referring to it.
078     */
079    protected static final short FLAG_DELETED = 1 << 2;
080
081    /**
082     * A primitive is incomplete if we know its id and type, but nothing more.
083     * Typically some members of a relation are incomplete until they are
084     * fetched from the server.
085     */
086    protected static final short FLAG_INCOMPLETE = 1 << 3;
087
088    /**
089     * An object can be disabled by the filter mechanism.
090     * Then it will show in a shade of gray on the map or it is completely
091     * hidden from the view.
092     * Disabled objects usually cannot be selected or modified
093     * while the filter is active.
094     */
095    protected static final short FLAG_DISABLED = 1 << 4;
096
097    /**
098     * This flag is only relevant if an object is disabled by the
099     * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
100     * Then it indicates, whether it is completely hidden or
101     * just shown in gray color.
102     *
103     * When the primitive is not disabled, this flag should be
104     * unset as well (for efficient access).
105     */
106    protected static final short FLAG_HIDE_IF_DISABLED = 1 << 5;
107
108    /**
109     * Flag used internally by the filter mechanism.
110     */
111    protected static final short FLAG_DISABLED_TYPE = 1 << 6;
112
113    /**
114     * Flag used internally by the filter mechanism.
115     */
116    protected static final short FLAG_HIDDEN_TYPE = 1 << 7;
117
118    /**
119     * This flag is set if the primitive is a way and
120     * according to the tags, the direction of the way is important.
121     * (e.g. one way street.)
122     */
123    protected static final short FLAG_HAS_DIRECTIONS = 1 << 8;
124
125    /**
126     * If the primitive is tagged.
127     * Some trivial tags like source=* are ignored here.
128     */
129    protected static final short FLAG_TAGGED = 1 << 9;
130
131    /**
132     * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
133     * It shows, that direction of the arrows should be reversed.
134     * (E.g. oneway=-1.)
135     */
136    protected static final short FLAG_DIRECTION_REVERSED = 1 << 10;
137
138    /**
139     * When hovering over ways and nodes in add mode, the
140     * "target" objects are visually highlighted. This flag indicates
141     * that the primitive is currently highlighted.
142     */
143    protected static final short FLAG_HIGHLIGHTED = 1 << 11;
144
145    /**
146     * If the primitive is annotated with a tag such as note, fixme, etc.
147     * Match the "work in progress" tags in default map style.
148     */
149    protected static final short FLAG_ANNOTATED = 1 << 12;
150
151    /**
152     * Determines if the primitive is preserved from the filter mechanism.
153     */
154    protected static final short FLAG_PRESERVED = 1 << 13;
155
156    /**
157     * Put several boolean flags to one short int field to save memory.
158     * Other bits of this field are used in subclasses.
159     */
160    protected volatile short flags = FLAG_VISIBLE;   // visible per default
161
162    /*-------------------
163     * OTHER PROPERTIES
164     *-------------------*/
165
166    /**
167     * Unique identifier in OSM. This is used to identify objects on the server.
168     * An id of 0 means an unknown id. The object has not been uploaded yet to
169     * know what id it will get.
170     */
171    protected long id;
172
173    /**
174     * User that last modified this primitive, as specified by the server.
175     * Never changed by JOSM.
176     */
177    protected User user;
178
179    /**
180     * Contains the version number as returned by the API. Needed to
181     * ensure update consistency
182     */
183    protected int version;
184
185    /**
186     * The id of the changeset this primitive was last uploaded to.
187     * 0 if it wasn't uploaded to a changeset yet of if the changeset
188     * id isn't known.
189     */
190    protected int changesetId;
191
192    protected int timestamp;
193
194    /**
195     * Get and write all attributes from the parameter. Does not fire any listener, so
196     * use this only in the data initializing phase
197     * @param other the primitive to clone data from
198     */
199    public void cloneFrom(AbstractPrimitive other) {
200        setKeys(other.getKeys());
201        id = other.id;
202        if (id <= 0) {
203            // reset version and changeset id
204            version = 0;
205            changesetId = 0;
206        }
207        timestamp = other.timestamp;
208        if (id > 0) {
209            version = other.version;
210        }
211        flags = other.flags;
212        user = other.user;
213        if (id > 0 && other.changesetId > 0) {
214            // #4208: sometimes we cloned from other with id < 0 *and*
215            // an assigned changeset id. Don't know why yet. For primitives
216            // with id < 0 we don't propagate the changeset id any more.
217            //
218            setChangesetId(other.changesetId);
219        }
220    }
221
222    @Override
223    public int getVersion() {
224        return version;
225    }
226
227    @Override
228    public long getId() {
229        long id = this.id;
230        return id >= 0 ? id : 0;
231    }
232
233    /**
234     * Gets a unique id representing this object.
235     *
236     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
237     */
238    @Override
239    public long getUniqueId() {
240        return id;
241    }
242
243    /**
244     * Determines if this primitive is new.
245     * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
246     */
247    @Override
248    public boolean isNew() {
249        return id <= 0;
250    }
251
252    @Override
253    public boolean isNewOrUndeleted() {
254        return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
255    }
256
257    @Override
258    public void setOsmId(long id, int version) {
259        if (id <= 0)
260            throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
261        if (version <= 0)
262            throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
263        this.id = id;
264        this.version = version;
265        this.setIncomplete(false);
266    }
267
268    /**
269     * Clears the metadata, including id and version known to the OSM API.
270     * The id is a new unique id. The version, changeset and timestamp are set to 0.
271     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
272     * of calling this method.
273     * @since 6140
274     */
275    public void clearOsmMetadata() {
276        // Not part of dataset - no lock necessary
277        this.id = generateUniqueId();
278        this.version = 0;
279        this.user = null;
280        this.changesetId = 0; // reset changeset id on a new object
281        this.timestamp = 0;
282        this.setIncomplete(false);
283        this.setDeleted(false);
284        this.setVisible(true);
285    }
286
287    @Override
288    public User getUser() {
289        return user;
290    }
291
292    @Override
293    public void setUser(User user) {
294        this.user = user;
295    }
296
297    @Override
298    public int getChangesetId() {
299        return changesetId;
300    }
301
302    @Override
303    public void setChangesetId(int changesetId) {
304        if (this.changesetId == changesetId)
305            return;
306        if (changesetId < 0)
307            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
308        if (changesetId > 0 && isNew())
309            throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
310
311        this.changesetId = changesetId;
312    }
313
314    @Override
315    public PrimitiveId getPrimitiveId() {
316        return new SimplePrimitiveId(getUniqueId(), getType());
317    }
318
319    @Override
320    public void setTimestamp(Date timestamp) {
321        this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
322    }
323
324    @Override
325    public void setRawTimestamp(int timestamp) {
326        this.timestamp = timestamp;
327    }
328
329    @Override
330    public Date getTimestamp() {
331        return new Date(TimeUnit.SECONDS.toMillis(timestamp));
332    }
333
334    @Override
335    public int getRawTimestamp() {
336        return timestamp;
337    }
338
339    @Override
340    public boolean isTimestampEmpty() {
341        return timestamp == 0;
342    }
343
344    /* -------
345    /* FLAGS
346    /* ------*/
347
348    protected void updateFlags(short flag, boolean value) {
349        if (value) {
350            flags |= flag;
351        } else {
352            flags &= (short) ~flag;
353        }
354    }
355
356    @Override
357    public void setModified(boolean modified) {
358        updateFlags(FLAG_MODIFIED, modified);
359    }
360
361    @Override
362    public boolean isModified() {
363        return (flags & FLAG_MODIFIED) != 0;
364    }
365
366    @Override
367    public boolean isDeleted() {
368        return (flags & FLAG_DELETED) != 0;
369    }
370
371    @Override
372    public boolean isUndeleted() {
373        return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
374    }
375
376    @Override
377    public boolean isUsable() {
378        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
379    }
380
381    @Override
382    public boolean isVisible() {
383        return (flags & FLAG_VISIBLE) != 0;
384    }
385
386    @Override
387    public void setVisible(boolean visible) {
388        if (!visible && isNew())
389            throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
390        updateFlags(FLAG_VISIBLE, visible);
391    }
392
393    @Override
394    public void setDeleted(boolean deleted) {
395        updateFlags(FLAG_DELETED, deleted);
396        setModified(deleted ^ !isVisible());
397    }
398
399    /**
400     * If set to true, this object is incomplete, which means only the id
401     * and type is known (type is the objects instance class)
402     * @param incomplete incomplete flag value
403     */
404    protected void setIncomplete(boolean incomplete) {
405        updateFlags(FLAG_INCOMPLETE, incomplete);
406    }
407
408    @Override
409    public boolean isIncomplete() {
410        return (flags & FLAG_INCOMPLETE) != 0;
411    }
412
413    protected String getFlagsAsString() {
414        StringBuilder builder = new StringBuilder();
415
416        if (isIncomplete()) {
417            builder.append('I');
418        }
419        if (isModified()) {
420            builder.append('M');
421        }
422        if (isVisible()) {
423            builder.append('V');
424        }
425        if (isDeleted()) {
426            builder.append('D');
427        }
428        return builder.toString();
429    }
430
431    /*------------
432     * Keys handling
433     ------------*/
434
435    /**
436     * The key/value list for this primitive.
437     * <p>
438     * Note that the keys field is synchronized using RCU.
439     * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
440     * <p>
441     * In short this means that you should not rely on this variable being the same value when read again and your should always
442     * copy it on writes.
443     * <p>
444     * Further reading:
445     * <ul>
446     * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
447     * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
448     *     http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
449     * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
450     *     https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
451     *     {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
452     * </ul>
453     */
454    protected volatile String[] keys;
455
456    /**
457     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
458     *
459     * @return tags of this primitive. Changes made in returned map are not mapped
460     * back to the primitive, use setKeys() to modify the keys
461     * @see #visitKeys(KeyValueVisitor)
462     */
463    @Override
464    public TagMap getKeys() {
465        return new TagMap(keys);
466    }
467
468    @Override
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.isStripEmpty(key))
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    /**
672     * Gets a key ignoring the case of the key
673     * @param key The key to get
674     * @return The value for a key that matches the given key ignoring case.
675     */
676    public final String getIgnoreCase(String key) {
677        String[] keys = this.keys;
678        if (key == null)
679            return null;
680        if (keys == null)
681            return null;
682        for (int i = 0; i < keys.length; i += 2) {
683            if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
684        }
685        return null;
686    }
687
688    @Override
689    public final int getNumKeys() {
690        String[] keys = this.keys;
691        return keys == null ? 0 : keys.length / 2;
692    }
693
694    @Override
695    public final Collection<String> keySet() {
696        final String[] keys = this.keys;
697        if (keys == null) {
698            return Collections.emptySet();
699        }
700        if (keys.length == 1) {
701            return Collections.singleton(keys[0]);
702        }
703
704        final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
705        for (int i = 0; i < keys.length; i += 2) {
706            result.add(keys[i]);
707        }
708        return result;
709    }
710
711    /**
712     * Replies true, if the map of key/value pairs of this primitive is not empty.
713     *
714     * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
715     */
716    @Override
717    public final boolean hasKeys() {
718        return keys != null;
719    }
720
721    /**
722     * Replies true if this primitive has a tag with key <code>key</code>.
723     *
724     * @param key the key
725     * @return true, if this primitive has a tag with key <code>key</code>
726     */
727    @Override
728    public boolean hasKey(String key) {
729        return key != null && indexOfKey(keys, key) >= 0;
730    }
731
732    /**
733     * Replies true if this primitive has a tag any of the <code>keys</code>.
734     *
735     * @param keys the keys
736     * @return true, if this primitive has a tag with any of the <code>keys</code>
737     * @since 11587
738     */
739    public boolean hasKey(String... keys) {
740        return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
741    }
742
743    /**
744     * What to do, when the tags have changed by one of the tag-changing methods.
745     * @param originalKeys original tags
746     */
747    protected abstract void keysChangedImpl(Map<String, String> originalKeys);
748
749    @Override
750    public String getName() {
751        return get("name");
752    }
753
754    @Override
755    public String getLocalName() {
756        for (String s : LanguageInfo.getLanguageCodes(null)) {
757            String val = get("name:" + s);
758            if (val != null)
759                return val;
760        }
761
762        return getName();
763    }
764}