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

Last change on this file since 2711 was 2711, checked in by stoecker, 14 years ago

fix bad line endings

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