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

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

fix #11767 - Use Arrays.copyOf in OsmPrimitive#put(String, String) (patch by michael2402)

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