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

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

use IPrimitive to make upload code work for both OsmPrimitive and PrimitiveData

  • Property svn:eol-style set to native
File size: 37.0 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.HashSet;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.actions.search.SearchCompiler;
21import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
22import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
23import org.openstreetmap.josm.data.osm.visitor.Visitor;
24import org.openstreetmap.josm.gui.mappaint.StyleCache;
25import org.openstreetmap.josm.tools.CheckParameterUtil;
26import org.openstreetmap.josm.tools.Predicate;
27
28/**
29 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
30 * and updated within the OSM-Server.
31 *
32 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
33 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
34 * by the server environment and not an extendible data stuff.
35 *
36 * @author imi
37 */
38abstract public class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive> {
39
40 /**
41 * An object can be disabled by the filter mechanism.
42 * Then it will show in a shade of gray on the map or it is completely
43 * hidden from the view.
44 * Disabled objects usually cannot be selected or modified
45 * while the filter is active.
46 */
47 protected static final int FLAG_DISABLED = 1 << 4;
48
49 /**
50 * This flag is only relevant if an object is disabled by the
51 * filter mechanism (i.e. FLAG_DISABLED is set).
52 * Then it indicates, whether it is completely hidden or
53 * just shown in gray color.
54 *
55 * When the primitive is not disabled, this flag should be
56 * unset as well (for efficient access).
57 */
58 protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
59
60 /**
61 * This flag is set if the primitive is a way and
62 * according to the tags, the direction of the way is important.
63 * (e.g. one way street.)
64 */
65 protected static final int FLAG_HAS_DIRECTIONS = 1 << 6;
66
67 /**
68 * If the primitive is tagged.
69 * Some trivial tags like source=* are ignored here.
70 */
71 protected static final int FLAG_TAGGED = 1 << 7;
72
73 /**
74 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
75 * It shows, that direction of the arrows should be reversed.
76 * (E.g. oneway=-1.)
77 */
78 protected static final int FLAG_DIRECTION_REVERSED = 1 << 8;
79
80 /**
81 * When hovering over ways and nodes in add mode, the
82 * "target" objects are visually highlighted. This flag indicates
83 * that the primitive is currently highlighted.
84 */
85 protected static final int FLAG_HIGHLIGHTED = 1 << 9;
86
87 /**
88 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
89 * another collection of {@see OsmPrimitive}s. The result collection is a list.
90 *
91 * If <code>list</code> is null, replies an empty list.
92 *
93 * @param <T>
94 * @param list the original list
95 * @param type the type to filter for
96 * @return the sub-list of OSM primitives of type <code>type</code>
97 */
98 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
99 if (list == null) return Collections.emptyList();
100 List<T> ret = new LinkedList<T>();
101 for(OsmPrimitive p: list) {
102 if (type.isInstance(p)) {
103 ret.add(type.cast(p));
104 }
105 }
106 return ret;
107 }
108
109 /**
110 * Replies the sub-collection of {@see OsmPrimitive}s of type <code>type</code> present in
111 * another collection of {@see OsmPrimitive}s. The result collection is a set.
112 *
113 * If <code>list</code> is null, replies an empty set.
114 *
115 * @param <T>
116 * @param list the original collection
117 * @param type the type to filter for
118 * @return the sub-set of OSM primitives of type <code>type</code>
119 */
120 static public <T extends OsmPrimitive> LinkedHashSet<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
121 LinkedHashSet<T> ret = new LinkedHashSet<T>();
122 if (set != null) {
123 for(OsmPrimitive p: set) {
124 if (type.isInstance(p)) {
125 ret.add(type.cast(p));
126 }
127 }
128 }
129 return ret;
130 }
131
132 /**
133 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
134 *
135 * @param primitives the collection of primitives.
136 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
137 * empty set if primitives is null or if there are no referring primitives
138 */
139 static public Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
140 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
141 if (primitives == null || primitives.isEmpty()) return ret;
142 for (OsmPrimitive p: primitives) {
143 ret.addAll(p.getReferrers());
144 }
145 return ret;
146 }
147
148 /**
149 * Some predicates, that describe conditions on primitives.
150 */
151 public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
152 @Override public boolean evaluate(OsmPrimitive primitive) {
153 return primitive.isUsable();
154 }
155 };
156
157 public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
158 @Override public boolean evaluate(OsmPrimitive primitive) {
159 return primitive.isSelectable();
160 }
161 };
162
163 public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
164 @Override public boolean evaluate(OsmPrimitive primitive) {
165 return !primitive.isDeleted();
166 }
167 };
168
169 public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
170 @Override public boolean evaluate(OsmPrimitive primitive) {
171 return !primitive.isDeleted() && !primitive.isIncomplete();
172 }
173 };
174
175 public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
176 @Override public boolean evaluate(OsmPrimitive primitive) {
177 return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
178 }
179 };
180
181 public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
182 @Override public boolean evaluate(OsmPrimitive primitive) {
183 return primitive.isModified();
184 }
185 };
186
187 public static final Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() {
188 @Override public boolean evaluate(OsmPrimitive primitive) {
189 return primitive.getClass() == Node.class;
190 }
191 };
192
193 public static final Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() {
194 @Override public boolean evaluate(OsmPrimitive primitive) {
195 return primitive.getClass() == Way.class;
196 }
197 };
198
199 public static final Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() {
200 @Override public boolean evaluate(OsmPrimitive primitive) {
201 return primitive.getClass() == Relation.class;
202 }
203 };
204
205 public static final Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() {
206 @Override public boolean evaluate(OsmPrimitive primitive) {
207 return true;
208 }
209 };
210
211 /**
212 * Creates a new primitive for the given id.
213 *
214 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
215 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
216 * positive number.
217 *
218 * @param id the id
219 * @param allowNegativeId
220 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
221 */
222 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
223 if (allowNegativeId) {
224 this.id = id;
225 } else {
226 if (id < 0)
227 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
228 else if (id == 0) {
229 this.id = generateUniqueId();
230 } else {
231 this.id = id;
232 }
233
234 }
235 this.version = 0;
236 this.setIncomplete(id > 0);
237 }
238
239 /**
240 * Creates a new primitive for the given id and version.
241 *
242 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
243 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
244 * positive number.
245 *
246 * If id is not > 0 version is ignored and set to 0.
247 *
248 * @param id
249 * @param version
250 * @param allowNegativeId
251 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
252 */
253 protected OsmPrimitive(long id, int version, boolean allowNegativeId) throws IllegalArgumentException {
254 this(id, allowNegativeId);
255 this.version = (id > 0 ? version : 0);
256 setIncomplete(id > 0 && version == 0);
257 }
258
259
260 /*----------
261 * MAPPAINT
262 *--------*/
263 public StyleCache mappaintStyle = null;
264 public int mappaintCacheIdx;
265
266 /* This should not be called from outside. Fixing the UI to add relevant
267 get/set functions calling this implicitely is preferred, so we can have
268 transparent cache handling in the future. */
269 public void clearCachedStyle()
270 {
271 mappaintStyle = null;
272 }
273 /* end of mappaint data */
274
275 /*---------
276 * DATASET
277 *---------*/
278
279 /** the parent dataset */
280 private DataSet dataSet;
281
282 /**
283 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
284 * @param dataSet
285 */
286 void setDataset(DataSet dataSet) {
287 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
288 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
289 this.dataSet = dataSet;
290 }
291
292 /**
293 *
294 * @return DataSet this primitive is part of.
295 */
296 public DataSet getDataSet() {
297 return dataSet;
298 }
299
300 /**
301 * Throws exception if primitive is not part of the dataset
302 */
303 public void checkDataset() {
304 if (dataSet == null)
305 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
306 }
307
308 protected boolean writeLock() {
309 if (dataSet != null) {
310 dataSet.beginUpdate();
311 return true;
312 } else
313 return false;
314 }
315
316 protected void writeUnlock(boolean locked) {
317 if (locked) {
318 // It shouldn't be possible for dataset to become null because method calling setDataset would need write lock which is owned by this thread
319 dataSet.endUpdate();
320 }
321 }
322
323 /**
324 * Sets the id and the version of this primitive if it is known to the OSM API.
325 *
326 * Since we know the id and its version it can't be incomplete anymore. incomplete
327 * is set to false.
328 *
329 * @param id the id. > 0 required
330 * @param version the version > 0 required
331 * @throws IllegalArgumentException thrown if id <= 0
332 * @throws IllegalArgumentException thrown if version <= 0
333 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
334 */
335 @Override
336 public void setOsmId(long id, int version) {
337 boolean locked = writeLock();
338 try {
339 if (id <= 0)
340 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
341 if (version <= 0)
342 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
343 if (dataSet != null && id != this.id) {
344 DataSet datasetCopy = dataSet;
345 // Reindex primitive
346 datasetCopy.removePrimitive(this);
347 this.id = id;
348 datasetCopy.addPrimitive(this);
349 }
350 super.setOsmId(id, version);
351 } finally {
352 writeUnlock(locked);
353 }
354 }
355
356 /**
357 * Clears the id and version known to the OSM API. The id and the version is set to 0.
358 * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
359 * of calling this method.
360 *
361 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
362 *
363 * @throws DataIntegrityProblemException If primitive was already added to the dataset
364 */
365 @Override
366 public void clearOsmId() {
367 if (dataSet != null)
368 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
369 super.clearOsmId();
370 }
371
372 @Override
373 public void setUser(User user) {
374 boolean locked = writeLock();
375 try {
376 super.setUser(user);
377 } finally {
378 writeUnlock(locked);
379 }
380 }
381
382 @Override
383 public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
384 boolean locked = writeLock();
385 try {
386 int old = this.changesetId;
387 super.setChangesetId(changesetId);
388 if (dataSet != null) {
389 dataSet.fireChangesetIdChanged(this, old, changesetId);
390 }
391 } finally {
392 writeUnlock(locked);
393 }
394 }
395
396 @Override
397 public void setTimestamp(Date timestamp) {
398 boolean locked = writeLock();
399 try {
400 super.setTimestamp(timestamp);
401 } finally {
402 writeUnlock(locked);
403 }
404 }
405
406
407 /* -------
408 /* FLAGS
409 /* ------*/
410
411 private void updateFlagsNoLock (int flag, boolean value) {
412 super.updateFlags(flag, value);
413 }
414
415 @Override
416 protected final void updateFlags(int flag, boolean value) {
417 boolean locked = writeLock();
418 try {
419 updateFlagsNoLock(flag, value);
420 } finally {
421 writeUnlock(locked);
422 }
423 }
424
425 /**
426 * Make the primitive disabled (e.g. if a filter applies).
427 * To enable the primitive again, use unsetDisabledState.
428 * @param hide if the primitive should be completely hidden from view or
429 * just shown in gray color.
430 */
431 public boolean setDisabledState(boolean hide) {
432 boolean locked = writeLock();
433 try {
434 int oldFlags = flags;
435 updateFlagsNoLock(FLAG_DISABLED, true);
436 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hide);
437 return oldFlags != flags;
438 } finally {
439 writeUnlock(locked);
440 }
441 }
442
443 /**
444 * Remove the disabled flag from the primitive.
445 * Afterwards, the primitive is displayed normally and can be selected
446 * again.
447 */
448 public boolean unsetDisabledState() {
449 boolean locked = writeLock();
450 try {
451 int oldFlags = flags;
452 updateFlagsNoLock(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false);
453 return oldFlags != flags;
454 } finally {
455 writeUnlock(locked);
456 }
457 }
458
459 /**
460 * Replies true, if this primitive is disabled. (E.g. a filter
461 * applies)
462 */
463 public boolean isDisabled() {
464 return (flags & FLAG_DISABLED) != 0;
465 }
466
467 /**
468 * Replies true, if this primitive is disabled and marked as
469 * completely hidden on the map.
470 */
471 public boolean isDisabledAndHidden() {
472 return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0));
473 }
474
475 @Deprecated
476 public boolean isFiltered() {
477 return isDisabledAndHidden();
478 }
479
480
481 public boolean isSelectable() {
482 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
483 }
484
485 public boolean isDrawable() {
486 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
487 }
488
489 @Override
490 public void setVisible(boolean visible) throws IllegalStateException {
491 boolean locked = writeLock();
492 try {
493 super.setVisible(visible);
494 } finally {
495 writeUnlock(locked);
496 }
497 }
498
499 @Override
500 public void setDeleted(boolean deleted) {
501 boolean locked = writeLock();
502 try {
503 super.setDeleted(deleted);
504 if (dataSet != null) {
505 if (deleted) {
506 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
507 } else {
508 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
509 }
510 }
511 } finally {
512 writeUnlock(locked);
513 }
514 }
515
516 @Override
517 protected void setIncomplete(boolean incomplete) {
518 boolean locked = writeLock();
519 try {
520 if (dataSet != null && incomplete != this.isIncomplete()) {
521 if (incomplete) {
522 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
523 } else {
524 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
525 }
526 }
527 super.setIncomplete(incomplete);
528 } finally {
529 writeUnlock(locked);
530 }
531 }
532
533 public boolean isSelected() {
534 return dataSet != null && dataSet.isSelected(this);
535 }
536
537 public boolean isMemberOfSelected() {
538 if (referrers == null)
539 return false;
540 if (referrers instanceof OsmPrimitive)
541 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
542 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
543 if (ref instanceof Relation && ref.isSelected())
544 return true;
545 }
546 return false;
547 }
548
549 public void setHighlighted(boolean highlighted) {
550 if (isHighlighted() != highlighted) {
551 updateFlags(FLAG_HIGHLIGHTED, highlighted);
552 if (dataSet != null) {
553 dataSet.fireHighlightingChanged(this);
554 }
555 }
556 }
557
558 public boolean isHighlighted() {
559 return (flags & FLAG_HIGHLIGHTED) != 0;
560 }
561
562 /*----------------------------------
563 * UNINTERESTING AND DIRECTION KEYS
564 *----------------------------------*/
565
566
567 private static volatile Collection<String> uninteresting = null;
568 /**
569 * Contains a list of "uninteresting" keys that do not make an object
570 * "tagged". Entries that end with ':' are causing a whole namespace to be considered
571 * "uninteresting". Only the first level namespace is considered.
572 * Initialized by isUninterestingKey()
573 */
574 public static Collection<String> getUninterestingKeys() {
575 if (uninteresting == null) {
576 uninteresting = Main.pref.getCollection("tags.uninteresting",
577 Arrays.asList(new String[]{"source", "source_ref", "source:", "note", "comment",
578 "converted_by", "created_by", "watch", "watch:", "fixme", "FIXME",
579 "description", "attribution"}));
580 }
581 return uninteresting;
582 }
583
584 /**
585 * Returns true if key is considered "uninteresting".
586 */
587 public static boolean isUninterestingKey(String key) {
588 getUninterestingKeys();
589 if (uninteresting.contains(key))
590 return true;
591 int pos = key.indexOf(':');
592 if (pos > 0)
593 return uninteresting.contains(key.substring(0, pos + 1));
594 return false;
595 }
596
597 private static volatile Match directionKeys = null;
598 private static volatile Match reversedDirectionKeys = null;
599
600 /**
601 * Contains a list of direction-dependent keys that make an object
602 * direction dependent.
603 * Initialized by checkDirectionTagged()
604 */
605 static {
606 // Legacy support - convert list of keys to search pattern
607 if (Main.pref.isCollection("tags.direction", false)) {
608 System.out.println("Collection of keys in tags.direction is no longer supported, value will converted to search pattern");
609 Collection<String> keys = Main.pref.getCollection("tags.direction", null);
610 StringBuilder builder = new StringBuilder();
611 for (String key:keys) {
612 builder.append(key);
613 builder.append("=* | ");
614 }
615 builder.delete(builder.length() - 3, builder.length());
616 Main.pref.put("tags.direction", builder.toString());
617 }
618
619 // FIXME: incline=\"-*\" search pattern does not work.
620 String reversedDirectionDefault = "oneway=\"-1\" | incline=down | incline=\"-*\"";
621
622 String directionDefault = "oneway? | incline=* | aerialway=* | "+
623 "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
624 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
625 "junction=roundabout";
626
627 try {
628 reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
629 } catch (ParseError e) {
630 System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
631
632 try {
633 reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
634 } catch (ParseError e2) {
635 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
636 }
637 }
638 try {
639 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
640 } catch (ParseError e) {
641 System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
642
643 try {
644 directionKeys = SearchCompiler.compile(directionDefault, false, false);
645 } catch (ParseError e2) {
646 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
647 }
648 }
649 }
650
651 private void updateTagged() {
652 if (keys != null) {
653 for (String key: keySet()) {
654 if (!isUninterestingKey(key)) {
655 updateFlagsNoLock(FLAG_TAGGED, true);
656 return;
657 }
658 }
659 }
660 updateFlagsNoLock(FLAG_TAGGED, false);
661 }
662
663 /**
664 * true if this object is considered "tagged". To be "tagged", an object
665 * must have one or more "interesting" tags. "created_by" and "source"
666 * are typically considered "uninteresting" and do not make an object
667 * "tagged".
668 */
669 public boolean isTagged() {
670 return (flags & FLAG_TAGGED) != 0;
671 }
672
673 private void updateDirectionFlags() {
674 boolean hasDirections = false;
675 boolean directionReversed = false;
676 if (reversedDirectionKeys.match(this)) {
677 hasDirections = true;
678 directionReversed = true;
679 }
680 if (directionKeys.match(this)) {
681 hasDirections = true;
682 }
683
684 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
685 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
686 }
687
688 /**
689 * true if this object has direction dependent tags (e.g. oneway)
690 */
691 public boolean hasDirectionKeys() {
692 return (flags & FLAG_HAS_DIRECTIONS) != 0;
693 }
694
695 public boolean reversedDirection() {
696 return (flags & FLAG_DIRECTION_REVERSED) != 0;
697 }
698
699 /*------------
700 * Keys handling
701 ------------*/
702
703 @Override
704 public final void setKeys(Map<String, String> keys) {
705 boolean locked = writeLock();
706 try {
707 super.setKeys(keys);
708 } finally {
709 writeUnlock(locked);
710 }
711 }
712
713 @Override
714 public final void put(String key, String value) {
715 boolean locked = writeLock();
716 try {
717 super.put(key, value);
718 } finally {
719 writeUnlock(locked);
720 }
721 }
722
723 @Override
724 public final void remove(String key) {
725 boolean locked = writeLock();
726 try {
727 super.remove(key);
728 } finally {
729 writeUnlock(locked);
730 }
731 }
732
733 @Override
734 public final void removeAll() {
735 boolean locked = writeLock();
736 try {
737 super.removeAll();
738 } finally {
739 writeUnlock(locked);
740 }
741 }
742
743 @Override
744 protected final void keysChangedImpl(Map<String, String> originalKeys) {
745 clearCachedStyle();
746 if (dataSet != null) {
747 for (OsmPrimitive ref : getReferrers()) {
748 ref.clearCachedStyle();
749 }
750 }
751 updateDirectionFlags();
752 updateTagged();
753 if (dataSet != null) {
754 dataSet.fireTagsChanged(this, originalKeys);
755 }
756 }
757
758 /*------------
759 * Referrers
760 ------------*/
761
762 private Object referrers;
763
764 /**
765 * Add new referrer. If referrer is already included then no action is taken
766 * @param referrer
767 */
768 protected void addReferrer(OsmPrimitive referrer) {
769 // Based on methods from josm-ng
770 if (referrers == null) {
771 referrers = referrer;
772 } else if (referrers instanceof OsmPrimitive) {
773 if (referrers != referrer) {
774 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
775 }
776 } else {
777 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
778 if (primitive == referrer)
779 return;
780 }
781 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
782 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
783 System.arraycopy(orig, 0, bigger, 0, orig.length);
784 bigger[orig.length] = referrer;
785 referrers = bigger;
786 }
787 }
788
789 /**
790 * Remove referrer. No action is taken if referrer is not registered
791 * @param referrer
792 */
793 protected void removeReferrer(OsmPrimitive referrer) {
794 // Based on methods from josm-ng
795 if (referrers instanceof OsmPrimitive) {
796 if (referrers == referrer) {
797 referrers = null;
798 }
799 } else if (referrers instanceof OsmPrimitive[]) {
800 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
801 int idx = -1;
802 for (int i=0; i<orig.length; i++) {
803 if (orig[i] == referrer) {
804 idx = i;
805 break;
806 }
807 }
808 if (idx == -1)
809 return;
810
811 if (orig.length == 2) {
812 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
813 } else { // downsize the array
814 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
815 System.arraycopy(orig, 0, smaller, 0, idx);
816 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
817 referrers = smaller;
818 }
819 }
820 }
821 /**
822 * Find primitives that reference this primitive. Returns only primitives that are included in the same
823 * dataset as this primitive. <br>
824 *
825 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
826 * not return wnew because it's not part of the dataset <br>
827 *
828 * <code>Way wnew = new Way(existingWay)</code>
829 *
830 * @return a collection of all primitives that reference this primitive.
831 */
832
833 public final List<OsmPrimitive> getReferrers() {
834 // Method copied from OsmPrimitive in josm-ng
835 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
836 // when way is cloned
837 checkDataset();
838 Object referrers = this.referrers;
839 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
840 if (referrers != null) {
841 if (referrers instanceof OsmPrimitive) {
842 OsmPrimitive ref = (OsmPrimitive)referrers;
843 if (ref.dataSet == dataSet) {
844 result.add(ref);
845 }
846 } else {
847 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
848 if (dataSet == o.dataSet) {
849 result.add(o);
850 }
851 }
852 }
853 }
854 return result;
855 }
856
857 /**
858 * <p>Visits {@code visitor} for all referrers.</p>
859 *
860 * @param visitor the visitor. Ignored, if null.
861 */
862 public void visitReferrers(Visitor visitor){
863 if (visitor == null) return;
864 if (this.referrers == null)
865 return;
866 else if (this.referrers instanceof OsmPrimitive) {
867 OsmPrimitive ref = (OsmPrimitive) this.referrers;
868 if (ref.dataSet == dataSet) {
869 ref.visit(visitor);
870 }
871 } else if (this.referrers instanceof OsmPrimitive[]) {
872 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
873 for (OsmPrimitive ref: refs) {
874 if (ref.dataSet == dataSet) {
875 ref.visit(visitor);
876 }
877 }
878 }
879 }
880
881 /**
882 Return true, if this primitive is referred by at least n ways
883 @param n Minimal number of ways to return true. Must be positive
884 */
885 public final boolean isReferredByWays(int n) {
886 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
887 // when way is cloned
888 Object referrers = this.referrers;
889 if (referrers == null) return false;
890 checkDataset();
891 if (referrers instanceof OsmPrimitive) {
892 return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet;
893 } else {
894 int counter=0;
895 for (OsmPrimitive o : (OsmPrimitive[])referrers) {
896 if (dataSet == o.dataSet && o instanceof Way) {
897 if (++counter >= n)
898 return true;
899 }
900 }
901 return false;
902 }
903 }
904
905
906 /*-----------------
907 * OTHER METHODS
908 *----------------/
909
910 /**
911 * Implementation of the visitor scheme. Subclasses have to call the correct
912 * visitor function.
913 * @param visitor The visitor from which the visit() function must be called.
914 */
915 abstract public void visit(Visitor visitor);
916
917 /**
918 * Get and write all attributes from the parameter. Does not fire any listener, so
919 * use this only in the data initializing phase
920 */
921 public void cloneFrom(OsmPrimitive other) {
922 // write lock is provided by subclasses
923 if (id != other.id && dataSet != null)
924 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
925
926 super.cloneFrom(other);
927 clearCachedStyle();
928 }
929
930 /**
931 * Merges the technical and semantical attributes from <code>other</code> onto this.
932 *
933 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
934 * have an assigend OSM id, the IDs have to be the same.
935 *
936 * @param other the other primitive. Must not be null.
937 * @throws IllegalArgumentException thrown if other is null.
938 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
939 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
940 */
941 public void mergeFrom(OsmPrimitive other) {
942 boolean locked = writeLock();
943 try {
944 CheckParameterUtil.ensureParameterNotNull(other, "other");
945 if (other.isNew() ^ isNew())
946 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
947 if (! other.isNew() && other.getId() != id)
948 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
949
950 setKeys(other.getKeys());
951 timestamp = other.timestamp;
952 version = other.version;
953 setIncomplete(other.isIncomplete());
954 flags = other.flags;
955 user= other.user;
956 changesetId = other.changesetId;
957 } finally {
958 writeUnlock(locked);
959 }
960 }
961
962 /**
963 * Replies true if this primitive and other are equal with respect to their
964 * semantic attributes.
965 * <ol>
966 * <li>equal id</ol>
967 * <li>both are complete or both are incomplete</li>
968 * <li>both have the same tags</li>
969 * </ol>
970 * @param other
971 * @return true if this primitive and other are equal with respect to their
972 * semantic attributes.
973 */
974 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
975 if (!isNew() && id != other.id)
976 return false;
977 if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
978 return false;
979 // can't do an equals check on the internal keys array because it is not ordered
980 //
981 return hasSameTags(other);
982 }
983
984 /**
985 * Replies true if this primitive and other are equal with respect to their
986 * technical attributes. The attributes:
987 * <ol>
988 * <li>deleted</ol>
989 * <li>modified</ol>
990 * <li>timestamp</ol>
991 * <li>version</ol>
992 * <li>visible</ol>
993 * <li>user</ol>
994 * </ol>
995 * have to be equal
996 * @param other the other primitive
997 * @return true if this primitive and other are equal with respect to their
998 * technical attributes
999 */
1000 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1001 if (other == null) return false;
1002
1003 return
1004 isDeleted() == other.isDeleted()
1005 && isModified() == other.isModified()
1006 && timestamp == other.timestamp
1007 && version == other.version
1008 && isVisible() == other.isVisible()
1009 && (user == null ? other.user==null : user==other.user)
1010 && changesetId == other.changesetId;
1011 }
1012
1013 /**
1014 * Loads (clone) this primitive from provided PrimitiveData
1015 * @param data
1016 */
1017 public void load(PrimitiveData data) {
1018 // Write lock is provided by subclasses
1019 setKeys(data.getKeys());
1020 setTimestamp(data.getTimestamp());
1021 user = data.getUser();
1022 setChangesetId(data.getChangesetId());
1023 setDeleted(data.isDeleted());
1024 setModified(data.isModified());
1025 setIncomplete(data.isIncomplete());
1026 version = data.getVersion();
1027 }
1028
1029 /**
1030 * Save parameters of this primitive to the transport object
1031 * @return
1032 */
1033 public abstract PrimitiveData save();
1034
1035 protected void saveCommonAttributes(PrimitiveData data) {
1036 data.setId(id);
1037 data.getKeys().clear();
1038 data.getKeys().putAll(getKeys());
1039 data.setTimestamp(getTimestamp());
1040 data.setUser(user);
1041 data.setDeleted(isDeleted());
1042 data.setModified(isModified());
1043 data.setVisible(isVisible());
1044 data.setIncomplete(isIncomplete());
1045 data.setChangesetId(changesetId);
1046 data.setVersion(version);
1047 }
1048
1049 public abstract BBox getBBox();
1050
1051 /**
1052 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1053 */
1054 public abstract void updatePosition();
1055
1056 /*----------------
1057 * OBJECT METHODS
1058 *---------------*/
1059
1060 @Override
1061 protected String getFlagsAsString() {
1062 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1063
1064 if (isDisabled()) {
1065 if (isDisabledAndHidden()) {
1066 builder.append("h");
1067 } else {
1068 builder.append("d");
1069 }
1070 }
1071 if (isTagged()) {
1072 builder.append("T");
1073 }
1074 if (hasDirectionKeys()) {
1075 if (reversedDirection()) {
1076 builder.append("<");
1077 } else {
1078 builder.append(">");
1079 }
1080 }
1081 return builder.toString();
1082 }
1083
1084 /**
1085 * Equal, if the id (and class) is equal.
1086 *
1087 * An primitive is equal to its incomplete counter part.
1088 */
1089 @Override public boolean equals(Object obj) {
1090 if (obj instanceof OsmPrimitive)
1091 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
1092 return false;
1093 }
1094
1095 /**
1096 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1097 *
1098 * An primitive has the same hashcode as its incomplete counterpart.
1099 */
1100 @Override public final int hashCode() {
1101 return (int)id;
1102 }
1103
1104}
Note: See TracBrowser for help on using the repository browser.