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

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

Reuse offscreenBuffer if layers didn't change

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