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

Last change on this file since 2845 was 2845, checked in by mjulius, 14 years ago

fix messages for data

  • Property svn:eol-style set to native
File size: 41.8 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.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Locale;
18import java.util.Map;
19import java.util.Set;
20import java.util.Map.Entry;
21import java.util.concurrent.atomic.AtomicLong;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.search.SearchCompiler;
25import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
26import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
27import org.openstreetmap.josm.data.osm.visitor.Visitor;
28import org.openstreetmap.josm.gui.mappaint.ElemStyle;
29import org.openstreetmap.josm.tools.CheckParameterUtil;
30
31/**
32 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
33 * and updated within the OSM-Server.
34 *
35 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
36 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
37 * by the server environment and not an extendible data stuff.
38 *
39 * @author imi
40 */
41abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged, PrimitiveId {
42
43 private static final AtomicLong idCounter = new AtomicLong(0);
44
45 static long generateUniqueId() {
46 return idCounter.decrementAndGet();
47 }
48
49 private static class KeysEntry implements Entry<String, String>{
50
51 private final String key;
52 private final String value;
53
54 private KeysEntry(String key, String value) {
55 this.key = key;
56 this.value = value;
57 }
58
59 public String getKey() {
60 return key;
61 }
62
63 public String getValue() {
64 return value;
65 }
66
67 public String setValue(String value) {
68 throw new UnsupportedOperationException();
69 }
70
71 @Override
72 public int hashCode() {
73 final int prime = 31;
74 int result = 1;
75 result = prime * result + ((key == null) ? 0 : key.hashCode());
76 result = prime * result + ((value == null) ? 0 : value.hashCode());
77 return result;
78 }
79
80 @Override
81 public boolean equals(Object obj) {
82 if (this == obj)
83 return true;
84 if (obj == null)
85 return false;
86 if (getClass() != obj.getClass())
87 return false;
88 KeysEntry other = (KeysEntry) obj;
89 if (key == null) {
90 if (other.key != null)
91 return false;
92 } else if (!key.equals(other.key))
93 return false;
94 if (value == null) {
95 if (other.value != null)
96 return false;
97 } else if (!value.equals(other.value))
98 return false;
99 return true;
100 }
101 }
102
103 private static final int FLAG_MODIFIED = 1 << 0;
104 private static final int FLAG_VISIBLE = 1 << 1;
105 private static final int FLAG_DISABLED = 1 << 2;
106 private static final int FLAG_DELETED = 1 << 3;
107 private static final int FLAG_FILTERED = 1 << 4;
108 private static final int FLAG_HAS_DIRECTIONS = 1 << 5;
109 private static final int FLAG_TAGGED = 1 << 6;
110
111 /**
112 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
113 * another collection of {@see OsmPrimitive}s. The result collection is a list.
114 *
115 * If <code>list</code> is null, replies an empty list.
116 *
117 * @param <T>
118 * @param list the original list
119 * @param type the type to filter for
120 * @return the sub-list of OSM primitives of type <code>type</code>
121 */
122 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
123 if (list == null) return Collections.emptyList();
124 List<T> ret = new LinkedList<T>();
125 for(OsmPrimitive p: list) {
126 if (type.isInstance(p)) {
127 ret.add(type.cast(p));
128 }
129 }
130 return ret;
131 }
132
133 /**
134 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
135 * another collection of {@see OsmPrimitive}s. The result collection is a set.
136 *
137 * If <code>list</code> is null, replies an empty set.
138 *
139 * @param <T>
140 * @param list the original collection
141 * @param type the type to filter for
142 * @return the sub-set of OSM primitives of type <code>type</code>
143 */
144 static public <T extends OsmPrimitive> LinkedHashSet<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
145 LinkedHashSet<T> ret = new LinkedHashSet<T>();
146 if (set != null) {
147 for(OsmPrimitive p: set) {
148 if (type.isInstance(p)) {
149 ret.add(type.cast(p));
150 }
151 }
152 }
153 return ret;
154 }
155
156 /**
157 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
158 *
159 * @param primitives the collection of primitives.
160 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
161 * empty set if primitives is null or if there are no referring primitives
162 */
163 static public Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
164 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
165 if (primitives == null || primitives.isEmpty()) return ret;
166 for (OsmPrimitive p: primitives) {
167 ret.addAll(p.getReferrers());
168 }
169 return ret;
170 }
171
172 /* mappaint data */
173 public ElemStyle mappaintStyle = null;
174 public Integer mappaintDrawnCode = 0;
175
176 /* This should not be called from outside. Fixing the UI to add relevant
177 get/set functions calling this implicitely is preferred, so we can have
178 transparent cache handling in the future. */
179 protected void clearCached()
180 {
181 mappaintDrawnCode = 0;
182 mappaintStyle = null;
183 }
184 /* end of mappaint data */
185
186 /**
187 * Unique identifier in OSM. This is used to identify objects on the server.
188 * An id of 0 means an unknown id. The object has not been uploaded yet to
189 * know what id it will get.
190 *
191 */
192 private long id = 0;
193
194 /** the parent dataset */
195 private DataSet dataSet;
196
197 /**
198 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
199 * @param dataSet
200 */
201 void setDataset(DataSet dataSet) {
202 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
203 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
204 this.dataSet = dataSet;
205 }
206
207 /**
208 *
209 * @return DataSet this primitive is part of.
210 */
211 public DataSet getDataSet() {
212 return dataSet;
213 }
214
215 /**
216 * Throws exception if primitive is not part of the dataset
217 */
218 public void checkDataset() {
219 if (dataSet == null)
220 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
221 }
222
223 private volatile byte flags = FLAG_VISIBLE; // visible per default
224
225 /**
226 * User that last modified this primitive, as specified by the server.
227 * Never changed by JOSM.
228 */
229 private User user = null;
230
231 /**
232 * If set to true, this object is incomplete, which means only the id
233 * and type is known (type is the objects instance class)
234 */
235 private boolean incomplete = false;
236
237 /**
238 * Contains the version number as returned by the API. Needed to
239 * ensure update consistency
240 */
241 private int version = 0;
242
243 /**
244 * The id of the changeset this primitive was last uploaded to.
245 * 0 if it wasn't uploaded to a changeset yet of if the changeset
246 * id isn't known.
247 */
248 private int changesetId;
249
250 /**
251 * Creates a new primitive for the given id.
252 *
253 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
254 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
255 * positive number.
256 *
257 * @param id the id
258 * @param allowNegativeId
259 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
260 */
261 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
262 if (allowNegativeId) {
263 this.id = id;
264 } else {
265 if (id < 0)
266 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
267 else if (id == 0) {
268 this.id = generateUniqueId();
269 } else {
270 this.id = id;
271 }
272
273 }
274 this.version = 0;
275 this.setIncomplete(id > 0);
276 }
277
278 protected OsmPrimitive(PrimitiveData data) {
279 version = data.getVersion();
280 id = data.getId();
281 }
282
283 /* ------------------------------------------------------------------------------------ */
284 /* accessors */
285 /* ------------------------------------------------------------------------------------ */
286 /**
287 * Sets whether this primitive is disabled or not.
288 *
289 * @param disabled true, if this primitive is disabled; false, otherwise
290 */
291 public void setDisabled(boolean disabled) {
292 if (disabled) {
293 flags |= FLAG_DISABLED;
294 } else {
295 flags &= ~FLAG_DISABLED;
296 }
297
298 }
299
300 /**
301 * Replies true, if this primitive is disabled.
302 *
303 * @return true, if this primitive is disabled
304 */
305 public boolean isDisabled() {
306 return (flags & FLAG_DISABLED) != 0;
307 }
308 /**
309 * Sets whether this primitive is filtered out or not.
310 *
311 * @param filtered true, if this primitive is filtered out; false, otherwise
312 */
313 public void setFiltered(boolean filtered) {
314 if (filtered) {
315 flags |= FLAG_FILTERED;
316 } else {
317 flags &= ~FLAG_FILTERED;
318 }
319 }
320 /**
321 * Replies true, if this primitive is filtered out.
322 *
323 * @return true, if this primitive is filtered out
324 */
325 public boolean isFiltered() {
326 return (flags & FLAG_FILTERED) != 0;
327 }
328
329 /**
330 * Marks this primitive as being modified.
331 *
332 * @param modified true, if this primitive is to be modified
333 */
334 public void setModified(boolean modified) {
335 if (modified) {
336 flags |= FLAG_MODIFIED;
337 } else {
338 flags &= ~FLAG_MODIFIED;
339 }
340 }
341
342 /**
343 * Replies <code>true</code> if the object has been modified since it was loaded from
344 * the server. In this case, on next upload, this object will be updated.
345 *
346 * Deleted objects are deleted from the server. If the objects are added (id=0),
347 * the modified is ignored and the object is added to the server.
348 *
349 * @return <code>true</code> if the object has been modified since it was loaded from
350 * the server
351 */
352 public boolean isModified() {
353 return (flags & FLAG_MODIFIED) != 0;
354 }
355
356 /**
357 * Replies <code>true</code>, if the object has been deleted.
358 *
359 * @return <code>true</code>, if the object has been deleted.
360 * @see #setDeleted(boolean)
361 */
362 public boolean isDeleted() {
363 return (flags & FLAG_DELETED) != 0;
364 }
365
366 /**
367 * Replies <code>true</code>, if the object is usable.
368 *
369 * @return <code>true</code>, if the object is unusable.
370 * @see #delete(boolean)
371 */
372 public boolean isUsable() {
373 return !isDeleted() && !isIncomplete() && !isDisabled();
374 }
375
376 public boolean isDrawable()
377 {
378 return !isDeleted() && !isIncomplete() && !isFiltered();
379 }
380
381 /**
382 * Replies true if this primitive is either unknown to the server (i.e. its id
383 * is 0) or it is known to the server and it hasn't be deleted on the server.
384 * Replies false, if this primitive is known on the server and has been deleted
385 * on the server.
386 *
387 * @see #setVisible(boolean)
388 */
389 public boolean isVisible() {
390 return (flags & FLAG_VISIBLE) != 0;
391 }
392
393 /**
394 * Sets whether this primitive is visible, i.e. whether it is known on the server
395 * and not deleted on the server.
396 *
397 * @see #isVisible()
398 * @throws IllegalStateException thrown if visible is set to false on an primitive with
399 * id==0
400 */
401 public void setVisible(boolean visible) throws IllegalStateException{
402 if (isNew() && visible == false)
403 throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
404 if (visible) {
405 flags |= FLAG_VISIBLE;
406 } else {
407 flags &= ~FLAG_VISIBLE;
408 }
409 }
410
411 /**
412 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
413 * if this primitive is incomplete.
414 *
415 * @see #setVersion(int)
416 */
417 public long getVersion() {
418 return version;
419 }
420
421 /**
422 * Replies the id of this primitive.
423 *
424 * @return the id of this primitive.
425 */
426 public long getId() {
427 return id >= 0?id:0;
428 }
429
430 /**
431 *
432 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
433 */
434 public long getUniqueId() {
435 return id;
436 }
437
438 /**
439 *
440 * @return True if primitive is new (not yet uploaded the server, id <= 0)
441 */
442 public boolean isNew() {
443 return id <= 0;
444 }
445
446 /**
447 * Sets the id and the version of this primitive if it is known to the OSM API.
448 *
449 * Since we know the id and its version it can't be incomplete anymore. incomplete
450 * is set to false.
451 *
452 * @param id the id. > 0 required
453 * @param version the version > 0 required
454 * @throws IllegalArgumentException thrown if id <= 0
455 * @throws IllegalArgumentException thrown if version <= 0
456 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
457 */
458 public void setOsmId(long id, int version) {
459 if (id <= 0)
460 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
461 if (version <= 0)
462 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
463 if (dataSet != null && id != this.id) {
464 DataSet datasetCopy = dataSet;
465 // Reindex primitive
466 datasetCopy.removePrimitive(this);
467 this.id = id;
468 datasetCopy.addPrimitive(this);
469 }
470 this.id = id;
471 this.version = version;
472 this.setIncomplete(false);
473 }
474
475 /**
476 * Clears the id and version known to the OSM API. The id and the version is set to 0.
477 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
478 * of calling this method.
479 *
480 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
481 *
482 * @throws DataIntegrityProblemException If primitive was already added to the dataset
483 */
484 public void clearOsmId() {
485 if (dataSet != null)
486 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
487 this.id = generateUniqueId();
488 this.version = 0;
489 this.changesetId = 0; // reset changeset id on a new object
490 this.setIncomplete(false);
491 }
492
493 public void setTimestamp(Date timestamp) {
494 this.timestamp = (int)(timestamp.getTime() / 1000);
495 }
496
497 /**
498 * Time of last modification to this object. This is not set by JOSM but
499 * read from the server and delivered back to the server unmodified. It is
500 * used to check against edit conflicts.
501 *
502 */
503 public Date getTimestamp() {
504 return new Date(timestamp * 1000l);
505 }
506
507 public boolean isTimestampEmpty() {
508 return timestamp == 0;
509 }
510
511 /**
512 * If set to true, this object is highlighted. Currently this is only used to
513 * show which ways/nodes will connect
514 */
515 public volatile boolean highlighted = false;
516
517 private int timestamp;
518
519 private static volatile Collection<String> uninteresting = null;
520 /**
521 * Contains a list of "uninteresting" keys that do not make an object
522 * "tagged".
523 * Initialized by checkTagged()
524 */
525 public static Collection<String> getUninterestingKeys() {
526 if (uninteresting == null) {
527 uninteresting = Main.pref.getCollection("tags.uninteresting",
528 Arrays.asList(new String[]{"source","note","comment","converted_by","created_by"}));
529 }
530 return uninteresting;
531 }
532
533 private static volatile Match directionKeys = null;
534
535 /**
536 * Contains a list of direction-dependent keys that make an object
537 * direction dependent.
538 * Initialized by checkDirectionTagged()
539 */
540 public static void initDirectionKeys() {
541 if(directionKeys == null) {
542
543 // Legacy support - convert list of keys to search pattern
544 if (Main.pref.isCollection("tags.direction", false)) {
545 System.out.println("Collection of keys in tags.direction is no longer supported, value will converted to search pattern");
546 Collection<String> keys = Main.pref.getCollection("tags.direction", null);
547 StringBuilder builder = new StringBuilder();
548 for (String key:keys) {
549 builder.append(key);
550 builder.append("=* | ");
551 }
552 builder.delete(builder.length() - 3, builder.length());
553 Main.pref.put("tags.direction", builder.toString());
554 }
555
556 String defaultValue = "oneway=* | incline=* | incline_steep=* | aerialway=*";
557 try {
558 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", defaultValue), false, false);
559 } catch (ParseError e) {
560 System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
561
562 try {
563 directionKeys = SearchCompiler.compile(defaultValue, false, false);
564 } catch (ParseError e2) {
565 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
566 }
567 }
568 }
569 }
570
571 /**
572 * Replies a list of direction-dependent keys that make an object
573 * direction dependent.
574 *
575 * @return a list of direction-dependent keys that make an object
576 * direction dependent.
577 */
578 public static Collection<String> getDirectionKeys() {
579 return Main.pref.getCollection("tags.direction",
580 Arrays.asList("oneway","incline","incline_steep","aerialway"));
581 }
582
583 /**
584 * Implementation of the visitor scheme. Subclasses have to call the correct
585 * visitor function.
586 * @param visitor The visitor from which the visit() function must be called.
587 */
588 abstract public void visit(Visitor visitor);
589
590 /**
591 * Sets whether this primitive is deleted or not.
592 *
593 * Also marks this primitive as modified if deleted is true.
594 *
595 * @param deleted true, if this primitive is deleted; false, otherwise
596 */
597 public void setDeleted(boolean deleted) {
598 if (deleted) {
599 flags |= FLAG_DELETED;
600 } else {
601 flags &= ~FLAG_DELETED;
602 }
603 setModified(deleted);
604 if (dataSet != null) {
605 if (deleted) {
606 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
607 } else {
608 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
609 }
610 }
611 }
612
613 /**
614 * Replies the user who has last touched this object. May be null.
615 *
616 * @return the user who has last touched this object. May be null.
617 */
618 public User getUser() {
619 return user;
620 }
621
622 /**
623 * Sets the user who has last touched this object.
624 *
625 * @param user the user
626 */
627 public void setUser(User user) {
628 this.user = user;
629 }
630
631 /**
632 * Replies the id of the changeset this primitive was last uploaded to.
633 * 0 if this primitive wasn't uploaded to a changeset yet or if the
634 * changeset isn't known.
635 *
636 * @return the id of the changeset this primitive was last uploaded to.
637 */
638 public int getChangesetId() {
639 return changesetId;
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(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
655 if (isNew() && changesetId > 0)
656 throw new IllegalStateException(tr("Cannot 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 if (id <=0) {
1006 // reset version and changeset id
1007 version = 0;
1008 changesetId = 0;
1009 }
1010 timestamp = other.timestamp;
1011 if (id > 0) {
1012 version = other.version;
1013 }
1014 setIncomplete(other.isIncomplete());
1015 flags = other.flags;
1016 user= other.user;
1017 if (id > 0 && other.changesetId > 0) {
1018 // #4208: sometimes we cloned from other with id < 0 *and*
1019 // an assigned changeset id. Don't know why yet. For primitives
1020 // with id < 0 we don't propagate the changeset id any more.
1021 //
1022 setChangesetId(other.changesetId);
1023 }
1024 clearCached();
1025 }
1026
1027 /**
1028 * Merges the technical and semantical attributes from <code>other</code> onto this.
1029 *
1030 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1031 * have an assigend OSM id, the IDs have to be the same.
1032 *
1033 * @param other the other primitive. Must not be null.
1034 * @throws IllegalArgumentException thrown if other is null.
1035 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
1036 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
1037 */
1038 public void mergeFrom(OsmPrimitive other) {
1039 CheckParameterUtil.ensureParameterNotNull(other, "other");
1040 if (other.isNew() ^ isNew())
1041 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
1042 if (! other.isNew() && other.getId() != id)
1043 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1044
1045 setKeys(other.getKeys());
1046 timestamp = other.timestamp;
1047 version = other.version;
1048 setIncomplete(other.isIncomplete());
1049 flags = other.flags;
1050 user= other.user;
1051 changesetId = other.changesetId;
1052 }
1053
1054 /**
1055 * Replies true if this primitive and other are equal with respect to their
1056 * semantic attributes.
1057 * <ol>
1058 * <li>equal id</ol>
1059 * <li>both are complete or both are incomplete</li>
1060 * <li>both have the same tags</li>
1061 * </ol>
1062 * @param other
1063 * @return true if this primitive and other are equal with respect to their
1064 * semantic attributes.
1065 */
1066 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1067 if (!isNew() && id != other.id)
1068 return false;
1069 if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
1070 return false;
1071 // can't do an equals check on the internal keys array because it is not ordered
1072 //
1073 return hasSameTags(other);
1074 }
1075
1076 /**
1077 * Replies true if this primitive and other are equal with respect to their
1078 * technical attributes. The attributes:
1079 * <ol>
1080 * <li>deleted</ol>
1081 * <li>modified</ol>
1082 * <li>timestamp</ol>
1083 * <li>version</ol>
1084 * <li>visible</ol>
1085 * <li>user</ol>
1086 * </ol>
1087 * have to be equal
1088 * @param other the other primitive
1089 * @return true if this primitive and other are equal with respect to their
1090 * technical attributes
1091 */
1092 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1093 if (other == null) return false;
1094
1095 return
1096 isDeleted() == other.isDeleted()
1097 && isModified() == other.isModified()
1098 && timestamp == other.timestamp
1099 && version == other.version
1100 && isVisible() == other.isVisible()
1101 && (user == null ? other.user==null : user==other.user)
1102 && changesetId == other.changesetId;
1103 }
1104
1105 private void updateTagged() {
1106 getUninterestingKeys();
1107 if (keys != null) {
1108 for (Entry<String,String> e : getKeys().entrySet()) {
1109 if (!uninteresting.contains(e.getKey())) {
1110 flags |= FLAG_TAGGED;
1111 return;
1112 }
1113 }
1114 }
1115 flags &= ~FLAG_TAGGED;
1116 }
1117
1118 /**
1119 * true if this object is considered "tagged". To be "tagged", an object
1120 * must have one or more "interesting" tags. "created_by" and "source"
1121 * are typically considered "uninteresting" and do not make an object
1122 * "tagged".
1123 */
1124 public boolean isTagged() {
1125 return (flags & FLAG_TAGGED) != 0;
1126 }
1127
1128 private void updateHasDirectionKeys() {
1129 initDirectionKeys();
1130 if (directionKeys.match(this)) {
1131 flags |= FLAG_HAS_DIRECTIONS;
1132 } else {
1133 flags &= ~FLAG_HAS_DIRECTIONS;
1134 }
1135 }
1136
1137 /**
1138 * true if this object has direction dependent tags (e.g. oneway)
1139 */
1140 public boolean hasDirectionKeys() {
1141 return (flags & FLAG_HAS_DIRECTIONS) != 0;
1142 }
1143
1144 /**
1145 * Replies the name of this primitive. The default implementation replies the value
1146 * of the tag <tt>name</tt> or null, if this tag is not present.
1147 *
1148 * @return the name of this primitive
1149 */
1150 public String getName() {
1151 if (get("name") != null)
1152 return get("name");
1153 return null;
1154 }
1155
1156 /**
1157 * Replies the a localized name for this primitive given by the value of the tags (in this order)
1158 * <ul>
1159 * <li>name:lang_COUNTRY_Variant of the current locale</li>
1160 * <li>name:lang_COUNTRY of the current locale</li>
1161 * <li>name:lang of the current locale</li>
1162 * <li>name of the current locale</li>
1163 * </ul>
1164 *
1165 * null, if no such tag exists
1166 *
1167 * @return the name of this primitive
1168 */
1169 public String getLocalName() {
1170 String key = "name:" + Locale.getDefault().toString();
1171 if (get(key) != null)
1172 return get(key);
1173 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
1174 if (get(key) != null)
1175 return get(key);
1176 key = "name:" + Locale.getDefault().getLanguage();
1177 if (get(key) != null)
1178 return get(key);
1179 return getName();
1180 }
1181
1182 /**
1183 * Replies the display name of a primitive formatted by <code>formatter</code>
1184 *
1185 * @return the display name
1186 */
1187 public abstract String getDisplayName(NameFormatter formatter);
1188
1189 /**
1190 * Loads (clone) this primitive from provided PrimitiveData
1191 * @param data
1192 */
1193 public void load(PrimitiveData data) {
1194 setKeys(data.getKeys());
1195 timestamp = data.getTimestamp();
1196 user = data.getUser();
1197 setChangesetId(data.getChangesetId());
1198 setDeleted(data.isDeleted());
1199 setModified(data.isModified());
1200 setVisible(data.isVisible());
1201 setIncomplete(data.isIncomplete());
1202 }
1203
1204 /**
1205 * Save parameters of this primitive to the transport object
1206 * @return
1207 */
1208 public abstract PrimitiveData save();
1209
1210 protected void saveCommonAttributes(PrimitiveData data) {
1211 data.setId(id);
1212 data.getKeys().clear();
1213 data.getKeys().putAll(getKeys());
1214 data.setTimestamp(timestamp);
1215 data.setUser(user);
1216 data.setDeleted(isDeleted());
1217 data.setModified(isModified());
1218 data.setVisible(isVisible());
1219 data.setIncomplete(isIncomplete());
1220 data.setChangesetId(changesetId);
1221 }
1222
1223 protected String getFlagsAsString() {
1224
1225 StringBuilder builder = new StringBuilder();
1226
1227 if (isIncomplete()) {
1228 builder.append("I");
1229 }
1230 if (isModified()) {
1231 builder.append("M");
1232 }
1233 if (isVisible()) {
1234 builder.append("V");
1235 }
1236 if (isDeleted()) {
1237 builder.append("D");
1238 }
1239 if (isFiltered()) {
1240 builder.append("f");
1241 }
1242
1243 if (isDeleted()) {
1244 builder.append("d");
1245 }
1246
1247 return builder.toString();
1248 }
1249
1250 public abstract BBox getBBox();
1251
1252 /**
1253 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1254 */
1255 public abstract void updatePosition();
1256
1257 /**
1258 * Replies the unique primitive id for this primitive
1259 *
1260 * @return the unique primitive id for this primitive
1261 */
1262 public PrimitiveId getPrimitiveId() {
1263 return new SimplePrimitiveId(getUniqueId(), getType());
1264 }
1265
1266 private void setIncomplete(boolean incomplete) {
1267 if (dataSet != null && incomplete != this.incomplete) {
1268 if (incomplete) {
1269 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
1270 } else {
1271 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
1272 }
1273 }
1274 this.incomplete = incomplete;
1275 }
1276
1277 public boolean isIncomplete() {
1278 return incomplete;
1279 }
1280
1281 public boolean isSelected() {
1282 return dataSet != null && dataSet.isSelected(this);
1283 }
1284}
Note: See TracBrowser for help on using the repository browser.