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