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

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

Cache list of used tags in properties dialog (faster adding of keys in large datasets)

  • Property svn:eol-style set to native
File size: 41.1 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
171 /* mappaint data */
172 public ElemStyle mappaintStyle = null;
173 public Integer mappaintDrawnCode = 0;
174
175 /* This should not be called from outside. Fixing the UI to add relevant
176 get/set functions calling this implicitely is preferred, so we can have
177 transparent cache handling in the future. */
178 protected void clearCached()
179 {
180 mappaintDrawnCode = 0;
181 mappaintStyle = null;
182 }
183 /* end of mappaint data */
184
185 /**
186 * Unique identifier in OSM. This is used to identify objects on the server.
187 * An id of 0 means an unknown id. The object has not been uploaded yet to
188 * know what id it will get.
189 *
190 */
191 private long id = 0;
192
193 /** the parent dataset */
194 private DataSet dataSet;
195
196 /**
197 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
198 * @param dataSet
199 */
200 void setDataset(DataSet dataSet) {
201 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
202 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
203 this.dataSet = dataSet;
204 }
205
206 /**
207 *
208 * @return DataSet this primitive is part of.
209 */
210 public DataSet getDataSet() {
211 return dataSet;
212 }
213
214 /**
215 * Throws exception if primitive is not part of the dataset
216 */
217 public void checkDataset() {
218 if (dataSet == null)
219 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
220 }
221
222 private volatile byte flags = FLAG_VISIBLE; // visible per default
223
224 /**
225 * User that last modified this primitive, as specified by the server.
226 * Never changed by JOSM.
227 */
228 private User user = null;
229
230 /**
231 * If set to true, this object is incomplete, which means only the id
232 * and type is known (type is the objects instance class)
233 */
234 private boolean incomplete = false;
235
236 /**
237 * Contains the version number as returned by the API. Needed to
238 * ensure update consistency
239 */
240 private int version = 0;
241
242 /**
243 * The id of the changeset this primitive was last uploaded to.
244 * 0 if it wasn't uploaded to a changeset yet of if the changeset
245 * id isn't known.
246 */
247 private int changesetId;
248
249 /**
250 * Creates a new primitive for the given id.
251 *
252 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
253 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
254 * positive number.
255 *
256 * @param id the id
257 * @param allowNegativeId
258 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
259 */
260 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
261 if (allowNegativeId) {
262 this.id = id;
263 } else {
264 if (id < 0)
265 throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
266 else if (id == 0) {
267 this.id = generateUniqueId();
268 } else {
269 this.id = id;
270 }
271
272 }
273 this.version = 0;
274 this.setIncomplete(id > 0);
275 }
276
277 protected OsmPrimitive(PrimitiveData data) {
278 version = data.getVersion();
279 id = data.getId();
280 }
281
282 /* ------------------------------------------------------------------------------------ */
283 /* accessors */
284 /* ------------------------------------------------------------------------------------ */
285 /**
286 * Sets whether this primitive is disabled or not.
287 *
288 * @param disabled true, if this primitive is disabled; false, otherwise
289 */
290 void setDisabled(boolean disabled) {
291 if (disabled) {
292 flags |= FLAG_DISABLED;
293 } else {
294 flags &= ~FLAG_DISABLED;
295 }
296
297 }
298
299 /**
300 * Replies true, if this primitive is disabled.
301 *
302 * @return true, if this primitive is disabled
303 */
304 public boolean isDisabled() {
305 return (flags & FLAG_DISABLED) != 0;
306 }
307 /**
308 * Sets whether this primitive is filtered out or not.
309 *
310 * @param filtered true, if this primitive is filtered out; false, otherwise
311 */
312 public void setFiltered(boolean filtered) {
313 if (filtered) {
314 flags |= FLAG_FILTERED;
315 } else {
316 flags &= ~FLAG_FILTERED;
317 }
318 }
319 /**
320 * Replies true, if this primitive is filtered out.
321 *
322 * @return true, if this primitive is filtered out
323 */
324 public boolean isFiltered() {
325 return (flags & FLAG_FILTERED) != 0;
326 }
327
328 /**
329 * Marks this primitive as being modified.
330 *
331 * @param modified true, if this primitive is to be modified
332 */
333 public void setModified(boolean modified) {
334 if (modified) {
335 flags |= FLAG_MODIFIED;
336 } else {
337 flags &= ~FLAG_MODIFIED;
338 }
339 }
340
341 /**
342 * Replies <code>true</code> if the object has been modified since it was loaded from
343 * the server. In this case, on next upload, this object will be updated.
344 *
345 * Deleted objects are deleted from the server. If the objects are added (id=0),
346 * the modified is ignored and the object is added to the server.
347 *
348 * @return <code>true</code> if the object has been modified since it was loaded from
349 * the server
350 */
351 public boolean isModified() {
352 return (flags & FLAG_MODIFIED) != 0;
353 }
354
355 /**
356 * Replies <code>true</code>, if the object has been deleted.
357 *
358 * @return <code>true</code>, if the object has been deleted.
359 * @see #setDeleted(boolean)
360 */
361 public boolean isDeleted() {
362 return (flags & FLAG_DELETED) != 0;
363 }
364
365 /**
366 * Replies <code>true</code>, if the object is usable.
367 *
368 * @return <code>true</code>, if the object is unusable.
369 * @see #delete(boolean)
370 */
371 public boolean isUsable() {
372 return !isDeleted() && !isIncomplete() && !isDisabled();
373 }
374
375 public boolean isDrawable()
376 {
377 return !isDeleted() && !isIncomplete() && !isFiltered();
378 }
379
380 /**
381 * Replies true if this primitive is either unknown to the server (i.e. its id
382 * is 0) or it is known to the server and it hasn't be deleted on the server.
383 * Replies false, if this primitive is known on the server and has been deleted
384 * on the server.
385 *
386 * @see #setVisible(boolean)
387 */
388 public boolean isVisible() {
389 return (flags & FLAG_VISIBLE) != 0;
390 }
391
392 /**
393 * Sets whether this primitive is visible, i.e. whether it is known on the server
394 * and not deleted on the server.
395 *
396 * @see #isVisible()
397 * @throws IllegalStateException thrown if visible is set to false on an primitive with
398 * id==0
399 */
400 public void setVisible(boolean visible) throws IllegalStateException{
401 if (isNew() && visible == false)
402 throw new IllegalStateException(tr("A primitive with ID = 0 can't be invisible."));
403 if (visible) {
404 flags |= FLAG_VISIBLE;
405 } else {
406 flags &= ~FLAG_VISIBLE;
407 }
408 }
409
410 /**
411 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
412 * if this primitive is incomplete.
413 *
414 * @see #setVersion(int)
415 */
416 public long getVersion() {
417 return version;
418 }
419
420 /**
421 * Replies the id of this primitive.
422 *
423 * @return the id of this primitive.
424 */
425 public long getId() {
426 return id >= 0?id:0;
427 }
428
429 /**
430 *
431 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
432 */
433 public long getUniqueId() {
434 return id;
435 }
436
437 /**
438 *
439 * @return True if primitive is new (not yet uploaded the server, id <= 0)
440 */
441 public boolean isNew() {
442 return id <= 0;
443 }
444
445 /**
446 * Sets the id and the version of this primitive if it is known to the OSM API.
447 *
448 * Since we know the id and its version it can't be incomplete anymore. incomplete
449 * is set to false.
450 *
451 * @param id the id. > 0 required
452 * @param version the version > 0 required
453 * @throws IllegalArgumentException thrown if id <= 0
454 * @throws IllegalArgumentException thrown if version <= 0
455 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
456 */
457 public void setOsmId(long id, int version) {
458 if (id <= 0)
459 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
460 if (version <= 0)
461 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
462 if (dataSet != null && id != this.id) {
463 DataSet datasetCopy = dataSet;
464 // Reindex primitive
465 datasetCopy.removePrimitive(this);
466 this.id = id;
467 datasetCopy.addPrimitive(this);
468 }
469 this.id = id;
470 this.version = version;
471 this.setIncomplete(false);
472 }
473
474 /**
475 * Clears the id and version known to the OSM API. The id and the version is set to 0.
476 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
477 * of calling this method.
478 *
479 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
480 *
481 * @throws DataIntegrityProblemException If primitive was already added to the dataset
482 */
483 public void clearOsmId() {
484 if (dataSet != null)
485 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
486 this.id = generateUniqueId();
487 this.version = 0;
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 /**
631 * Replies the id of the changeset this primitive was last uploaded to.
632 * 0 if this primitive wasn't uploaded to a changeset yet or if the
633 * changeset isn't known.
634 *
635 * @return the id of the changeset this primitive was last uploaded to.
636 */
637 public int getChangesetId() {
638 return changesetId;
639 }
640
641
642 /**
643 * Sets the changeset id of this primitive. Can't be set on a new
644 * primitive.
645 *
646 * @param changesetId the id. >= 0 required.
647 * @throws IllegalStateException thrown if this primitive is new.
648 * @throws IllegalArgumentException thrown if id < 0
649 */
650 public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
651 if (this.changesetId == changesetId)
652 return;
653 if (changesetId < 0)
654 throw new IllegalArgumentException(tr("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
655 if (isNew() && changesetId > 0)
656 throw new IllegalStateException(tr("Can''t assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
657 int old = this.changesetId;
658 this.changesetId = changesetId;
659 if (dataSet != null) {
660 dataSet.fireChangesetIdChanged(this, old, changesetId);
661 }
662 }
663
664 /**
665 * Equal, if the id (and class) is equal.
666 *
667 * An primitive is equal to its incomplete counter part.
668 */
669 @Override public boolean equals(Object obj) {
670 if (obj instanceof OsmPrimitive)
671 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
672 return false;
673 }
674
675 /**
676 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
677 *
678 * An primitive has the same hashcode as its incomplete counterpart.
679 */
680 @Override public final int hashCode() {
681 return (int)id;
682 }
683
684 /*------------
685 * Keys handling
686 ------------*/
687
688 /**
689 * The key/value list for this primitive.
690 *
691 */
692 private String[] keys;
693
694 /**
695 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
696 *
697 * @return tags of this primitive. Changes made in returned map are not mapped
698 * back to the primitive, use setKeys() to modify the keys
699 * @since 1924
700 */
701 public Map<String, String> getKeys() {
702 Map<String, String> result = new HashMap<String, String>();
703 if (keys != null) {
704 for (int i=0; i<keys.length ; i+=2) {
705 result.put(keys[i], keys[i + 1]);
706 }
707 }
708 return result;
709 }
710
711 /**
712 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
713 * If <code>keys</code> is null removes all existing key/value pairs.
714 *
715 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
716 * @since 1924
717 */
718 public void setKeys(Map<String, String> keys) {
719 Map<String, String> originalKeys = getKeys();
720 if (keys == null) {
721 this.keys = null;
722 keysChangedImpl(originalKeys);
723 return;
724 }
725 String[] newKeys = new String[keys.size() * 2];
726 int index = 0;
727 for (Entry<String, String> entry:keys.entrySet()) {
728 newKeys[index++] = entry.getKey();
729 newKeys[index++] = entry.getValue();
730 }
731 this.keys = newKeys;
732 keysChangedImpl(originalKeys);
733 }
734
735 /**
736 * Set the given value to the given key. If key is null, does nothing. If value is null,
737 * removes the key and behaves like {@see #remove(String)}.
738 *
739 * @param key The key, for which the value is to be set. Can be null, does nothing in this case.
740 * @param value The value for the key. If null, removes the respective key/value pair.
741 *
742 * @see #remove(String)
743 */
744 public final void put(String key, String value) {
745 Map<String, String> originalKeys = getKeys();
746 if (key == null)
747 return;
748 else if (value == null) {
749 remove(key);
750 } else if (keys == null || keys.length == 0){
751 keys = new String[] {key, value};
752 keysChangedImpl(originalKeys);
753 } else {
754 for (int i=0; i<keys.length;i+=2) {
755 if (keys[i].equals(key)) {
756 keys[i+1] = value;
757 keysChangedImpl(originalKeys);
758 return;
759 }
760 }
761 String[] newKeys = new String[keys.length + 2];
762 for (int i=0; i< keys.length;i+=2) {
763 newKeys[i] = keys[i];
764 newKeys[i+1] = keys[i+1];
765 }
766 newKeys[keys.length] = key;
767 newKeys[keys.length + 1] = value;
768 keys = newKeys;
769 keysChangedImpl(originalKeys);
770 }
771 }
772 /**
773 * Remove the given key from the list
774 *
775 * @param key the key to be removed. Ignored, if key is null.
776 */
777 public final void remove(String key) {
778 if (key == null || keys == null || keys.length == 0 ) return;
779 if (!hasKey(key))
780 return;
781 Map<String, String> originalKeys = getKeys();
782 if (keys.length == 2) {
783 keys = null;
784 keysChangedImpl(originalKeys);
785 return;
786 }
787 String[] newKeys = new String[keys.length - 2];
788 int j=0;
789 for (int i=0; i < keys.length; i+=2) {
790 if (!keys[i].equals(key)) {
791 newKeys[j++] = keys[i];
792 newKeys[j++] = keys[i+1];
793 }
794 }
795 keys = newKeys;
796 keysChangedImpl(originalKeys);
797 }
798
799 /**
800 * Removes all keys from this primitive.
801 *
802 * @since 1843
803 */
804 public final void removeAll() {
805 if (keys != null) {
806 Map<String, String> originalKeys = getKeys();
807 keys = null;
808 keysChangedImpl(originalKeys);
809 }
810 }
811
812 /**
813 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
814 * Replies null, if there is no value for the given key.
815 *
816 * @param key the key. Can be null, replies null in this case.
817 * @return the value for key <code>key</code>.
818 */
819 public final String get(String key) {
820 if (key == null)
821 return null;
822 if (keys == null || keys.length == 0)
823 return null;
824 for (int i=0; i<keys.length;i+=2) {
825 if (keys[i].equals(key)) return keys[i+1];
826 }
827 return null;
828 }
829
830 // FIXME: why replying a collection of entries? Should be replaced by
831 // a method Map<String,String> getTags()
832 //
833 public final Collection<Entry<String, String>> entrySet() {
834 if (keys == null || keys.length ==0)
835 return Collections.emptySet();
836 Set<Entry<String, String>> result = new HashSet<Entry<String,String>>();
837 for (int i=0; i<keys.length; i+=2) {
838 result.add(new KeysEntry(keys[i], keys[i+1]));
839 }
840 return result;
841 }
842
843 public final Collection<String> keySet() {
844 if (keys == null || keys.length == 0)
845 return Collections.emptySet();
846 Set<String> result = new HashSet<String>(keys.length / 2);
847 for (int i=0; i<keys.length; i+=2) {
848 result.add(keys[i]);
849 }
850 return result;
851 }
852
853 /**
854 * Replies true, if the map of key/value pairs of this primitive is not empty.
855 *
856 * @return true, if the map of key/value pairs of this primitive is not empty; false
857 * otherwise
858 */
859 public final boolean hasKeys() {
860 return keys != null && keys.length != 0;
861 }
862
863 private void keysChangedImpl(Map<String, String> originalKeys) {
864 clearCached();
865 updateHasDirectionKeys();
866 updateTagged();
867 if (dataSet != null) {
868 dataSet.fireTagsChanged(this, originalKeys);
869 }
870 }
871
872 /**
873 * Replies true if this primitive has a tag with key <code>key</code>
874 *
875 * @param key the key
876 * @return true, if his primitive has a tag with key <code>key</code>
877 */
878 public boolean hasKey(String key) {
879 if (key == null) return false;
880 if (keys == null) return false;
881 for (int i=0; i< keys.length;i+=2) {
882 if (keys[i].equals(key)) return true;
883 }
884 return false;
885 }
886
887 /**
888 * Replies true if other isn't null and has the same tags (key/value-pairs) as this.
889 *
890 * @param other the other object primitive
891 * @return true if other isn't null and has the same tags (key/value-pairs) as this.
892 */
893 public boolean hasSameTags(OsmPrimitive other) {
894 return entrySet().equals(other.entrySet());
895 }
896
897 /*------------
898 * Referrers
899 ------------*/
900
901 private Object referrers;
902
903 /**
904 * Add new referrer. If referrer is already included then no action is taken
905 * @param referrer
906 */
907 protected void addReferrer(OsmPrimitive referrer) {
908 // Based on methods from josm-ng
909 if (referrers == null) {
910 referrers = referrer;
911 } else if (referrers instanceof OsmPrimitive) {
912 if (referrers != referrer) {
913 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
914 }
915 } else {
916 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
917 if (primitive == referrer)
918 return;
919 }
920 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
921 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
922 System.arraycopy(orig, 0, bigger, 0, orig.length);
923 bigger[orig.length] = referrer;
924 referrers = bigger;
925 }
926 }
927
928 /**
929 * Remove referrer. No action is taken if referrer is not registered
930 * @param referrer
931 */
932 protected void removeReferrer(OsmPrimitive referrer) {
933 // Based on methods from josm-ng
934 if (referrers instanceof OsmPrimitive) {
935 if (referrers == referrer) {
936 referrers = null;
937 }
938 } else if (referrers instanceof OsmPrimitive[]) {
939 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
940 int idx = -1;
941 for (int i=0; i<orig.length; i++) {
942 if (orig[i] == referrer) {
943 idx = i;
944 break;
945 }
946 }
947 if (idx == -1)
948 return;
949
950 if (orig.length == 2) {
951 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
952 } else { // downsize the array
953 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
954 System.arraycopy(orig, 0, smaller, 0, idx);
955 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
956 referrers = smaller;
957 }
958 }
959 }
960 /**
961 * Find primitives that reference this primitive. Returns only primitives that are included in the same
962 * dataset as this primitive. <br>
963 *
964 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
965 * not return wnew because it's not part of the dataset <br>
966 *
967 * <code>Way wnew = new Way(existingWay)</code>
968 *
969 * @return a collection of all primitives that reference this primitive.
970 */
971
972 public final List<OsmPrimitive> getReferrers() {
973 checkDataset();
974 // Method copied from OsmPrimitive in josm-ng
975 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
976 // when way is cloned
977 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
978 if (referrers != null) {
979 if (referrers instanceof OsmPrimitive) {
980 OsmPrimitive ref = (OsmPrimitive)referrers;
981 if (ref.dataSet == dataSet) {
982 result.add(ref);
983 }
984 } else {
985 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
986 if (dataSet == o.dataSet) {
987 result.add(o);
988 }
989 }
990 }
991 }
992
993 return result;
994 }
995
996 /**
997 * Get and write all attributes from the parameter. Does not fire any listener, so
998 * use this only in the data initializing phase
999 */
1000 public void cloneFrom(OsmPrimitive other) {
1001 if (id != other.id && dataSet != null)
1002 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
1003 setKeys(other.getKeys());
1004 id = other.id;
1005 timestamp = other.timestamp;
1006 version = other.version;
1007 setIncomplete(other.isIncomplete());
1008 flags = other.flags;
1009 user= other.user;
1010 setChangesetId(other.changesetId);
1011 clearCached();
1012 }
1013
1014 /**
1015 * Merges the technical and semantical attributes from <code>other</code> onto this.
1016 *
1017 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1018 * have an assigend OSM id, the IDs have to be the same.
1019 *
1020 * @param other the other primitive. Must not be null.
1021 * @throws IllegalArgumentException thrown if other is null.
1022 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
1023 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
1024 */
1025 public void mergeFrom(OsmPrimitive other) {
1026 if (other == null)
1027 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "other"));
1028 if (other.isNew() ^ isNew())
1029 throw new DataIntegrityProblemException(tr("Can''t merge because either of the participating primitives is new and the other is not"));
1030 if (! other.isNew() && other.getId() != id)
1031 throw new DataIntegrityProblemException(tr("Can''t merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1032 setKeys(other.getKeys());
1033 timestamp = other.timestamp;
1034 version = other.version;
1035 setIncomplete(other.isIncomplete());
1036 flags = other.flags;
1037 user= other.user;
1038 setChangesetId(other.changesetId);
1039 }
1040
1041 /**
1042 * Replies true if this primitive and other are equal with respect to their
1043 * semantic attributes.
1044 * <ol>
1045 * <li>equal id</ol>
1046 * <li>both are complete or both are incomplete</li>
1047 * <li>both have the same tags</li>
1048 * </ol>
1049 * @param other
1050 * @return true if this primitive and other are equal with respect to their
1051 * semantic attributes.
1052 */
1053 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1054 if (!isNew() && id != other.id)
1055 return false;
1056 if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
1057 return false;
1058 // can't do an equals check on the internal keys array because it is not ordered
1059 //
1060 return hasSameTags(other);
1061 }
1062
1063 /**
1064 * Replies true if this primitive and other are equal with respect to their
1065 * technical attributes. The attributes:
1066 * <ol>
1067 * <li>deleted</ol>
1068 * <li>modified</ol>
1069 * <li>timestamp</ol>
1070 * <li>version</ol>
1071 * <li>visible</ol>
1072 * <li>user</ol>
1073 * </ol>
1074 * have to be equal
1075 * @param other the other primitive
1076 * @return true if this primitive and other are equal with respect to their
1077 * technical attributes
1078 */
1079 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1080 if (other == null) return false;
1081
1082 return
1083 isDeleted() == other.isDeleted()
1084 && isModified() == other.isModified()
1085 && timestamp == other.timestamp
1086 && version == other.version
1087 && isVisible() == other.isVisible()
1088 && (user == null ? other.user==null : user==other.user)
1089 && changesetId == other.changesetId;
1090 }
1091
1092 private void updateTagged() {
1093 getUninterestingKeys();
1094 if (keys != null) {
1095 for (Entry<String,String> e : getKeys().entrySet()) {
1096 if (!uninteresting.contains(e.getKey())) {
1097 flags |= FLAG_TAGGED;
1098 return;
1099 }
1100 }
1101 }
1102 flags &= ~FLAG_TAGGED;
1103 }
1104
1105 /**
1106 * true if this object is considered "tagged". To be "tagged", an object
1107 * must have one or more "interesting" tags. "created_by" and "source"
1108 * are typically considered "uninteresting" and do not make an object
1109 * "tagged".
1110 */
1111 public boolean isTagged() {
1112 return (flags & FLAG_TAGGED) != 0;
1113 }
1114
1115 private void updateHasDirectionKeys() {
1116 initDirectionKeys();
1117 if (directionKeys.match(this)) {
1118 flags |= FLAG_HAS_DIRECTIONS;
1119 } else {
1120 flags &= ~FLAG_HAS_DIRECTIONS;
1121 }
1122 }
1123
1124
1125 /**
1126 * true if this object has direction dependent tags (e.g. oneway)
1127 */
1128 public boolean hasDirectionKeys() {
1129 return (flags & FLAG_HAS_DIRECTIONS) != 0;
1130 }
1131
1132 /**
1133 * Replies the name of this primitive. The default implementation replies the value
1134 * of the tag <tt>name</tt> or null, if this tag is not present.
1135 *
1136 * @return the name of this primitive
1137 */
1138 public String getName() {
1139 if (get("name") != null)
1140 return get("name");
1141 return null;
1142 }
1143
1144 /**
1145 * Replies the a localized name for this primitive given by the value of the tags (in this order)
1146 * <ul>
1147 * <li>name:lang_COUNTRY_Variant of the current locale</li>
1148 * <li>name:lang_COUNTRY of the current locale</li>
1149 * <li>name:lang of the current locale</li>
1150 * <li>name of the current locale</li>
1151 * </ul>
1152 *
1153 * null, if no such tag exists
1154 *
1155 * @return the name of this primitive
1156 */
1157 public String getLocalName() {
1158 String key = "name:" + Locale.getDefault().toString();
1159 if (get(key) != null)
1160 return get(key);
1161 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
1162 if (get(key) != null)
1163 return get(key);
1164 key = "name:" + Locale.getDefault().getLanguage();
1165 if (get(key) != null)
1166 return get(key);
1167 return getName();
1168 }
1169
1170 /**
1171 * Replies the display name of a primitive formatted by <code>formatter</code>
1172 *
1173 * @return the display name
1174 */
1175 public abstract String getDisplayName(NameFormatter formatter);
1176
1177 /**
1178 * Loads (clone) this primitive from provided PrimitiveData
1179 * @param data
1180 */
1181 public void load(PrimitiveData data) {
1182 setKeys(data.getKeys());
1183 timestamp = data.getTimestamp();
1184 user = data.getUser();
1185 setChangesetId(data.getChangesetId());
1186 setDeleted(data.isDeleted());
1187 setModified(data.isModified());
1188 setVisible(data.isVisible());
1189 setIncomplete(data.isIncomplete());
1190 }
1191
1192 /**
1193 * Save parameters of this primitive to the transport object
1194 * @return
1195 */
1196 public abstract PrimitiveData save();
1197
1198 protected void saveCommonAttributes(PrimitiveData data) {
1199 data.setId(id);
1200 data.getKeys().clear();
1201 data.getKeys().putAll(getKeys());
1202 data.setTimestamp(timestamp);
1203 data.setUser(user);
1204 data.setDeleted(isDeleted());
1205 data.setModified(isModified());
1206 data.setVisible(isVisible());
1207 data.setIncomplete(isIncomplete());
1208 data.setChangesetId(changesetId);
1209 }
1210
1211 protected String getFlagsAsString() {
1212
1213 StringBuilder builder = new StringBuilder();
1214
1215 if (isIncomplete()) {
1216 builder.append("I");
1217 }
1218 if (isModified()) {
1219 builder.append("M");
1220 }
1221 if (isVisible()) {
1222 builder.append("V");
1223 }
1224 if (isDeleted()) {
1225 builder.append("D");
1226 }
1227 if (isFiltered()) {
1228 builder.append("f");
1229 }
1230
1231 if (isDeleted()) {
1232 builder.append("d");
1233 }
1234
1235 return builder.toString();
1236 }
1237
1238 public abstract BBox getBBox();
1239
1240 /**
1241 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1242 */
1243 public abstract void updatePosition();
1244
1245 /**
1246 * Replies the unique primitive id for this primitive
1247 *
1248 * @return the unique primitive id for this primitive
1249 */
1250 public PrimitiveId getPrimitiveId() {
1251 return new SimplePrimitiveId(getUniqueId(), getType());
1252 }
1253
1254 private void setIncomplete(boolean incomplete) {
1255 if (dataSet != null && incomplete != this.incomplete) {
1256 if (incomplete) {
1257 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
1258 } else {
1259 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
1260 }
1261 }
1262 this.incomplete = incomplete;
1263 }
1264
1265 public boolean isIncomplete() {
1266 return incomplete;
1267 }
1268}
Note: See TracBrowser for help on using the repository browser.