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

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

Fire primitivesAdded event after also after primitive was downloaded (incomplete set to false). Remove DataChangeListener from RelationListDialog (should not be necessary anymore, was there only for catching incomplete state changes)

  • Property svn:eol-style set to native
File size: 39.2 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 * The id of the changeset this primitive was last uploaded to.
241 * 0 if it wasn't uploaded to a changeset yet of if the changeset
242 * id isn't known.
243 */
244 private int changesetId;
245
246 /**
247 * Creates a new primitive for the given id.
248 *
249 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
250 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
251 * positive number.
252 *
253 * @param id the id
254 * @param allowNegativeId
255 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
256 */
257 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
258 if (allowNegativeId) {
259 this.id = id;
260 } else {
261 if (id < 0)
262 throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
263 else if (id == 0) {
264 this.id = generateUniqueId();
265 } else {
266 this.id = id;
267 }
268
269 }
270 this.version = 0;
271 this.setIncomplete(id > 0);
272 }
273
274 protected OsmPrimitive(PrimitiveData data) {
275 version = data.getVersion();
276 id = data.getId();
277 }
278
279 /* ------------------------------------------------------------------------------------ */
280 /* accessors */
281 /* ------------------------------------------------------------------------------------ */
282 /**
283 * Sets whether this primitive is disabled or not.
284 *
285 * @param disabled true, if this primitive is disabled; false, otherwise
286 */
287 void setDisabled(boolean disabled) {
288 if (disabled) {
289 flags |= FLAG_DISABLED;
290 } else {
291 flags &= ~FLAG_DISABLED;
292 }
293
294 }
295
296 /**
297 * Replies true, if this primitive is disabled.
298 *
299 * @return true, if this primitive is disabled
300 */
301 public boolean isDisabled() {
302 return (flags & FLAG_DISABLED) != 0;
303 }
304 /**
305 * Sets whether this primitive is filtered out or not.
306 *
307 * @param filtered true, if this primitive is filtered out; false, otherwise
308 */
309 public void setFiltered(boolean filtered) {
310 if (filtered) {
311 flags |= FLAG_FILTERED;
312 } else {
313 flags &= ~FLAG_FILTERED;
314 }
315 }
316 /**
317 * Replies true, if this primitive is filtered out.
318 *
319 * @return true, if this primitive is filtered out
320 */
321 public boolean isFiltered() {
322 return (flags & FLAG_FILTERED) != 0;
323 }
324
325 /**
326 * Marks this primitive as being modified.
327 *
328 * @param modified true, if this primitive is to be modified
329 */
330 public void setModified(boolean modified) {
331 if (modified) {
332 flags |= FLAG_MODIFIED;
333 } else {
334 flags &= ~FLAG_MODIFIED;
335 }
336 }
337
338 /**
339 * Replies <code>true</code> if the object has been modified since it was loaded from
340 * the server. In this case, on next upload, this object will be updated.
341 *
342 * Deleted objects are deleted from the server. If the objects are added (id=0),
343 * the modified is ignored and the object is added to the server.
344 *
345 * @return <code>true</code> if the object has been modified since it was loaded from
346 * the server
347 */
348 public boolean isModified() {
349 return (flags & FLAG_MODIFIED) != 0;
350 }
351
352 /**
353 * Replies <code>true</code>, if the object has been deleted.
354 *
355 * @return <code>true</code>, if the object has been deleted.
356 * @see #setDeleted(boolean)
357 */
358 public boolean isDeleted() {
359 return (flags & FLAG_DELETED) != 0;
360 }
361
362 /**
363 * Replies <code>true</code>, if the object is usable.
364 *
365 * @return <code>true</code>, if the object is unusable.
366 * @see #delete(boolean)
367 */
368 public boolean isUsable() {
369 return !isDeleted() && !isIncomplete() && !isDisabled();
370 }
371
372 public boolean isDrawable()
373 {
374 return !isDeleted() && !isIncomplete() && !isFiltered();
375 }
376
377 /**
378 * Replies true if this primitive is either unknown to the server (i.e. its id
379 * is 0) or it is known to the server and it hasn't be deleted on the server.
380 * Replies false, if this primitive is known on the server and has been deleted
381 * on the server.
382 *
383 * @see #setVisible(boolean)
384 */
385 public boolean isVisible() {
386 return (flags & FLAG_VISIBLE) != 0;
387 }
388
389 /**
390 * Sets whether this primitive is visible, i.e. whether it is known on the server
391 * and not deleted on the server.
392 *
393 * @see #isVisible()
394 * @throws IllegalStateException thrown if visible is set to false on an primitive with
395 * id==0
396 */
397 public void setVisible(boolean visible) throws IllegalStateException{
398 if (isNew() && visible == false)
399 throw new IllegalStateException(tr("A primitive with ID = 0 can't be invisible."));
400 if (visible) {
401 flags |= FLAG_VISIBLE;
402 } else {
403 flags &= ~FLAG_VISIBLE;
404 }
405 }
406
407 /**
408 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
409 * if this primitive is incomplete.
410 *
411 * @see #setVersion(int)
412 */
413 public long getVersion() {
414 return version;
415 }
416
417 /**
418 * Replies the id of this primitive.
419 *
420 * @return the id of this primitive.
421 */
422 public long getId() {
423 return id >= 0?id:0;
424 }
425
426 /**
427 *
428 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
429 */
430 public long getUniqueId() {
431 return id;
432 }
433
434 /**
435 *
436 * @return True if primitive is new (not yet uploaded the server, id <= 0)
437 */
438 public boolean isNew() {
439 return id <= 0;
440 }
441
442 /**
443 * Sets the id and the version of this primitive if it is known to the OSM API.
444 *
445 * Since we know the id and its version it can't be incomplete anymore. incomplete
446 * is set to false.
447 *
448 * @param id the id. > 0 required
449 * @param version the version > 0 required
450 * @throws IllegalArgumentException thrown if id <= 0
451 * @throws IllegalArgumentException thrown if version <= 0
452 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
453 */
454 public void setOsmId(long id, int version) {
455 if (id <= 0)
456 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
457 if (version <= 0)
458 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
459 if (dataSet != null && id != this.id) {
460 DataSet datasetCopy = dataSet;
461 // Reindex primitive
462 datasetCopy.removePrimitive(this);
463 this.id = id;
464 datasetCopy.addPrimitive(this);
465 }
466 this.id = id;
467 this.version = version;
468 this.setIncomplete(false);
469 }
470
471 /**
472 * Clears the id and version known to the OSM API. The id and the version is set to 0.
473 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
474 * of calling this method.
475 *
476 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
477 *
478 * @throws DataIntegrityProblemException If primitive was already added to the dataset
479 */
480 public void clearOsmId() {
481 if (dataSet != null)
482 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
483 this.id = generateUniqueId();
484 this.version = 0;
485 this.setIncomplete(false);
486 }
487
488 public void setTimestamp(Date timestamp) {
489 this.timestamp = (int)(timestamp.getTime() / 1000);
490 }
491
492 /**
493 * Time of last modification to this object. This is not set by JOSM but
494 * read from the server and delivered back to the server unmodified. It is
495 * used to check against edit conflicts.
496 *
497 */
498 public Date getTimestamp() {
499 return new Date(timestamp * 1000l);
500 }
501
502 public boolean isTimestampEmpty() {
503 return timestamp == 0;
504 }
505
506 /**
507 * If set to true, this object is highlighted. Currently this is only used to
508 * show which ways/nodes will connect
509 */
510 public volatile boolean highlighted = false;
511
512 private int timestamp;
513
514 private static Collection<String> uninteresting = null;
515 /**
516 * Contains a list of "uninteresting" keys that do not make an object
517 * "tagged".
518 * Initialized by checkTagged()
519 */
520 public static Collection<String> getUninterestingKeys() {
521 if (uninteresting == null) {
522 uninteresting = Main.pref.getCollection("tags.uninteresting",
523 Arrays.asList(new String[]{"source","note","comment","converted_by","created_by"}));
524 }
525 return uninteresting;
526 }
527
528 private static Collection<String> directionKeys = null;
529
530 /**
531 * Contains a list of direction-dependent keys that make an object
532 * direction dependent.
533 * Initialized by checkDirectionTagged()
534 */
535 public static Collection<String> getDirectionKeys() {
536 if(directionKeys == null) {
537 directionKeys = Main.pref.getCollection("tags.direction",
538 Arrays.asList("oneway","incline","incline_steep","aerialway"));
539 }
540 return directionKeys;
541 }
542
543 /**
544 * Implementation of the visitor scheme. Subclasses have to call the correct
545 * visitor function.
546 * @param visitor The visitor from which the visit() function must be called.
547 */
548 abstract public void visit(Visitor visitor);
549
550 /**
551 * Sets whether this primitive is deleted or not.
552 *
553 * Also marks this primitive as modified if deleted is true.
554 *
555 * @param deleted true, if this primitive is deleted; false, otherwise
556 */
557 public void setDeleted(boolean deleted) {
558 if (deleted) {
559 flags |= FLAG_DELETED;
560 } else {
561 flags &= ~FLAG_DELETED;
562 }
563 setModified(deleted);
564 if (dataSet != null) {
565 if (deleted) {
566 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
567 } else {
568 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
569 }
570 }
571 }
572
573 /**
574 * Replies the user who has last touched this object. May be null.
575 *
576 * @return the user who has last touched this object. May be null.
577 */
578 public User getUser() {
579 return user;
580 }
581
582 /**
583 * Sets the user who has last touched this object.
584 *
585 * @param user the user
586 */
587 public void setUser(User user) {
588 this.user = user;
589 }
590
591
592 /**
593 * Replies the id of the changeset this primitive was last uploaded to.
594 * 0 if this primitive wasn't uploaded to a changeset yet or if the
595 * changeset isn't known.
596 *
597 * @return the id of the changeset this primitive was last uploaded to.
598 */
599 public int getChangesetId() {
600 return changesetId;
601 }
602
603
604 /**
605 * Sets the changeset id of this primitive. Can't be set on a new
606 * primitive.
607 *
608 * @param changesetId the id. >= 0 required.
609 * @throws IllegalStateException thrown if this primitive is new.
610 * @throws IllegalArgumentException thrown if id < 0
611 */
612 public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
613 if (changesetId < 0)
614 throw new IllegalArgumentException(tr("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
615 if (isNew() && changesetId > 0)
616 throw new IllegalStateException(tr("Can''t assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
617 this.changesetId = changesetId;
618 }
619
620 /**
621 * Equal, if the id (and class) is equal.
622 *
623 * An primitive is equal to its incomplete counter part.
624 */
625 @Override public boolean equals(Object obj) {
626 if (obj instanceof OsmPrimitive)
627 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
628 return false;
629 }
630
631 /**
632 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
633 *
634 * An primitive has the same hashcode as its incomplete counterpart.
635 */
636 @Override public final int hashCode() {
637 final int[] ret = new int[1];
638 Visitor v = new Visitor(){
639 public void visit(Node n) { ret[0] = 0; }
640 public void visit(Way w) { ret[0] = 1; }
641 public void visit(Relation e) { ret[0] = 2; }
642 public void visit(Changeset cs) { ret[0] = 3; }
643 };
644 visit(v);
645 return (int)(id<<2)+ret[0];
646 }
647
648 /*------------
649 * Keys handling
650 ------------*/
651
652 /**
653 * The key/value list for this primitive.
654 *
655 */
656 private String[] keys;
657
658 /**
659 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
660 *
661 * @return tags of this primitive. Changes made in returned map are not mapped
662 * back to the primitive, use setKeys() to modify the keys
663 * @since 1924
664 */
665 public Map<String, String> getKeys() {
666 Map<String, String> result = new HashMap<String, String>();
667 if (keys != null) {
668 for (int i=0; i<keys.length ; i+=2) {
669 result.put(keys[i], keys[i + 1]);
670 }
671 }
672 return result;
673 }
674
675 /**
676 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
677 * If <code>keys</code> is null removes all existing key/value pairs.
678 *
679 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
680 * @since 1924
681 */
682 public void setKeys(Map<String, String> keys) {
683 if (keys == null) {
684 this.keys = null;
685 return;
686 }
687 String[] newKeys = new String[keys.size() * 2];
688 int index = 0;
689 for (Entry<String, String> entry:keys.entrySet()) {
690 newKeys[index++] = entry.getKey();
691 newKeys[index++] = entry.getValue();
692 }
693 this.keys = newKeys;
694 keysChangedImpl();
695 }
696
697 /**
698 * Set the given value to the given key. If key is null, does nothing. If value is null,
699 * removes the key and behaves like {@see #remove(String)}.
700 *
701 * @param key The key, for which the value is to be set. Can be null, does nothing in this case.
702 * @param value The value for the key. If null, removes the respective key/value pair.
703 *
704 * @see #remove(String)
705 */
706 public final void put(String key, String value) {
707 if (key == null)
708 return;
709 else if (value == null) {
710 remove(key);
711 } else if (keys == null || keys.length == 0){
712 keys = new String[] {key, value};
713 keysChangedImpl();
714 } else {
715 for (int i=0; i<keys.length;i+=2) {
716 if (keys[i].equals(key)) {
717 keys[i+1] = value;
718 keysChangedImpl();
719 return;
720 }
721 }
722 String[] newKeys = new String[keys.length + 2];
723 for (int i=0; i< keys.length;i+=2) {
724 newKeys[i] = keys[i];
725 newKeys[i+1] = keys[i+1];
726 }
727 newKeys[keys.length] = key;
728 newKeys[keys.length + 1] = value;
729 keys = newKeys;
730 keysChangedImpl();
731 }
732 }
733 /**
734 * Remove the given key from the list
735 *
736 * @param key the key to be removed. Ignored, if key is null.
737 */
738 public final void remove(String key) {
739 if (key == null || keys == null || keys.length == 0 ) return;
740 if (!hasKey(key))
741 return;
742 if (keys.length == 2) {
743 keys = null;
744 keysChangedImpl();
745 return;
746 }
747 String[] newKeys = new String[keys.length - 2];
748 int j=0;
749 for (int i=0; i < keys.length; i+=2) {
750 if (!keys[i].equals(key)) {
751 newKeys[j++] = keys[i];
752 newKeys[j++] = keys[i+1];
753 }
754 }
755 keys = newKeys;
756 keysChangedImpl();
757 }
758
759 /**
760 * Removes all keys from this primitive.
761 *
762 * @since 1843
763 */
764 public final void removeAll() {
765 keys = null;
766 keysChangedImpl();
767 }
768
769 /**
770 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
771 * Replies null, if there is no value for the given key.
772 *
773 * @param key the key. Can be null, replies null in this case.
774 * @return the value for key <code>key</code>.
775 */
776 public final String get(String key) {
777 if (key == null)
778 return null;
779 if (keys == null || keys.length == 0)
780 return null;
781 for (int i=0; i<keys.length;i+=2) {
782 if (keys[i].equals(key)) return keys[i+1];
783 }
784 return null;
785 }
786
787 // FIXME: why replying a collection of entries? Should be replaced by
788 // a method Map<String,String> getTags()
789 //
790 public final Collection<Entry<String, String>> entrySet() {
791 if (keys == null || keys.length ==0)
792 return Collections.emptySet();
793 Set<Entry<String, String>> result = new HashSet<Entry<String,String>>();
794 for (int i=0; i<keys.length; i+=2) {
795 result.add(new KeysEntry(keys[i], keys[i+1]));
796 }
797 return result;
798 }
799
800 public final Collection<String> keySet() {
801 if (keys == null || keys.length == 0)
802 return Collections.emptySet();
803 Set<String> result = new HashSet<String>(keys.length / 2);
804 for (int i=0; i<keys.length; i+=2) {
805 result.add(keys[i]);
806 }
807 return result;
808 }
809
810 /**
811 * Replies true, if the map of key/value pairs of this primitive is not empty.
812 *
813 * @return true, if the map of key/value pairs of this primitive is not empty; false
814 * otherwise
815 */
816 public final boolean hasKeys() {
817 return keys != null && keys.length != 0;
818 }
819
820 private void keysChangedImpl() {
821 clearCached();
822 updateHasDirectionKeys();
823 updateTagged();
824 if (dataSet != null) {
825 dataSet.fireTagsChanged(this);
826 }
827 }
828
829 /**
830 * Replies true if this primitive has a tag with key <code>key</code>
831 *
832 * @param key the key
833 * @return true, if his primitive has a tag with key <code>key</code>
834 */
835 public boolean hasKey(String key) {
836 if (key == null) return false;
837 if (keys == null) return false;
838 for (int i=0; i< keys.length;i+=2) {
839 if (keys[i].equals(key)) return true;
840 }
841 return false;
842 }
843
844 /**
845 * Replies true if other isn't null and has the same tags (key/value-pairs) as this.
846 *
847 * @param other the other object primitive
848 * @return true if other isn't null and has the same tags (key/value-pairs) as this.
849 */
850 public boolean hasSameTags(OsmPrimitive other) {
851 return entrySet().equals(other.entrySet());
852 }
853
854 /*------------
855 * Referrers
856 ------------*/
857
858 private Object referrers;
859
860 /**
861 * Add new referrer. If referrer is already included then no action is taken
862 * @param referrer
863 */
864 protected void addReferrer(OsmPrimitive referrer) {
865 // Based on methods from josm-ng
866 if (referrers == null) {
867 referrers = referrer;
868 } else if (referrers instanceof OsmPrimitive) {
869 if (referrers != referrer) {
870 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
871 }
872 } else {
873 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
874 if (primitive == referrer)
875 return;
876 }
877 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
878 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
879 System.arraycopy(orig, 0, bigger, 0, orig.length);
880 bigger[orig.length] = referrer;
881 referrers = bigger;
882 }
883 }
884
885 /**
886 * Remove referrer. No action is taken if referrer is not registered
887 * @param referrer
888 */
889 protected void removeReferrer(OsmPrimitive referrer) {
890 // Based on methods from josm-ng
891 if (referrers instanceof OsmPrimitive) {
892 if (referrers == referrer) {
893 referrers = null;
894 }
895 } else if (referrers instanceof OsmPrimitive[]) {
896 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
897 int idx = -1;
898 for (int i=0; i<orig.length; i++) {
899 if (orig[i] == referrer) {
900 idx = i;
901 break;
902 }
903 }
904 if (idx == -1)
905 return;
906
907 if (orig.length == 2) {
908 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
909 } else { // downsize the array
910 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
911 System.arraycopy(orig, 0, smaller, 0, idx);
912 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
913 referrers = smaller;
914 }
915 }
916 }
917 /**
918 * Find primitives that reference this primitive. Returns only primitives that are included in the same
919 * dataset as this primitive. <br>
920 *
921 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
922 * not return wnew because it's not part of the dataset <br>
923 *
924 * <code>Way wnew = new Way(existingWay)</code>
925 *
926 * @return a collection of all primitives that reference this primitive.
927 */
928
929 public final List<OsmPrimitive> getReferrers() {
930 checkDataset();
931 // Method copied from OsmPrimitive in josm-ng
932 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
933 // when way is cloned
934 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
935 if (referrers != null) {
936 if (referrers instanceof OsmPrimitive) {
937 OsmPrimitive ref = (OsmPrimitive)referrers;
938 if (ref.dataSet == dataSet) {
939 result.add(ref);
940 }
941 } else {
942 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
943 if (dataSet == o.dataSet) {
944 result.add(o);
945 }
946 }
947 }
948 }
949
950 return result;
951 }
952
953 /**
954 * Get and write all attributes from the parameter. Does not fire any listener, so
955 * use this only in the data initializing phase
956 */
957 public void cloneFrom(OsmPrimitive other) {
958 if (id != other.id && dataSet != null)
959 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
960 setKeys(other.getKeys());
961 id = other.id;
962 timestamp = other.timestamp;
963 version = other.version;
964 setIncomplete(other.isIncomplete());
965 flags = other.flags;
966 user= other.user;
967 changesetId = other.changesetId;
968 clearCached();
969 }
970
971 /**
972 * Merges the technical and semantical attributes from <code>other</code> onto this.
973 *
974 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
975 * have an assigend OSM id, the IDs have to be the same.
976 *
977 * @param other the other primitive. Must not be null.
978 * @throws IllegalArgumentException thrown if other is null.
979 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
980 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
981 */
982 public void mergeFrom(OsmPrimitive other) {
983 if (other == null)
984 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "other"));
985 if (other.isNew() ^ isNew())
986 throw new DataIntegrityProblemException(tr("Can''t merge because either of the participating primitives is new and the other is not"));
987 if (! other.isNew() && other.getId() != id)
988 throw new DataIntegrityProblemException(tr("Can''t merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
989 setKeys(other.getKeys());
990 timestamp = other.timestamp;
991 version = other.version;
992 setIncomplete(other.isIncomplete());
993 flags = other.flags;
994 user= other.user;
995 changesetId = other.changesetId;
996 }
997
998 /**
999 * Replies true if this primitive and other are equal with respect to their
1000 * semantic attributes.
1001 * <ol>
1002 * <li>equal id</ol>
1003 * <li>both are complete or both are incomplete</li>
1004 * <li>both have the same tags</li>
1005 * </ol>
1006 * @param other
1007 * @return true if this primitive and other are equal with respect to their
1008 * semantic attributes.
1009 */
1010 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1011 if (!isNew() && id != other.id)
1012 return false;
1013 if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
1014 return false;
1015 // can't do an equals check on the internal keys array because it is not ordered
1016 //
1017 return hasSameTags(other);
1018 }
1019
1020 /**
1021 * Replies true if this primitive and other are equal with respect to their
1022 * technical attributes. The attributes:
1023 * <ol>
1024 * <li>deleted</ol>
1025 * <li>modified</ol>
1026 * <li>timestamp</ol>
1027 * <li>version</ol>
1028 * <li>visible</ol>
1029 * <li>user</ol>
1030 * </ol>
1031 * have to be equal
1032 * @param other the other primitive
1033 * @return true if this primitive and other are equal with respect to their
1034 * technical attributes
1035 */
1036 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1037 if (other == null) return false;
1038
1039 return
1040 isDeleted() == other.isDeleted()
1041 && isModified() == other.isModified()
1042 && timestamp == other.timestamp
1043 && version == other.version
1044 && isVisible() == other.isVisible()
1045 && (user == null ? other.user==null : user==other.user)
1046 && changesetId == other.changesetId;
1047 }
1048
1049 private void updateTagged() {
1050 getUninterestingKeys();
1051 if (keys != null) {
1052 for (Entry<String,String> e : getKeys().entrySet()) {
1053 if (!uninteresting.contains(e.getKey())) {
1054 flags |= FLAG_TAGGED;
1055 return;
1056 }
1057 }
1058 }
1059 flags &= ~FLAG_TAGGED;
1060 }
1061
1062 /**
1063 * true if this object is considered "tagged". To be "tagged", an object
1064 * must have one or more "interesting" tags. "created_by" and "source"
1065 * are typically considered "uninteresting" and do not make an object
1066 * "tagged".
1067 */
1068 public boolean isTagged() {
1069 return (flags & FLAG_TAGGED) != 0;
1070 }
1071
1072 private void updateHasDirectionKeys() {
1073 getDirectionKeys();
1074 if (keys != null) {
1075 for (Entry<String,String> e : getKeys().entrySet()) {
1076 if (directionKeys.contains(e.getKey())) {
1077 flags |= FLAG_HAS_DIRECTIONS;
1078 return;
1079 }
1080 }
1081 }
1082 flags &= ~FLAG_HAS_DIRECTIONS;
1083 }
1084 /**
1085 * true if this object has direction dependent tags (e.g. oneway)
1086 */
1087 public boolean hasDirectionKeys() {
1088 return (flags & FLAG_HAS_DIRECTIONS) != 0;
1089 }
1090
1091 /**
1092 * Replies the name of this primitive. The default implementation replies the value
1093 * of the tag <tt>name</tt> or null, if this tag is not present.
1094 *
1095 * @return the name of this primitive
1096 */
1097 public String getName() {
1098 if (get("name") != null)
1099 return get("name");
1100 return null;
1101 }
1102
1103 /**
1104 * Replies the a localized name for this primitive given by the value of the tags (in this order)
1105 * <ul>
1106 * <li>name:lang_COUNTRY_Variant of the current locale</li>
1107 * <li>name:lang_COUNTRY of the current locale</li>
1108 * <li>name:lang of the current locale</li>
1109 * <li>name of the current locale</li>
1110 * </ul>
1111 *
1112 * null, if no such tag exists
1113 *
1114 * @return the name of this primitive
1115 */
1116 public String getLocalName() {
1117 String key = "name:" + Locale.getDefault().toString();
1118 if (get(key) != null)
1119 return get(key);
1120 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
1121 if (get(key) != null)
1122 return get(key);
1123 key = "name:" + Locale.getDefault().getLanguage();
1124 if (get(key) != null)
1125 return get(key);
1126 return getName();
1127 }
1128
1129 /**
1130 * Replies the display name of a primitive formatted by <code>formatter</code>
1131 *
1132 * @return the display name
1133 */
1134 public abstract String getDisplayName(NameFormatter formatter);
1135
1136 /**
1137 * Loads (clone) this primitive from provided PrimitiveData
1138 * @param data
1139 */
1140 public void load(PrimitiveData data) {
1141 setKeys(data.getKeys());
1142 timestamp = data.getTimestamp();
1143 user = data.getUser();
1144 changesetId = data.getChangesetId();
1145 setDeleted(data.isDeleted());
1146 setModified(data.isModified());
1147 setVisible(data.isVisible());
1148 setIncomplete(data.isIncomplete());
1149 }
1150
1151 /**
1152 * Save parameters of this primitive to the transport object
1153 * @return
1154 */
1155 public abstract PrimitiveData save();
1156
1157 protected void saveCommonAttributes(PrimitiveData data) {
1158 data.setId(id);
1159 data.getKeys().clear();
1160 data.getKeys().putAll(getKeys());
1161 data.setTimestamp(timestamp);
1162 data.setUser(user);
1163 data.setDeleted(isDeleted());
1164 data.setModified(isModified());
1165 data.setVisible(isVisible());
1166 data.setIncomplete(isIncomplete());
1167 data.setChangesetId(changesetId);
1168 }
1169
1170 protected String getFlagsAsString() {
1171
1172 StringBuilder builder = new StringBuilder();
1173
1174 if (isIncomplete()) {
1175 builder.append("I");
1176 }
1177 if (isModified()) {
1178 builder.append("M");
1179 }
1180 if (isVisible()) {
1181 builder.append("V");
1182 }
1183 if (isDeleted()) {
1184 builder.append("D");
1185 }
1186 if (isFiltered()) {
1187 builder.append("f");
1188 }
1189
1190 if (isDeleted()) {
1191 builder.append("d");
1192 }
1193
1194 return builder.toString();
1195 }
1196
1197 public abstract BBox getBBox();
1198
1199 /**
1200 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1201 */
1202 public abstract void updatePosition();
1203
1204 /**
1205 * Replies the unique primitive id for this primitive
1206 *
1207 * @return the unique primitive id for this primitive
1208 */
1209 public PrimitiveId getPrimitiveId() {
1210 return new SimplePrimitiveId(getUniqueId(), getType());
1211 }
1212
1213 private void setIncomplete(boolean incomplete) {
1214 if (dataSet != null && incomplete != this.incomplete) {
1215 if (incomplete) {
1216 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
1217 } else {
1218 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
1219 }
1220 }
1221 this.incomplete = incomplete;
1222 }
1223
1224 public boolean isIncomplete() {
1225 return incomplete;
1226 }
1227}
Note: See TracBrowser for help on using the repository browser.