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

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

Do not call tagsChanged listeners that often, update RelationListDialog only when relation is changed. Should help a bit to #3974

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