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

Last change on this file since 3657 was 3657, checked in by bastiK, 13 years ago

fixed #5636 - Can't fully PURGE some boundary:administrative ways (added new uninteresting key)

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