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

Last change on this file since 8742 was 8742, checked in by simon04, 9 years ago

fix #11834 - KeyValueVisitor: also provide primitive to the visitor

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