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 }