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

Last change on this file since 6830 was 6830, checked in by Don-vip, 10 years ago

javadoc fixes for jdk8 compatibility

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