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

Last change on this file since 5132 was 5132, checked in by simon04, 12 years ago

fix #7513 - Warn non-experts when combining ways with conflicting tags or ways being part of relations

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