source: josm/trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java@ 2583

Last change on this file since 2583 was 2583, checked in by jttt, 14 years ago

Fix #4103 Exception on undo

  • Property svn:eol-style set to native
File size: 36.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
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.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Locale;
17import java.util.Map;
18import java.util.Set;
19import java.util.Map.Entry;
20import java.util.concurrent.atomic.AtomicLong;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.osm.visitor.Visitor;
24import org.openstreetmap.josm.gui.mappaint.ElemStyle;
25
26/**
27 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
28 * and updated within the OSM-Server.
29 *
30 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
31 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
32 * by the server environment and not an extendible data stuff.
33 *
34 * @author imi
35 */
36abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged, PrimitiveId {
37
38 private static final AtomicLong idCounter = new AtomicLong(0);
39
40 static long generateUniqueId() {
41 return idCounter.decrementAndGet();
42 }
43
44 private static class KeysEntry implements Entry<String, String>{
45
46 private final String key;
47 private final String value;
48
49 private KeysEntry(String key, String value) {
50 this.key = key;
51 this.value = value;
52 }
53
54 public String getKey() {
55 return key;
56 }
57
58 public String getValue() {
59 return value;
60 }
61
62 public String setValue(String value) {
63 throw new UnsupportedOperationException();
64 }
65
66 @Override
67 public int hashCode() {
68 final int prime = 31;
69 int result = 1;
70 result = prime * result + ((key == null) ? 0 : key.hashCode());
71 result = prime * result + ((value == null) ? 0 : value.hashCode());
72 return result;
73 }
74
75 @Override
76 public boolean equals(Object obj) {
77 if (this == obj)
78 return true;
79 if (obj == null)
80 return false;
81 if (getClass() != obj.getClass())
82 return false;
83 KeysEntry other = (KeysEntry) obj;
84 if (key == null) {
85 if (other.key != null)
86 return false;
87 } else if (!key.equals(other.key))
88 return false;
89 if (value == null) {
90 if (other.value != null)
91 return false;
92 } else if (!value.equals(other.value))
93 return false;
94 return true;
95 }
96 }
97
98 private static final int FLAG_MODIFIED = 1 << 0;
99 private static final int FLAG_VISIBLE = 1 << 1;
100 private static final int FLAG_DISABLED = 1 << 2;
101 private static final int FLAG_DELETED = 1 << 3;
102 private static final int FLAG_FILTERED = 1 << 4;
103 private static final int FLAG_HAS_DIRECTIONS = 1 << 5;
104 private static final int FLAG_TAGGED = 1 << 6;
105
106 /**
107 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
108 * another collection of {@see OsmPrimitive}s. The result collection is a list.
109 *
110 * If <code>list</code> is null, replies an empty list.
111 *
112 * @param <T>
113 * @param list the original list
114 * @param type the type to filter for
115 * @return the sub-list of OSM primitives of type <code>type</code>
116 */
117 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
118 if (list == null) return Collections.emptyList();
119 List<T> ret = new LinkedList<T>();
120 for(OsmPrimitive p: list) {
121 if (type.isInstance(p)) {
122 ret.add(type.cast(p));
123 }
124 }
125 return ret;
126 }
127
128 /**
129 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
130 * another collection of {@see OsmPrimitive}s. The result collection is a set.
131 *
132 * If <code>list</code> is null, replies an empty set.
133 *
134 * @param <T>
135 * @param list the original collection
136 * @param type the type to filter for
137 * @return the sub-set of OSM primitives of type <code>type</code>
138 */
139 static public <T extends OsmPrimitive> LinkedHashSet<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
140 LinkedHashSet<T> ret = new LinkedHashSet<T>();
141 if (set != null) {
142 for(OsmPrimitive p: set) {
143 if (type.isInstance(p)) {
144 ret.add(type.cast(p));
145 }
146 }
147 }
148 return ret;
149 }
150
151 /**
152 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
153 *
154 * @param primitives the collection of primitives.
155 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
156 * empty set if primitives is null or if there are no referring primitives
157 */
158 static public Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
159 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
160 if (primitives == null || primitives.isEmpty()) return ret;
161 for (OsmPrimitive p: primitives) {
162 ret.addAll(p.getReferrers());
163 }
164 return ret;
165 }
166
167
168 /* mappaint data */
169 public ElemStyle mappaintStyle = null;
170 public Integer mappaintDrawnCode = 0;
171
172 /* This should not be called from outside. Fixing the UI to add relevant
173 get/set functions calling this implicitely is preferred, so we can have
174 transparent cache handling in the future. */
175 protected void clearCached()
176 {
177 mappaintDrawnCode = 0;
178 mappaintStyle = null;
179 }
180 /* end of mappaint data */
181
182 /**
183 * Unique identifier in OSM. This is used to identify objects on the server.
184 * An id of 0 means an unknown id. The object has not been uploaded yet to
185 * know what id it will get.
186 *
187 */
188 private long id = 0;
189
190 /** the parent dataset */
191 private DataSet dataSet;
192
193 /**
194 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
195 * @param dataSet
196 */
197 void setDataset(DataSet dataSet) {
198 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
199 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
200 this.dataSet = dataSet;
201 }
202
203 /**
204 *
205 * @return DataSet this primitive is part of.
206 */
207 public DataSet getDataSet() {
208 return dataSet;
209 }
210
211 /**
212 * Throws exception if primitive is not part of the dataset
213 */
214 public void checkDataset() {
215 if (dataSet == null)
216 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
217 }
218
219 private volatile byte flags = FLAG_VISIBLE; // visible per default
220
221 /**
222 * User that last modified this primitive, as specified by the server.
223 * Never changed by JOSM.
224 */
225 private User user = null;
226
227 /**
228 * If set to true, this object is incomplete, which means only the id
229 * and type is known (type is the objects instance class)
230 */
231 private boolean incomplete = false;
232
233 /**
234 * Contains the version number as returned by the API. Needed to
235 * ensure update consistency
236 */
237 private int version = 0;
238
239 /**
240 * Creates a new primitive for the given id. If the id > 0, the primitive is marked
241 * as incomplete.
242 *
243 * @param id the id. > 0 required
244 * @param allowNegativeId Allows to set negative id. For internal use
245 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
246 */
247 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
248 if (allowNegativeId) {
249 this.id = id;
250 } else {
251 if (id < 0)
252 throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
253 else if (id == 0) {
254 this.id = generateUniqueId();
255 } else {
256 this.id = id;
257 }
258
259 }
260 this.version = 0;
261 this.setIncomplete(id > 0);
262 }
263
264 protected OsmPrimitive(PrimitiveData data) {
265 version = data.getVersion();
266 id = data.getId();
267 }
268
269 /* ------------------------------------------------------------------------------------ */
270 /* accessors */
271 /* ------------------------------------------------------------------------------------ */
272 /**
273 * Sets whether this primitive is disabled or not.
274 *
275 * @param disabled true, if this primitive is disabled; false, otherwise
276 */
277 void setDisabled(boolean disabled) {
278 if (disabled) {
279 flags |= FLAG_DISABLED;
280 } else {
281 flags &= ~FLAG_DISABLED;
282 }
283
284 }
285
286 /**
287 * Replies true, if this primitive is disabled.
288 *
289 * @return true, if this primitive is disabled
290 */
291 public boolean isDisabled() {
292 return (flags & FLAG_DISABLED) != 0;
293 }
294 /**
295 * Sets whether this primitive is filtered out or not.
296 *
297 * @param filtered true, if this primitive is filtered out; false, otherwise
298 */
299 public void setFiltered(boolean filtered) {
300 if (filtered) {
301 flags |= FLAG_FILTERED;
302 } else {
303 flags &= ~FLAG_FILTERED;
304 }
305 }
306 /**
307 * Replies true, if this primitive is filtered out.
308 *
309 * @return true, if this primitive is filtered out
310 */
311 public boolean isFiltered() {
312 return (flags & FLAG_FILTERED) != 0;
313 }
314
315 /**
316 * Marks this primitive as being modified.
317 *
318 * @param modified true, if this primitive is to be modified
319 */
320 public void setModified(boolean modified) {
321 if (modified) {
322 flags |= FLAG_MODIFIED;
323 } else {
324 flags &= ~FLAG_MODIFIED;
325 }
326 }
327
328 /**
329 * Replies <code>true</code> if the object has been modified since it was loaded from
330 * the server. In this case, on next upload, this object will be updated.
331 *
332 * Deleted objects are deleted from the server. If the objects are added (id=0),
333 * the modified is ignored and the object is added to the server.
334 *
335 * @return <code>true</code> if the object has been modified since it was loaded from
336 * the server
337 */
338 public boolean isModified() {
339 return (flags & FLAG_MODIFIED) != 0;
340 }
341
342 /**
343 * Replies <code>true</code>, if the object has been deleted.
344 *
345 * @return <code>true</code>, if the object has been deleted.
346 * @see #setDeleted(boolean)
347 */
348 public boolean isDeleted() {
349 return (flags & FLAG_DELETED) != 0;
350 }
351
352 /**
353 * Replies <code>true</code>, if the object is usable.
354 *
355 * @return <code>true</code>, if the object is unusable.
356 * @see #delete(boolean)
357 */
358 public boolean isUsable() {
359 return !isDeleted() && !isIncomplete() && !isDisabled();
360 }
361
362 /**
363 * Replies true if this primitive is either unknown to the server (i.e. its id
364 * is 0) or it is known to the server and it hasn't be deleted on the server.
365 * Replies false, if this primitive is known on the server and has been deleted
366 * on the server.
367 *
368 * @see #setVisible(boolean)
369 */
370 public boolean isVisible() {
371 return (flags & FLAG_VISIBLE) != 0;
372 }
373
374 /**
375 * Sets whether this primitive is visible, i.e. whether it is known on the server
376 * and not deleted on the server.
377 *
378 * @see #isVisible()
379 * @throws IllegalStateException thrown if visible is set to false on an primitive with
380 * id==0
381 */
382 public void setVisible(boolean visible) throws IllegalStateException{
383 if (isNew() && visible == false)
384 throw new IllegalStateException(tr("A primitive with ID = 0 can't be invisible."));
385 if (visible) {
386 flags |= FLAG_VISIBLE;
387 } else {
388 flags &= ~FLAG_VISIBLE;
389 }
390 }
391
392 /**
393 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
394 * if this primitive is incomplete.
395 *
396 * @see #setVersion(int)
397 */
398 public long getVersion() {
399 return version;
400 }
401
402 /**
403 * Replies the id of this primitive.
404 *
405 * @return the id of this primitive.
406 */
407 public long getId() {
408 return id >= 0?id:0;
409 }
410
411 /**
412 *
413 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
414 */
415 public long getUniqueId() {
416 return id;
417 }
418
419 /**
420 *
421 * @return True if primitive is new (not yet uploaded the server, id <= 0)
422 */
423 public boolean isNew() {
424 return id <= 0;
425 }
426
427 /**
428 * Sets the id and the version of this primitive if it is known to the OSM API.
429 *
430 * Since we know the id and its version it can't be incomplete anymore. incomplete
431 * is set to false.
432 *
433 * @param id the id. > 0 required
434 * @param version the version > 0 required
435 * @throws IllegalArgumentException thrown if id <= 0
436 * @throws IllegalArgumentException thrown if version <= 0
437 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
438 */
439 public void setOsmId(long id, int version) {
440 if (id <= 0)
441 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
442 if (version <= 0)
443 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
444 if (dataSet != null && id != this.id) {
445 DataSet datasetCopy = dataSet;
446 // Reindex primitive
447 datasetCopy.removePrimitive(this);
448 this.id = id;
449 datasetCopy.addPrimitive(this);
450 }
451 this.id = id;
452 this.version = version;
453 this.setIncomplete(false);
454 }
455
456 /**
457 * Clears the id and version known to the OSM API. The id and the version is set to 0.
458 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
459 * of calling this method.
460 *
461 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
462 *
463 * @throws DataIntegrityProblemException If primitive was already added to the dataset
464 */
465 public void clearOsmId() {
466 if (dataSet != null)
467 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
468 this.id = generateUniqueId();
469 this.version = 0;
470 this.setIncomplete(false);
471 }
472
473 public void setTimestamp(Date timestamp) {
474 this.timestamp = (int)(timestamp.getTime() / 1000);
475 }
476
477 /**
478 * Time of last modification to this object. This is not set by JOSM but
479 * read from the server and delivered back to the server unmodified. It is
480 * used to check against edit conflicts.
481 *
482 */
483 public Date getTimestamp() {
484 return new Date(timestamp * 1000l);
485 }
486
487 public boolean isTimestampEmpty() {
488 return timestamp == 0;
489 }
490
491 /**
492 * If set to true, this object is highlighted. Currently this is only used to
493 * show which ways/nodes will connect
494 */
495 public volatile boolean highlighted = false;
496
497 private int timestamp;
498
499 private static Collection<String> uninteresting = null;
500 /**
501 * Contains a list of "uninteresting" keys that do not make an object
502 * "tagged".
503 * Initialized by checkTagged()
504 */
505 public static Collection<String> getUninterestingKeys() {
506 if (uninteresting == null) {
507 uninteresting = Main.pref.getCollection("tags.uninteresting",
508 Arrays.asList(new String[]{"source","note","comment","converted_by","created_by"}));
509 }
510 return uninteresting;
511 }
512
513 private static Collection<String> directionKeys = null;
514
515 /**
516 * Contains a list of direction-dependent keys that make an object
517 * direction dependent.
518 * Initialized by checkDirectionTagged()
519 */
520 public static Collection<String> getDirectionKeys() {
521 if(directionKeys == null) {
522 directionKeys = Main.pref.getCollection("tags.direction",
523 Arrays.asList("oneway","incline","incline_steep","aerialway"));
524 }
525 return directionKeys;
526 }
527
528 /**
529 * Implementation of the visitor scheme. Subclasses have to call the correct
530 * visitor function.
531 * @param visitor The visitor from which the visit() function must be called.
532 */
533 abstract public void visit(Visitor visitor);
534
535 /**
536 * Sets whether this primitive is deleted or not.
537 *
538 * Also marks this primitive as modified if deleted is true.
539 *
540 * @param deleted true, if this primitive is deleted; false, otherwise
541 */
542 public void setDeleted(boolean deleted) {
543 if (deleted) {
544 flags |= FLAG_DELETED;
545 } else {
546 flags &= ~FLAG_DELETED;
547 }
548 setModified(deleted);
549 if (dataSet != null) {
550 if (deleted) {
551 dataSet.firePrimitivesRemoved(Collections.singleton(this));
552 } else {
553 dataSet.firePrimitivesAdded(Collections.singleton(this));
554 }
555 }
556 }
557
558 /**
559 * Replies the user who has last touched this object. May be null.
560 *
561 * @return the user who has last touched this object. May be null.
562 */
563 public User getUser() {
564 return user;
565 }
566
567 /**
568 * Sets the user who has last touched this object.
569 *
570 * @param user the user
571 */
572 public void setUser(User user) {
573 this.user = user;
574 }
575
576 /**
577 * Equal, if the id (and class) is equal.
578 *
579 * An primitive is equal to its incomplete counter part.
580 */
581 @Override public boolean equals(Object obj) {
582 if (obj instanceof OsmPrimitive)
583 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
584 return false;
585 }
586
587 /**
588 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
589 *
590 * An primitive has the same hashcode as its incomplete counterpart.
591 */
592 @Override public final int hashCode() {
593 final int[] ret = new int[1];
594 Visitor v = new Visitor(){
595 public void visit(Node n) { ret[0] = 0; }
596 public void visit(Way w) { ret[0] = 1; }
597 public void visit(Relation e) { ret[0] = 2; }
598 public void visit(Changeset cs) { ret[0] = 3; }
599 };
600 visit(v);
601 return (int)(id<<2)+ret[0];
602 }
603
604 /*------------
605 * Keys handling
606 ------------*/
607
608 /**
609 * The key/value list for this primitive.
610 *
611 */
612 private String[] keys;
613
614 /**
615 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
616 *
617 * @return tags of this primitive. Changes made in returned map are not mapped
618 * back to the primitive, use setKeys() to modify the keys
619 * @since 1924
620 */
621 public Map<String, String> getKeys() {
622 Map<String, String> result = new HashMap<String, String>();
623 if (keys != null) {
624 for (int i=0; i<keys.length ; i+=2) {
625 result.put(keys[i], keys[i + 1]);
626 }
627 }
628 return result;
629 }
630
631 /**
632 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
633 * If <code>keys</code> is null removes all existing key/value pairs.
634 *
635 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
636 * @since 1924
637 */
638 public void setKeys(Map<String, String> keys) {
639 if (keys == null) {
640 this.keys = null;
641 return;
642 }
643 String[] newKeys = new String[keys.size() * 2];
644 int index = 0;
645 for (Entry<String, String> entry:keys.entrySet()) {
646 newKeys[index++] = entry.getKey();
647 newKeys[index++] = entry.getValue();
648 }
649 this.keys = newKeys;
650 keysChangedImpl();
651 }
652
653 /**
654 * Set the given value to the given key. If key is null, does nothing. If value is null,
655 * removes the key and behaves like {@see #remove(String)}.
656 *
657 * @param key The key, for which the value is to be set. Can be null, does nothing in this case.
658 * @param value The value for the key. If null, removes the respective key/value pair.
659 *
660 * @see #remove(String)
661 */
662 public final void put(String key, String value) {
663 if (key == null)
664 return;
665 else if (value == null) {
666 remove(key);
667 } else if (keys == null || keys.length == 0){
668 keys = new String[] {key, value};
669 keysChangedImpl();
670 } else {
671 for (int i=0; i<keys.length;i+=2) {
672 if (keys[i].equals(key)) {
673 keys[i+1] = value;
674 keysChangedImpl();
675 return;
676 }
677 }
678 String[] newKeys = new String[keys.length + 2];
679 for (int i=0; i< keys.length;i+=2) {
680 newKeys[i] = keys[i];
681 newKeys[i+1] = keys[i+1];
682 }
683 newKeys[keys.length] = key;
684 newKeys[keys.length + 1] = value;
685 keys = newKeys;
686 keysChangedImpl();
687 }
688 }
689 /**
690 * Remove the given key from the list
691 *
692 * @param key the key to be removed. Ignored, if key is null.
693 */
694 public final void remove(String key) {
695 if (key == null || keys == null || keys.length == 0 ) return;
696 if (!hasKey(key))
697 return;
698 if (keys.length == 2) {
699 keys = null;
700 keysChangedImpl();
701 return;
702 }
703 String[] newKeys = new String[keys.length - 2];
704 int j=0;
705 for (int i=0; i < keys.length; i+=2) {
706 if (!keys[i].equals(key)) {
707 newKeys[j++] = keys[i];
708 newKeys[j++] = keys[i+1];
709 }
710 }
711 keys = newKeys;
712 keysChangedImpl();
713 }
714
715 /**
716 * Removes all keys from this primitive.
717 *
718 * @since 1843
719 */
720 public final void removeAll() {
721 keys = null;
722 keysChangedImpl();
723 }
724
725 /**
726 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
727 * Replies null, if there is no value for the given key.
728 *
729 * @param key the key. Can be null, replies null in this case.
730 * @return the value for key <code>key</code>.
731 */
732 public final String get(String key) {
733 if (key == null)
734 return null;
735 if (keys == null || keys.length == 0)
736 return null;
737 for (int i=0; i<keys.length;i+=2) {
738 if (keys[i].equals(key)) return keys[i+1];
739 }
740 return null;
741 }
742
743 // FIXME: why replying a collection of entries? Should be replaced by
744 // a method Map<String,String> getTags()
745 //
746 public final Collection<Entry<String, String>> entrySet() {
747 if (keys == null || keys.length ==0)
748 return Collections.emptySet();
749 Set<Entry<String, String>> result = new HashSet<Entry<String,String>>();
750 for (int i=0; i<keys.length; i+=2) {
751 result.add(new KeysEntry(keys[i], keys[i+1]));
752 }
753 return result;
754 }
755
756 public final Collection<String> keySet() {
757 if (keys == null || keys.length == 0)
758 return Collections.emptySet();
759 Set<String> result = new HashSet<String>(keys.length / 2);
760 for (int i=0; i<keys.length; i+=2) {
761 result.add(keys[i]);
762 }
763 return result;
764 }
765
766 /**
767 * Replies true, if the map of key/value pairs of this primitive is not empty.
768 *
769 * @return true, if the map of key/value pairs of this primitive is not empty; false
770 * otherwise
771 */
772 public final boolean hasKeys() {
773 return keys != null && keys.length != 0;
774 }
775
776 private void keysChangedImpl() {
777 clearCached();
778 updateHasDirectionKeys();
779 updateTagged();
780 if (dataSet != null) {
781 dataSet.fireTagsChanged(this);
782 }
783 }
784
785 /**
786 * Replies true if this primitive has a tag with key <code>key</code>
787 *
788 * @param key the key
789 * @return true, if his primitive has a tag with key <code>key</code>
790 */
791 public boolean hasKey(String key) {
792 if (key == null) return false;
793 if (keys == null) return false;
794 for (int i=0; i< keys.length;i+=2) {
795 if (keys[i].equals(key)) return true;
796 }
797 return false;
798 }
799
800 /**
801 * Replies true if other isn't null and has the same tags (key/value-pairs) as this.
802 *
803 * @param other the other object primitive
804 * @return true if other isn't null and has the same tags (key/value-pairs) as this.
805 */
806 public boolean hasSameTags(OsmPrimitive other) {
807 return entrySet().equals(other.entrySet());
808 }
809
810 /*------------
811 * Referrers
812 ------------*/
813
814 private Object referrers;
815
816 /**
817 * Add new referrer. If referrer is already included then no action is taken
818 * @param referrer
819 */
820 protected void addReferrer(OsmPrimitive referrer) {
821 // Based on methods from josm-ng
822 if (referrers == null) {
823 referrers = referrer;
824 } else if (referrers instanceof OsmPrimitive) {
825 if (referrers != referrer) {
826 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
827 }
828 } else {
829 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
830 if (primitive == referrer)
831 return;
832 }
833 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
834 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
835 System.arraycopy(orig, 0, bigger, 0, orig.length);
836 bigger[orig.length] = referrer;
837 referrers = bigger;
838 }
839 }
840
841 /**
842 * Remove referrer. No action is taken if referrer is not registered
843 * @param referrer
844 */
845 protected void removeReferrer(OsmPrimitive referrer) {
846 // Based on methods from josm-ng
847 if (referrers instanceof OsmPrimitive) {
848 if (referrers == referrer) {
849 referrers = null;
850 }
851 } else if (referrers instanceof OsmPrimitive[]) {
852 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
853 int idx = -1;
854 for (int i=0; i<orig.length; i++) {
855 if (orig[i] == referrer) {
856 idx = i;
857 break;
858 }
859 }
860 if (idx == -1)
861 return;
862
863 if (orig.length == 2) {
864 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
865 } else { // downsize the array
866 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
867 System.arraycopy(orig, 0, smaller, 0, idx);
868 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
869 referrers = smaller;
870 }
871 }
872 }
873 /**
874 * Find primitives that reference this primitive. Returns only primitives that are included in the same
875 * dataset as this primitive. <br>
876 *
877 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
878 * not return wnew because it's not part of the dataset <br>
879 *
880 * <code>Way wnew = new Way(existingWay)</code>
881 *
882 * @return a collection of all primitives that reference this primitive.
883 */
884
885 public final List<OsmPrimitive> getReferrers() {
886 checkDataset();
887 // Method copied from OsmPrimitive in josm-ng
888 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
889 // when way is cloned
890 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
891 if (referrers != null) {
892 if (referrers instanceof OsmPrimitive) {
893 OsmPrimitive ref = (OsmPrimitive)referrers;
894 if (ref.dataSet == dataSet) {
895 result.add(ref);
896 }
897 } else {
898 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
899 if (dataSet == o.dataSet) {
900 result.add(o);
901 }
902 }
903 }
904 }
905
906 return result;
907 }
908
909 /**
910 * Get and write all attributes from the parameter. Does not fire any listener, so
911 * use this only in the data initializing phase
912 */
913 public void cloneFrom(OsmPrimitive osm) {
914 setKeys(osm.getKeys());
915 id = osm.id;
916 timestamp = osm.timestamp;
917 version = osm.version;
918 setIncomplete(osm.isIncomplete());
919 flags = osm.flags;
920 user= osm.user;
921 clearCached();
922 }
923
924 /**
925 * Merges the technical and semantical attributes from <code>other</code> onto this.
926 *
927 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
928 * have an assigend OSM id, the IDs have to be the same.
929 *
930 * @param other the other primitive. Must not be null.
931 * @throws IllegalArgumentException thrown if other is null.
932 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
933 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
934 */
935 public void mergeFrom(OsmPrimitive other) {
936 if (other == null)
937 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "other"));
938 if (other.isNew() ^ isNew())
939 throw new DataIntegrityProblemException(tr("Can''t merge because either of the participating primitives is new and the other is not"));
940 if (! other.isNew() && other.getId() != id)
941 throw new DataIntegrityProblemException(tr("Can''t merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
942 setKeys(other.getKeys());
943 timestamp = other.timestamp;
944 version = other.version;
945 setIncomplete(other.isIncomplete());
946 flags = other.flags;
947 user= other.user;
948 }
949
950 /**
951 * Replies true if this primitive and other are equal with respect to their
952 * semantic attributes.
953 * <ol>
954 * <li>equal id</ol>
955 * <li>both are complete or both are incomplete</li>
956 * <li>both have the same tags</li>
957 * </ol>
958 * @param other
959 * @return true if this primitive and other are equal with respect to their
960 * semantic attributes.
961 */
962 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
963 if (!isNew() && id != other.id)
964 return false;
965 if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
966 return false;
967 // can't do an equals check on the internal keys array because it is not ordered
968 //
969 return hasSameTags(other);
970 }
971
972 /**
973 * Replies true if this primitive and other are equal with respect to their
974 * technical attributes. The attributes:
975 * <ol>
976 * <li>deleted</ol>
977 * <li>modified</ol>
978 * <li>timestamp</ol>
979 * <li>version</ol>
980 * <li>visible</ol>
981 * <li>user</ol>
982 * </ol>
983 * have to be equal
984 * @param other the other primitive
985 * @return true if this primitive and other are equal with respect to their
986 * technical attributes
987 */
988 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
989 if (other == null) return false;
990
991 return
992 isDeleted() == other.isDeleted()
993 && isModified() == other.isModified()
994 && timestamp == other.timestamp
995 && version == other.version
996 && isVisible() == other.isVisible()
997 && (user == null ? other.user==null : user==other.user);
998 }
999
1000 private void updateTagged() {
1001 getUninterestingKeys();
1002 if (keys != null) {
1003 for (Entry<String,String> e : getKeys().entrySet()) {
1004 if (!uninteresting.contains(e.getKey())) {
1005 flags |= FLAG_TAGGED;
1006 return;
1007 }
1008 }
1009 }
1010 flags &= ~FLAG_TAGGED;
1011 }
1012
1013 /**
1014 * true if this object is considered "tagged". To be "tagged", an object
1015 * must have one or more "interesting" tags. "created_by" and "source"
1016 * are typically considered "uninteresting" and do not make an object
1017 * "tagged".
1018 */
1019 public boolean isTagged() {
1020 return (flags & FLAG_TAGGED) != 0;
1021 }
1022
1023 private void updateHasDirectionKeys() {
1024 getDirectionKeys();
1025 if (keys != null) {
1026 for (Entry<String,String> e : getKeys().entrySet()) {
1027 if (directionKeys.contains(e.getKey())) {
1028 flags |= FLAG_HAS_DIRECTIONS;
1029 return;
1030 }
1031 }
1032 }
1033 flags &= ~FLAG_HAS_DIRECTIONS;
1034 }
1035 /**
1036 * true if this object has direction dependent tags (e.g. oneway)
1037 */
1038 public boolean hasDirectionKeys() {
1039 return (flags & FLAG_HAS_DIRECTIONS) != 0;
1040 }
1041
1042 /**
1043 * Replies the name of this primitive. The default implementation replies the value
1044 * of the tag <tt>name</tt> or null, if this tag is not present.
1045 *
1046 * @return the name of this primitive
1047 */
1048 public String getName() {
1049 if (get("name") != null)
1050 return get("name");
1051 return null;
1052 }
1053
1054 /**
1055 * Replies the a localized name for this primitive given by the value of the tags (in this order)
1056 * <ul>
1057 * <li>name:lang_COUNTRY_Variant of the current locale</li>
1058 * <li>name:lang_COUNTRY of the current locale</li>
1059 * <li>name:lang of the current locale</li>
1060 * <li>name of the current locale</li>
1061 * </ul>
1062 *
1063 * null, if no such tag exists
1064 *
1065 * @return the name of this primitive
1066 */
1067 public String getLocalName() {
1068 String key = "name:" + Locale.getDefault().toString();
1069 if (get(key) != null)
1070 return get(key);
1071 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
1072 if (get(key) != null)
1073 return get(key);
1074 key = "name:" + Locale.getDefault().getLanguage();
1075 if (get(key) != null)
1076 return get(key);
1077 return getName();
1078 }
1079
1080 /**
1081 * Replies the display name of a primitive formatted by <code>formatter</code>
1082 *
1083 * @return the display name
1084 */
1085 public abstract String getDisplayName(NameFormatter formatter);
1086
1087 /**
1088 * Loads (clone) this primitive from provided PrimitiveData
1089 * @param data
1090 */
1091 public void load(PrimitiveData data) {
1092 setKeys(data.getKeys());
1093 timestamp = data.getTimestamp();
1094 user = data.getUser();
1095 setDeleted(data.isDeleted());
1096 setModified(data.isModified());
1097 setVisible(data.isVisible());
1098 }
1099
1100 /**
1101 * Save parameters of this primitive to the transport object
1102 * @return
1103 */
1104 public abstract PrimitiveData save();
1105
1106 protected void saveCommonAttributes(PrimitiveData data) {
1107 data.setId(id);
1108 data.getKeys().clear();
1109 data.getKeys().putAll(getKeys());
1110 data.setTimestamp(timestamp);
1111 data.setUser(user);
1112 data.setDeleted(isDeleted());
1113 data.setModified(isModified());
1114 data.setVisible(isVisible());
1115 }
1116
1117 protected String getFlagsAsString() {
1118
1119 StringBuilder builder = new StringBuilder();
1120
1121 if (isModified()) {
1122 builder.append("M");
1123 }
1124 if (isVisible()) {
1125 builder.append("V");
1126 }
1127 if (isDeleted()) {
1128 builder.append("D");
1129 }
1130 if (isFiltered()) {
1131 builder.append("f");
1132 }
1133
1134 if (isDeleted()) {
1135 builder.append("d");
1136 }
1137
1138 return builder.toString();
1139 }
1140
1141 public abstract BBox getBBox();
1142
1143 /**
1144 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1145 */
1146 public abstract void updatePosition();
1147
1148 /**
1149 * Replies the unique primitive id for this primitive
1150 *
1151 * @return the unique primitive id for this primitive
1152 */
1153 public PrimitiveId getPrimitiveId() {
1154 return new SimplePrimitiveId(getUniqueId(), getType());
1155 }
1156
1157 public void setIncomplete(boolean incomplete) {
1158 this.incomplete = incomplete;
1159 }
1160
1161 public boolean isIncomplete() {
1162 return incomplete;
1163 }
1164}
Note: See TracBrowser for help on using the repository browser.