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

Last change on this file since 4682 was 4682, checked in by simon04, 12 years ago

fix #2746 - add validation: Way connected to Area

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