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

Last change on this file since 2890 was 2890, checked in by bastiK, 14 years ago

Reverse Arrows for 'oneway=-1' and similar (see #2387).
More efficient calculation of arrow geometry.
Test file for Arrow direction added.

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