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

Last change on this file since 2452 was 2452, checked in by jttt, 14 years ago
  • removed mappaintVisibleCode from OsmPrimitive (should not be necessary now when MappaintVisitor use QuadBuckets.search())
  • moved OsmPrimitive.errors to Dataset (it was waste of memory)
  • Property svn:eol-style set to native
File size: 32.5 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.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Date;
11import java.util.HashMap;
12import java.util.LinkedHashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.concurrent.atomic.AtomicLong;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.data.osm.visitor.Visitor;
22import org.openstreetmap.josm.gui.mappaint.ElemStyle;
23
24
25/**
26 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
27 * and updated within the OSM-Server.
28 *
29 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
30 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
31 * by the server environment and not an extendible data stuff.
32 *
33 * @author imi
34 */
35abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged, PrimitiveId {
36
37 private static final AtomicLong idCounter = new AtomicLong(0);
38
39 static long generateUniqueId() {
40 return idCounter.decrementAndGet();
41 }
42
43 private static final int FLAG_MODIFIED = 1 << 0;
44 private static final int FLAG_VISIBLE = 1 << 1;
45 private static final int FLAG_DISABLED = 1 << 2;
46 private static final int FLAG_DELETED = 1 << 3;
47 private static final int FLAG_FILTERED = 1 << 4;
48 private static final int FLAG_HAS_DIRECTIONS = 1 << 5;
49 private static final int FLAG_TAGGED = 1 << 6;
50
51 /**
52 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
53 * another collection of {@see OsmPrimitive}s. The result collection is a list.
54 *
55 * If <code>list</code> is null, replies an empty list.
56 *
57 * @param <T>
58 * @param list the original list
59 * @param type the type to filter for
60 * @return the sub-list of OSM primitives of type <code>type</code>
61 */
62 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
63 if (list == null) return Collections.emptyList();
64 List<T> ret = new LinkedList<T>();
65 for(OsmPrimitive p: list) {
66 if (type.isInstance(p)) {
67 ret.add(type.cast(p));
68 }
69 }
70 return ret;
71 }
72
73 /**
74 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
75 * another collection of {@see OsmPrimitive}s. The result collection is a set.
76 *
77 * If <code>list</code> is null, replies an empty set.
78 *
79 * @param <T>
80 * @param list the original collection
81 * @param type the type to filter for
82 * @return the sub-set of OSM primitives of type <code>type</code>
83 */
84 static public <T extends OsmPrimitive> LinkedHashSet<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
85 LinkedHashSet<T> ret = new LinkedHashSet<T>();
86 if (set != null) {
87 for(OsmPrimitive p: set) {
88 if (type.isInstance(p)) {
89 ret.add(type.cast(p));
90 }
91 }
92 }
93 return ret;
94 }
95
96
97 /* mappaint data */
98 public ElemStyle mappaintStyle = null;
99 public Integer mappaintDrawnCode = 0;
100
101 public void putError(String text, boolean isError)
102 {
103 checkDataset();
104 List<String> errors = dataSet.getErrors(this);
105 if (errors == null) {
106 errors = new ArrayList<String>();
107 }
108 String s = isError ? tr("Error: {0}", text) : tr("Warning: {0}", text);
109 errors.add(s);
110 dataSet.setErrors(this, errors);
111 }
112 public void clearErrors()
113 {
114 if (dataSet != null) {
115 dataSet.setErrors(this, null);
116 }
117 }
118
119 public List<String> getErrors() {
120 if (dataSet == null)
121 return null;
122 else
123 return dataSet.getErrors(this);
124 }
125 /* This should not be called from outside. Fixing the UI to add relevant
126 get/set functions calling this implicitely is preferred, so we can have
127 transparent cache handling in the future. */
128 protected void clearCached()
129 {
130 mappaintDrawnCode = 0;
131 mappaintStyle = null;
132 }
133 /* end of mappaint data */
134
135 /**
136 * Unique identifier in OSM. This is used to identify objects on the server.
137 * An id of 0 means an unknown id. The object has not been uploaded yet to
138 * know what id it will get.
139 *
140 */
141 private long id = 0;
142
143 private DataSet dataSet;
144
145 /**
146 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
147 * @param dataSet
148 */
149 void setDataset(DataSet dataSet) {
150 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
151 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
152 this.dataSet = dataSet;
153 }
154
155 /**
156 *
157 * @return DataSet this primitive is part of.
158 */
159 public DataSet getDataSet() {
160 return dataSet;
161 }
162
163 /**
164 * Throws exception if primitive is not part of the dataset
165 */
166 public void checkDataset() {
167 if (dataSet == null)
168 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
169 }
170
171 private volatile byte flags = FLAG_VISIBLE; // visible per default
172
173 /**
174 * User that last modified this primitive, as specified by the server.
175 * Never changed by JOSM.
176 */
177 private User user = null;
178
179 /**
180 * If set to true, this object is incomplete, which means only the id
181 * and type is known (type is the objects instance class)
182 */
183 public boolean incomplete = false;
184
185 /**
186 * Contains the version number as returned by the API. Needed to
187 * ensure update consistency
188 */
189 private int version = 0;
190
191 /**
192 * Creates a new primitive for the given id. If the id > 0, the primitive is marked
193 * as incomplete.
194 *
195 * @param id the id. > 0 required
196 * @param allowNegativeId Allows to set negative id. For internal use
197 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
198 */
199 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
200 if (allowNegativeId) {
201 this.id = id;
202 } else {
203 if (id < 0)
204 throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
205 else if (id == 0) {
206 this.id = generateUniqueId();
207 } else {
208 this.id = id;
209 }
210
211 }
212 this.version = 0;
213 this.incomplete = id > 0;
214 }
215
216 protected OsmPrimitive(PrimitiveData data) {
217 version = data.getVersion();
218 id = data.getId();
219 }
220
221 /* ------------------------------------------------------------------------------------ */
222 /* accessors */
223 /* ------------------------------------------------------------------------------------ */
224 /**
225 * Sets whether this primitive is disabled or not.
226 *
227 * @param disabled true, if this primitive is disabled; false, otherwise
228 */
229 void setDisabled(boolean disabled) {
230 if (disabled) {
231 flags |= FLAG_DISABLED;
232 } else {
233 flags &= ~FLAG_DISABLED;
234 }
235
236 }
237
238 /**
239 * Replies true, if this primitive is disabled.
240 *
241 * @return true, if this primitive is disabled
242 */
243 public boolean isDisabled() {
244 return (flags & FLAG_DISABLED) != 0;
245 }
246 /**
247 * Sets whether this primitive is filtered out or not.
248 *
249 * @param filtered true, if this primitive is filtered out; false, otherwise
250 */
251 public void setFiltered(boolean filtered) {
252 if (filtered) {
253 flags |= FLAG_FILTERED;
254 } else {
255 flags &= ~FLAG_FILTERED;
256 }
257 }
258 /**
259 * Replies true, if this primitive is filtered out.
260 *
261 * @return true, if this primitive is filtered out
262 */
263 public boolean isFiltered() {
264 return (flags & FLAG_FILTERED) != 0;
265 }
266
267 /**
268 * Marks this primitive as being modified.
269 *
270 * @param modified true, if this primitive is to be modified
271 */
272 public void setModified(boolean modified) {
273 if (modified) {
274 flags |= FLAG_MODIFIED;
275 } else {
276 flags &= ~FLAG_MODIFIED;
277 }
278 }
279
280 /**
281 * Replies <code>true</code> if the object has been modified since it was loaded from
282 * the server. In this case, on next upload, this object will be updated.
283 *
284 * Deleted objects are deleted from the server. If the objects are added (id=0),
285 * the modified is ignored and the object is added to the server.
286 *
287 * @return <code>true</code> if the object has been modified since it was loaded from
288 * the server
289 */
290 public boolean isModified() {
291 return (flags & FLAG_MODIFIED) != 0;
292 }
293
294 /**
295 * Replies <code>true</code>, if the object has been deleted.
296 *
297 * @return <code>true</code>, if the object has been deleted.
298 * @see #setDeleted(boolean)
299 */
300 public boolean isDeleted() {
301 return (flags & FLAG_DELETED) != 0;
302 }
303
304 /**
305 * Replies <code>true</code>, if the object is usable.
306 *
307 * @return <code>true</code>, if the object is unusable.
308 * @see #delete(boolean)
309 */
310 public boolean isUsable() {
311 return !isDeleted() && !incomplete && !isDisabled();
312 }
313
314 /**
315 * Replies true if this primitive is either unknown to the server (i.e. its id
316 * is 0) or it is known to the server and it hasn't be deleted on the server.
317 * Replies false, if this primitive is known on the server and has been deleted
318 * on the server.
319 *
320 * @see #setVisible(boolean)
321 */
322 public boolean isVisible() {
323 return (flags & FLAG_VISIBLE) != 0;
324 }
325
326 /**
327 * Sets whether this primitive is visible, i.e. whether it is known on the server
328 * and not deleted on the server.
329 *
330 * @see #isVisible()
331 * @throws IllegalStateException thrown if visible is set to false on an primitive with
332 * id==0
333 */
334 public void setVisible(boolean visible) throws IllegalStateException{
335 if (isNew() && visible == false)
336 throw new IllegalStateException(tr("A primitive with ID = 0 can't be invisible."));
337 if (visible) {
338 flags |= FLAG_VISIBLE;
339 } else {
340 flags &= ~FLAG_VISIBLE;
341 }
342 }
343
344 /**
345 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
346 * if this primitive is incomplete.
347 *
348 * @see #setVersion(int)
349 */
350 public long getVersion() {
351 return version;
352 }
353
354 /**
355 * Replies the id of this primitive.
356 *
357 * @return the id of this primitive.
358 */
359 public long getId() {
360 return id >= 0?id:0;
361 }
362
363 /**
364 *
365 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
366 */
367 public long getUniqueId() {
368 return id;
369 }
370
371 /**
372 *
373 * @return True if primitive is new (not yet uploaded the server, id <= 0)
374 */
375 public boolean isNew() {
376 return id <= 0;
377 }
378
379 /**
380 * Sets the id and the version of this primitive if it is known to the OSM API.
381 *
382 * Since we know the id and its version it can't be incomplete anymore. incomplete
383 * is set to false.
384 *
385 * @param id the id. > 0 required
386 * @param version the version > 0 required
387 * @throws IllegalArgumentException thrown if id <= 0
388 * @throws IllegalArgumentException thrown if version <= 0
389 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
390 */
391 public void setOsmId(long id, int version) {
392 if (id <= 0)
393 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
394 if (version <= 0)
395 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
396 if (dataSet != null && id != this.id) {
397 DataSet datasetCopy = dataSet;
398 // Reindex primitive
399 datasetCopy.removePrimitive(this);
400 this.id = id;
401 datasetCopy.addPrimitive(this);
402 }
403 this.id = id;
404 this.version = version;
405 this.incomplete = false;
406 }
407
408 /**
409 * Clears the id and version known to the OSM API. The id and the version is set to 0.
410 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
411 * of calling this method.
412 *
413 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
414 *
415 * @throws DataIntegrityProblemException If primitive was already added to the dataset
416 */
417 public void clearOsmId() {
418 if (dataSet != null)
419 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
420 this.id = generateUniqueId();
421 this.version = 0;
422 this.incomplete = false;
423 }
424
425 public void setTimestamp(Date timestamp) {
426 this.timestamp = (int)(timestamp.getTime() / 1000);
427 }
428
429 /**
430 * Time of last modification to this object. This is not set by JOSM but
431 * read from the server and delivered back to the server unmodified. It is
432 * used to check against edit conflicts.
433 *
434 */
435 public Date getTimestamp() {
436 return new Date(timestamp * 1000l);
437 }
438
439 public boolean isTimestampEmpty() {
440 return timestamp == 0;
441 }
442
443 /**
444 * If set to true, this object is highlighted. Currently this is only used to
445 * show which ways/nodes will connect
446 */
447 public volatile boolean highlighted = false;
448
449 private int timestamp;
450
451 private static Collection<String> uninteresting = null;
452 /**
453 * Contains a list of "uninteresting" keys that do not make an object
454 * "tagged".
455 * Initialized by checkTagged()
456 */
457 public static Collection<String> getUninterestingKeys() {
458 if (uninteresting == null) {
459 uninteresting = Main.pref.getCollection("tags.uninteresting",
460 Arrays.asList(new String[]{"source","note","comment","converted_by","created_by"}));
461 }
462 return uninteresting;
463 }
464
465
466 private static Collection<String> directionKeys = null;
467
468 /**
469 * Contains a list of direction-dependent keys that make an object
470 * direction dependent.
471 * Initialized by checkDirectionTagged()
472 */
473 public static Collection<String> getDirectionKeys() {
474 if(directionKeys == null) {
475 directionKeys = Main.pref.getCollection("tags.direction",
476 Arrays.asList("oneway","incline","incline_steep","aerialway"));
477 }
478 return directionKeys;
479 }
480
481 /**
482 * Implementation of the visitor scheme. Subclasses have to call the correct
483 * visitor function.
484 * @param visitor The visitor from which the visit() function must be called.
485 */
486 abstract public void visit(Visitor visitor);
487
488 /**
489 * Sets whether this primitive is deleted or not.
490 *
491 * Also marks this primitive as modified if deleted is true.
492 *
493 * @param deleted true, if this primitive is deleted; false, otherwise
494 */
495 public void setDeleted(boolean deleted) {
496 if (deleted) {
497 flags |= FLAG_DELETED;
498 } else {
499 flags &= ~FLAG_DELETED;
500 }
501 setModified(deleted);
502 if (dataSet != null) {
503 if (deleted) {
504 dataSet.firePrimitivesRemoved(Collections.singleton(this));
505 } else {
506 dataSet.firePrimitivesAdded(Collections.singleton(this));
507 }
508 }
509 }
510
511 /**
512 * Replies the user who has last touched this object. May be null.
513 *
514 * @return the user who has last touched this object. May be null.
515 */
516 public User getUser() {
517 return user;
518 }
519
520 /**
521 * Sets the user who has last touched this object.
522 *
523 * @param user the user
524 */
525 public void setUser(User user) {
526 this.user = user;
527 }
528
529 /**
530 * Equal, if the id (and class) is equal.
531 *
532 * An primitive is equal to its incomplete counter part.
533 */
534 @Override public boolean equals(Object obj) {
535 if (obj instanceof OsmPrimitive)
536 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
537 return false;
538 }
539
540 /**
541 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
542 *
543 * An primitive has the same hashcode as its incomplete counterpart.
544 */
545 @Override public final int hashCode() {
546 final int[] ret = new int[1];
547 Visitor v = new Visitor(){
548 public void visit(Node n) { ret[0] = 0; }
549 public void visit(Way w) { ret[0] = 1; }
550 public void visit(Relation e) { ret[0] = 2; }
551 public void visit(Changeset cs) { ret[0] = 3; }
552 };
553 visit(v);
554 return (int)(id<<2)+ret[0];
555 }
556
557 /*------------
558 * Keys handling
559 ------------*/
560
561 /**
562 * The key/value list for this primitive.
563 *
564 */
565 private Map<String, String> keys;
566
567 /**
568 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
569 *
570 * @return Keys of this primitive. Changes made in returned map are not mapped
571 * back to the primitive, use setKeys() to modify the keys
572 * @since 1924
573 */
574 public Map<String, String> getKeys() {
575 // TODO More effective map
576 // fix for #3218
577 if (keys == null)
578 return new HashMap<String, String>();
579 else
580 return new HashMap<String, String>(keys);
581 }
582
583 /**
584 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
585 * If <code>keys</code> is null removes all existing key/value pairs.
586 *
587 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
588 * @since 1924
589 */
590 public void setKeys(Map<String, String> keys) {
591 if (keys == null) {
592 this.keys = null;
593 } else {
594 this.keys = new HashMap<String, String>(keys);
595 }
596 keysChangedImpl();
597 }
598
599 /**
600 * Set the given value to the given key. If key is null, does nothing. If value is null,
601 * removes the key and behaves like {@see #remove(String)}.
602 *
603 * @param key The key, for which the value is to be set. Can be null, does nothing in this case.
604 * @param value The value for the key. If null, removes the respective key/value pair.
605 *
606 * @see #remove(String)
607 */
608 public final void put(String key, String value) {
609 if (key == null)
610 return;
611 else if (value == null) {
612 remove(key);
613 } else {
614 if (keys == null) {
615 keys = new HashMap<String, String>();
616 }
617 keys.put(key, value);
618 }
619 mappaintStyle = null;
620 keysChangedImpl();
621 }
622 /**
623 * Remove the given key from the list
624 *
625 * @param key the key to be removed. Ignored, if key is null.
626 */
627 public final void remove(String key) {
628 if (keys != null) {
629 keys.remove(key);
630 if (keys.isEmpty()) {
631 keys = null;
632 }
633 }
634 mappaintStyle = null;
635 keysChangedImpl();
636 }
637
638 /**
639 * Removes all keys from this primitive.
640 *
641 * @since 1843
642 */
643 public final void removeAll() {
644 keys = null;
645 mappaintStyle = null;
646 keysChangedImpl();
647 }
648
649 /**
650 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
651 * Replies null, if there is no value for the given key.
652 *
653 * @param key the key. Can be null, replies null in this case.
654 * @return the value for key <code>key</code>.
655 */
656 public final String get(String key) {
657 if (key == null) return null;
658 return keys == null ? null : keys.get(key);
659 }
660
661 public final Collection<Entry<String, String>> entrySet() {
662 if (keys == null)
663 return Collections.emptyList();
664 return keys.entrySet();
665 }
666
667 public final Collection<String> keySet() {
668 if (keys == null)
669 return Collections.emptyList();
670 return keys.keySet();
671 }
672
673
674 /*------------
675 * Referrers
676 ------------*/
677
678 private Object referrers;
679
680
681 /**
682 * Add new referrer. If referrer is already included then no action is taken
683 * @param referrer
684 */
685 protected void addReferrer(OsmPrimitive referrer) {
686 // Based on methods from josm-ng
687 if (referrers == null) {
688 referrers = referrer;
689 } else if (referrers instanceof OsmPrimitive) {
690 if (referrers != referrer) {
691 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
692 }
693 } else {
694 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
695 if (primitive == referrer)
696 return;
697 }
698 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
699 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
700 System.arraycopy(orig, 0, bigger, 0, orig.length);
701 bigger[orig.length] = referrer;
702 referrers = bigger;
703 }
704 }
705
706 /**
707 * Remove referrer. No action is taken if referrer is not registered
708 * @param referrer
709 */
710 protected void removeReferrer(OsmPrimitive referrer) {
711 // Based on methods from josm-ng
712 if (referrers instanceof OsmPrimitive) {
713 if (referrers == referrer) {
714 referrers = null;
715 }
716 } else if (referrers instanceof OsmPrimitive[]) {
717 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
718 int idx = -1;
719 for (int i=0; i<orig.length; i++) {
720 if (orig[i] == referrer) {
721 idx = i;
722 break;
723 }
724 }
725 if (idx == -1)
726 return;
727
728 if (orig.length == 2) {
729 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
730 } else { // downsize the array
731 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
732 System.arraycopy(orig, 0, smaller, 0, idx);
733 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
734 referrers = smaller;
735 }
736 }
737 }
738 /**
739 * Find primitives that reference this primitive. Returns only primitives that are included in the same
740 * dataset as this primitive. <br>
741 *
742 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
743 * not return wnew because it's not part of the dataset <br>
744 *
745 * <code>Way wnew = new Way(existingWay)</code>
746 *
747 * @return a collection of all primitives that reference this primitive.
748 */
749
750 public final List<OsmPrimitive> getReferrers() {
751 checkDataset();
752 // Method copied from OsmPrimitive in josm-ng
753 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
754 // when way is cloned
755 if (referrers == null)
756 return Collections.emptyList();
757
758 if (referrers instanceof OsmPrimitive) {
759 if (((OsmPrimitive)referrers).dataSet == dataSet)
760 return Collections.singletonList((OsmPrimitive)referrers);
761 else
762 return Collections.emptyList();
763 }
764
765 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
766 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
767 if (dataSet == o.dataSet) {
768 result.add(o);
769 }
770 }
771
772 return result;
773 }
774
775
776 /**
777 * Replies true, if the map of key/value pairs of this primitive is not empty.
778 *
779 * @return true, if the map of key/value pairs of this primitive is not empty; false
780 * otherwise
781 *
782 * @since 1843
783 */
784 public final boolean hasKeys() {
785 return keys != null && !keys.isEmpty();
786 }
787
788 private void keysChangedImpl() {
789 updateHasDirectionKeys();
790 updateTagged();
791 if (dataSet != null) {
792 dataSet.fireTagsChanged(this);
793 }
794 }
795
796 /**
797 * Get and write all attributes from the parameter. Does not fire any listener, so
798 * use this only in the data initializing phase
799 */
800 public void cloneFrom(OsmPrimitive osm) {
801 keys = osm.keys == null ? null : new HashMap<String, String>(osm.keys);
802 id = osm.id;
803 timestamp = osm.timestamp;
804 version = osm.version;
805 incomplete = osm.incomplete;
806 flags = osm.flags;
807 user= osm.user;
808 clearCached();
809 }
810
811 /**
812 * Merges the technical and semantical attributes from <code>other</code> onto this.
813 *
814 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
815 * have an assigend OSM id, the IDs have to be the same.
816 *
817 * @param other the other primitive. Must not be null.
818 * @throws IllegalArgumentException thrown if other is null.
819 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
820 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
821 */
822 public void mergeFrom(OsmPrimitive other) {
823 if (other == null)
824 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "other"));
825 if (other.isNew() ^ isNew())
826 throw new DataIntegrityProblemException(tr("Can''t merge because either of the participating primitives is new and the other is not"));
827 if (! other.isNew() && other.getId() != id)
828 throw new DataIntegrityProblemException(tr("Can''t merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
829 keys = other.keys == null ? null : new HashMap<String, String>(other.keys);
830 timestamp = other.timestamp;
831 version = other.version;
832 incomplete = other.incomplete;
833 flags = other.flags;
834 user= other.user;
835 }
836
837 /**
838 * Replies true if this primitive and other are equal with respect to their
839 * semantic attributes.
840 * <ol>
841 * <li>equal id</ol>
842 * <li>both are complete or both are incomplete</li>
843 * <li>both have the same tags</li>
844 * </ol>
845 * @param other
846 * @return true if this primitive and other are equal with respect to their
847 * semantic attributes.
848 */
849 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
850 if (!isNew() && id != other.id)
851 return false;
852 if (incomplete && ! other.incomplete || !incomplete && other.incomplete)
853 return false;
854 return (keys == null ? other.keys==null : keys.equals(other.keys));
855 }
856
857 /**
858 * Replies true if this primitive and other are equal with respect to their
859 * technical attributes. The attributes:
860 * <ol>
861 * <li>deleted</ol>
862 * <li>modified</ol>
863 * <li>timestamp</ol>
864 * <li>version</ol>
865 * <li>visible</ol>
866 * <li>user</ol>
867 * </ol>
868 * have to be equal
869 * @param other the other primitive
870 * @return true if this primitive and other are equal with respect to their
871 * technical attributes
872 */
873 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
874 if (other == null) return false;
875
876 return
877 isDeleted() == other.isDeleted()
878 && isModified() == other.isModified()
879 && timestamp == other.timestamp
880 && version == other.version
881 && isVisible() == other.isVisible()
882 && (user == null ? other.user==null : user==other.user);
883 }
884
885 private void updateTagged() {
886 getUninterestingKeys();
887 if (keys != null) {
888 for (Entry<String,String> e : keys.entrySet()) {
889 if (!uninteresting.contains(e.getKey())) {
890 flags |= FLAG_TAGGED;
891 return;
892 }
893 }
894 }
895 flags &= ~FLAG_TAGGED;
896 }
897
898 /**
899 * true if this object is considered "tagged". To be "tagged", an object
900 * must have one or more "interesting" tags. "created_by" and "source"
901 * are typically considered "uninteresting" and do not make an object
902 * "tagged".
903 */
904 public boolean isTagged() {
905 return (flags & FLAG_TAGGED) != 0;
906 }
907
908 private void updateHasDirectionKeys() {
909 getDirectionKeys();
910 if (keys != null) {
911 for (Entry<String,String> e : keys.entrySet()) {
912 if (directionKeys.contains(e.getKey())) {
913 flags |= FLAG_HAS_DIRECTIONS;
914 return;
915 }
916 }
917 }
918 flags &= ~FLAG_HAS_DIRECTIONS;
919 }
920 /**
921 * true if this object has direction dependent tags (e.g. oneway)
922 */
923 public boolean hasDirectionKeys() {
924 return (flags & FLAG_HAS_DIRECTIONS) != 0;
925 }
926
927
928 /**
929 * Replies the name of this primitive. The default implementation replies the value
930 * of the tag <tt>name</tt> or null, if this tag is not present.
931 *
932 * @return the name of this primitive
933 */
934 public String getName() {
935 if (get("name") != null)
936 return get("name");
937 return null;
938 }
939
940 /**
941 * Replies the a localized name for this primitive given by the value of the tags (in this order)
942 * <ul>
943 * <li>name:lang_COUNTRY_Variant of the current locale</li>
944 * <li>name:lang_COUNTRY of the current locale</li>
945 * <li>name:lang of the current locale</li>
946 * <li>name of the current locale</li>
947 * </ul>
948 *
949 * null, if no such tag exists
950 *
951 * @return the name of this primitive
952 */
953 public String getLocalName() {
954 String key = "name:" + Locale.getDefault().toString();
955 if (get(key) != null)
956 return get(key);
957 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
958 if (get(key) != null)
959 return get(key);
960 key = "name:" + Locale.getDefault().getLanguage();
961 if (get(key) != null)
962 return get(key);
963 return getName();
964 }
965
966 /**
967 * Replies the display name of a primitive formatted by <code>formatter</code>
968 *
969 * @return the display name
970 */
971 public abstract String getDisplayName(NameFormatter formatter);
972
973 /**
974 * Loads (clone) this primitive from provided PrimitiveData
975 * @param data
976 */
977 public void load(PrimitiveData data) {
978 setKeys(data.getKeys());
979 timestamp = data.getTimestamp();
980 user = data.getUser();
981 setDeleted(data.isDeleted());
982 setModified(data.isModified());
983 setVisible(data.isVisible());
984 }
985
986 /**
987 * Save parameters of this primitive to the transport object
988 * @return
989 */
990 public abstract PrimitiveData save();
991
992 protected void saveCommonAttributes(PrimitiveData data) {
993 data.setId(id);
994 data.getKeys().clear();
995 data.getKeys().putAll(getKeys());
996 data.setTimestamp(timestamp);
997 data.setUser(user);
998 data.setDeleted(isDeleted());
999 data.setModified(isModified());
1000 data.setVisible(isVisible());
1001 }
1002
1003 protected String getFlagsAsString() {
1004
1005 StringBuilder builder = new StringBuilder();
1006
1007 if (isModified()) {
1008 builder.append("M");
1009 }
1010 if (isVisible()) {
1011 builder.append("V");
1012 }
1013 if (isDeleted()) {
1014 builder.append("D");
1015 }
1016 if (isFiltered()) {
1017 builder.append("f");
1018 }
1019
1020 if (isDeleted()) {
1021 builder.append("d");
1022 }
1023
1024 return builder.toString();
1025 }
1026
1027 public abstract BBox getBBox();
1028
1029 /**
1030 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1031 */
1032 public abstract void updatePosition();
1033
1034 /**
1035 * Replies the unique primitive id for this primitive
1036 *
1037 * @return the unique primitive id for this primitive
1038 */
1039 public PrimitiveId getPrimitiveId() {
1040 return new SimplePrimitiveId(getUniqueId(), getType());
1041 }
1042}
1043
Note: See TracBrowser for help on using the repository browser.