source: josm/trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java @ 5241

Revision 4684, 22.1 KB checked in by Don-vip, 5 months ago (diff)

see #7159 - Layer merging performance

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