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

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

Do not allow zero length arrays for OsmPrimitive.keys

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