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

Last change on this file since 7501 was 7237, checked in by bastiK, 10 years ago

fixed #10130 - add mapcss expression to get number of tags

new mapcss expression to print debugging output to the console

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