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

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

Sonar/FindBugs - various bugfixes / violation fixes

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