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

Last change on this file since 4431 was 4431, checked in by jttt, 13 years ago

Custom primitive name formatters via tagging presets

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