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

Last change on this file since 2482 was 2482, checked in by Gubaer, 14 years ago

Had to clean up key handling methods in OsmPrimtive. Semantics of equals() on keys was broken. Also, fixed some FIXMEs related to efficient implementation. See also unit tests.

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