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

Last change on this file since 8283 was 8283, checked in by stoecker, 9 years ago

some i18n code cleanups

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