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