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

Last change on this file since 3336 was 3336, checked in by stoecker, 14 years ago

#close #5135 - allow undeleting without recreating object - patch by Upliner

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