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

Last change on this file since 13922 was 13809, checked in by Don-vip, 6 years ago

define InterestingTags functions in IPrimitive

  • Property svn:eol-style set to native
File size: 39.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.Collection;
9import java.util.Collections;
10import java.util.Date;
11import java.util.HashSet;
12import java.util.LinkedHashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.Objects;
18import java.util.Set;
19import java.util.function.Consumer;
20
21import org.openstreetmap.josm.data.osm.search.SearchCompiler;
22import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
23import org.openstreetmap.josm.data.osm.search.SearchParseError;
24import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
25import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
26import org.openstreetmap.josm.gui.mappaint.StyleCache;
27import org.openstreetmap.josm.spi.preferences.Config;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29import org.openstreetmap.josm.tools.Logging;
30import org.openstreetmap.josm.tools.Utils;
31import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
32
33/**
34 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
35 *
36 * It can be created, deleted and uploaded to the OSM-Server.
37 *
38 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
39 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
40 * by the server environment and not an extendible data stuff.
41 *
42 * @author imi
43 */
44public abstract class OsmPrimitive extends AbstractPrimitive implements TemplateEngineDataProvider {
45 private static final String SPECIAL_VALUE_ID = "id";
46 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
47
48 /**
49 * A tagged way that matches this pattern has a direction.
50 * @see #FLAG_HAS_DIRECTIONS
51 */
52 static volatile Match directionKeys;
53
54 /**
55 * A tagged way that matches this pattern has a direction that is reversed.
56 * <p>
57 * This pattern should be a subset of {@link #directionKeys}
58 * @see #FLAG_DIRECTION_REVERSED
59 */
60 private static volatile Match reversedDirectionKeys;
61
62 static {
63 String reversedDirectionDefault = "oneway=\"-1\"";
64
65 String directionDefault = "oneway? | "+
66 "(aerialway=chair_lift & -oneway=no) | "+
67 "(aerialway=rope_tow & -oneway=no) | "+
68 "(aerialway=magic_carpet & -oneway=no) | "+
69 "(aerialway=zip_line & -oneway=no) | "+
70 "(aerialway=drag_lift & -oneway=no) | "+
71 "(aerialway=t-bar & -oneway=no) | "+
72 "(aerialway=j-bar & -oneway=no) | "+
73 "(aerialway=platter & -oneway=no) | "+
74 "waterway=stream | waterway=river | waterway=ditch | waterway=drain | "+
75 "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+
76 "junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+
77 "(highway=motorway_link & -oneway=no & -oneway=reversible)";
78
79 reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault);
80 directionKeys = compileDirectionKeys("tags.direction", directionDefault);
81 }
82
83 /**
84 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
85 * another collection of {@link OsmPrimitive}s. The result collection is a list.
86 *
87 * If <code>list</code> is null, replies an empty list.
88 *
89 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
90 * @param list the original list
91 * @param type the type to filter for
92 * @return the sub-list of OSM primitives of type <code>type</code>
93 */
94 public static <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
95 if (list == null) return Collections.emptyList();
96 List<T> ret = new LinkedList<>();
97 for (OsmPrimitive p: list) {
98 if (type.isInstance(p)) {
99 ret.add(type.cast(p));
100 }
101 }
102 return ret;
103 }
104
105 /**
106 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
107 * another collection of {@link OsmPrimitive}s. The result collection is a set.
108 *
109 * If <code>list</code> is null, replies an empty set.
110 *
111 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
112 * @param set the original collection
113 * @param type the type to filter for
114 * @return the sub-set of OSM primitives of type <code>type</code>
115 */
116 public static <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
117 Set<T> ret = new LinkedHashSet<>();
118 if (set != null) {
119 for (OsmPrimitive p: set) {
120 if (type.isInstance(p)) {
121 ret.add(type.cast(p));
122 }
123 }
124 }
125 return ret;
126 }
127
128 /**
129 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
130 *
131 * @param primitives the collection of primitives.
132 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
133 * empty set if primitives is null or if there are no referring primitives
134 */
135 public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
136 Set<OsmPrimitive> ret = new HashSet<>();
137 if (primitives == null || primitives.isEmpty()) return ret;
138 for (OsmPrimitive p: primitives) {
139 ret.addAll(p.getReferrers());
140 }
141 return ret;
142 }
143
144 /**
145 * Creates a new primitive for the given id.
146 *
147 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
148 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
149 * positive number.
150 *
151 * @param id the id
152 * @param allowNegativeId {@code true} to allow negative id
153 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
154 */
155 protected OsmPrimitive(long id, boolean allowNegativeId) {
156 if (allowNegativeId) {
157 this.id = id;
158 } else {
159 if (id < 0)
160 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
161 else if (id == 0) {
162 this.id = generateUniqueId();
163 } else {
164 this.id = id;
165 }
166
167 }
168 this.version = 0;
169 this.setIncomplete(id > 0);
170 }
171
172 /**
173 * Creates a new primitive for the given id and version.
174 *
175 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
176 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
177 * positive number.
178 *
179 * If id is not &gt; 0 version is ignored and set to 0.
180 *
181 * @param id the id
182 * @param version the version (positive integer)
183 * @param allowNegativeId {@code true} to allow negative id
184 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
185 */
186 protected OsmPrimitive(long id, int version, boolean allowNegativeId) {
187 this(id, allowNegativeId);
188 this.version = id > 0 ? version : 0;
189 setIncomplete(id > 0 && version == 0);
190 }
191
192 /*----------
193 * MAPPAINT
194 *--------*/
195 private StyleCache mappaintStyle;
196 private short mappaintCacheIdx;
197
198 @Override
199 public final StyleCache getCachedStyle() {
200 return mappaintStyle;
201 }
202
203 @Override
204 public final void setCachedStyle(StyleCache mappaintStyle) {
205 this.mappaintStyle = mappaintStyle;
206 }
207
208 @Override
209 public final boolean isCachedStyleUpToDate() {
210 return mappaintStyle != null && mappaintCacheIdx == dataSet.getMappaintCacheIndex();
211 }
212
213 @Override
214 public final void declareCachedStyleUpToDate() {
215 this.mappaintCacheIdx = dataSet.getMappaintCacheIndex();
216 }
217
218 /* end of mappaint data */
219
220 /*---------
221 * DATASET
222 *---------*/
223
224 /** the parent dataset */
225 private DataSet dataSet;
226
227 /**
228 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
229 * @param dataSet the parent dataset
230 */
231 void setDataset(DataSet dataSet) {
232 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
233 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
234 this.dataSet = dataSet;
235 }
236
237 @Override
238 public DataSet getDataSet() {
239 return dataSet;
240 }
241
242 /**
243 * Throws exception if primitive is not part of the dataset
244 */
245 public void checkDataset() {
246 if (dataSet == null)
247 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
248 }
249
250 /**
251 * Throws exception if primitive is in a read-only dataset
252 */
253 protected final void checkDatasetNotReadOnly() {
254 if (dataSet != null && dataSet.isLocked())
255 throw new DataIntegrityProblemException("Primitive cannot be modified in read-only dataset: " + toString());
256 }
257
258 protected boolean writeLock() {
259 if (dataSet != null) {
260 dataSet.beginUpdate();
261 return true;
262 } else
263 return false;
264 }
265
266 protected void writeUnlock(boolean locked) {
267 if (locked) {
268 // It shouldn't be possible for dataset to become null because
269 // method calling setDataset would need write lock which is owned by this thread
270 dataSet.endUpdate();
271 }
272 }
273
274 /**
275 * Sets the id and the version of this primitive if it is known to the OSM API.
276 *
277 * Since we know the id and its version it can't be incomplete anymore. incomplete
278 * is set to false.
279 *
280 * @param id the id. &gt; 0 required
281 * @param version the version &gt; 0 required
282 * @throws IllegalArgumentException if id &lt;= 0
283 * @throws IllegalArgumentException if version &lt;= 0
284 * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
285 */
286 @Override
287 public void setOsmId(long id, int version) {
288 checkDatasetNotReadOnly();
289 boolean locked = writeLock();
290 try {
291 if (id <= 0)
292 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
293 if (version <= 0)
294 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
295 if (dataSet != null && id != this.id) {
296 DataSet datasetCopy = dataSet;
297 // Reindex primitive
298 datasetCopy.removePrimitive(this);
299 this.id = id;
300 datasetCopy.addPrimitive(this);
301 }
302 super.setOsmId(id, version);
303 } finally {
304 writeUnlock(locked);
305 }
306 }
307
308 /**
309 * Clears the metadata, including id and version known to the OSM API.
310 * The id is a new unique id. The version, changeset and timestamp are set to 0.
311 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
312 *
313 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
314 *
315 * @throws DataIntegrityProblemException If primitive was already added to the dataset
316 * @since 6140
317 */
318 @Override
319 public void clearOsmMetadata() {
320 if (dataSet != null)
321 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
322 super.clearOsmMetadata();
323 }
324
325 @Override
326 public void setUser(User user) {
327 checkDatasetNotReadOnly();
328 boolean locked = writeLock();
329 try {
330 super.setUser(user);
331 } finally {
332 writeUnlock(locked);
333 }
334 }
335
336 @Override
337 public void setChangesetId(int changesetId) {
338 checkDatasetNotReadOnly();
339 boolean locked = writeLock();
340 try {
341 int old = this.changesetId;
342 super.setChangesetId(changesetId);
343 if (dataSet != null) {
344 dataSet.fireChangesetIdChanged(this, old, changesetId);
345 }
346 } finally {
347 writeUnlock(locked);
348 }
349 }
350
351 @Override
352 public void setTimestamp(Date timestamp) {
353 checkDatasetNotReadOnly();
354 boolean locked = writeLock();
355 try {
356 super.setTimestamp(timestamp);
357 } finally {
358 writeUnlock(locked);
359 }
360 }
361
362
363 /* -------
364 /* FLAGS
365 /* ------*/
366
367 private void updateFlagsNoLock(short flag, boolean value) {
368 super.updateFlags(flag, value);
369 }
370
371 @Override
372 protected final void updateFlags(short flag, boolean value) {
373 boolean locked = writeLock();
374 try {
375 updateFlagsNoLock(flag, value);
376 } finally {
377 writeUnlock(locked);
378 }
379 }
380
381 /**
382 * Make the primitive disabled (e.g.&nbsp;if a filter applies).
383 *
384 * To enable the primitive again, use unsetDisabledState.
385 * @param hidden if the primitive should be completely hidden from view or
386 * just shown in gray color.
387 * @return true, any flag has changed; false if you try to set the disabled
388 * state to the value that is already preset
389 */
390 public boolean setDisabledState(boolean hidden) {
391 boolean locked = writeLock();
392 try {
393 int oldFlags = flags;
394 updateFlagsNoLock(FLAG_DISABLED, true);
395 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
396 return oldFlags != flags;
397 } finally {
398 writeUnlock(locked);
399 }
400 }
401
402 /**
403 * Remove the disabled flag from the primitive.
404 * Afterwards, the primitive is displayed normally and can be selected again.
405 * @return {@code true} if a change occurred
406 */
407 public boolean unsetDisabledState() {
408 boolean locked = writeLock();
409 try {
410 int oldFlags = flags;
411 updateFlagsNoLock(FLAG_DISABLED, false);
412 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, false);
413 return oldFlags != flags;
414 } finally {
415 writeUnlock(locked);
416 }
417 }
418
419 /**
420 * Set binary property used internally by the filter mechanism.
421 * @param isExplicit new "disabled type" flag value
422 */
423 public void setDisabledType(boolean isExplicit) {
424 updateFlags(FLAG_DISABLED_TYPE, isExplicit);
425 }
426
427 /**
428 * Set binary property used internally by the filter mechanism.
429 * @param isExplicit new "hidden type" flag value
430 */
431 public void setHiddenType(boolean isExplicit) {
432 updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
433 }
434
435 /**
436 * Set binary property used internally by the filter mechanism.
437 * @param isPreserved new "preserved" flag value
438 * @since 13309
439 */
440 public void setPreserved(boolean isPreserved) {
441 updateFlags(FLAG_PRESERVED, isPreserved);
442 }
443
444 @Override
445 public boolean isDisabled() {
446 return (flags & FLAG_DISABLED) != 0;
447 }
448
449 @Override
450 public boolean isDisabledAndHidden() {
451 return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
452 }
453
454 /**
455 * Get binary property used internally by the filter mechanism.
456 * @return {@code true} if this object has the "hidden type" flag enabled
457 */
458 public boolean getHiddenType() {
459 return (flags & FLAG_HIDDEN_TYPE) != 0;
460 }
461
462 /**
463 * Get binary property used internally by the filter mechanism.
464 * @return {@code true} if this object has the "disabled type" flag enabled
465 */
466 public boolean getDisabledType() {
467 return (flags & FLAG_DISABLED_TYPE) != 0;
468 }
469
470 @Override
471 public boolean isPreserved() {
472 return (flags & FLAG_PRESERVED) != 0;
473 }
474
475 @Override
476 public boolean isSelectable() {
477 // not synchronized -> check disabled twice just to be sure we did not have a race condition.
478 return !isDisabled() && isDrawable() && !isDisabled();
479 }
480
481 @Override
482 public boolean isDrawable() {
483 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
484 }
485
486 @Override
487 public void setModified(boolean modified) {
488 checkDatasetNotReadOnly();
489 boolean locked = writeLock();
490 try {
491 super.setModified(modified);
492 if (dataSet != null) {
493 dataSet.firePrimitiveFlagsChanged(this);
494 }
495 clearCachedStyle();
496 } finally {
497 writeUnlock(locked);
498 }
499 }
500
501 @Override
502 public void setVisible(boolean visible) {
503 checkDatasetNotReadOnly();
504 boolean locked = writeLock();
505 try {
506 super.setVisible(visible);
507 clearCachedStyle();
508 } finally {
509 writeUnlock(locked);
510 }
511 }
512
513 @Override
514 public void setDeleted(boolean deleted) {
515 checkDatasetNotReadOnly();
516 boolean locked = writeLock();
517 try {
518 super.setDeleted(deleted);
519 if (dataSet != null) {
520 if (deleted) {
521 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
522 } else {
523 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
524 }
525 }
526 clearCachedStyle();
527 } finally {
528 writeUnlock(locked);
529 }
530 }
531
532 @Override
533 protected final void setIncomplete(boolean incomplete) {
534 checkDatasetNotReadOnly();
535 boolean locked = writeLock();
536 try {
537 if (dataSet != null && incomplete != this.isIncomplete()) {
538 if (incomplete) {
539 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
540 } else {
541 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
542 }
543 }
544 super.setIncomplete(incomplete);
545 } finally {
546 writeUnlock(locked);
547 }
548 }
549
550 @Override
551 public boolean isSelected() {
552 return dataSet != null && dataSet.isSelected(this);
553 }
554
555 @Override
556 public boolean isMemberOfSelected() {
557 if (referrers == null)
558 return false;
559 if (referrers instanceof OsmPrimitive)
560 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
561 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
562 if (ref instanceof Relation && ref.isSelected())
563 return true;
564 }
565 return false;
566 }
567
568 @Override
569 public boolean isOuterMemberOfSelected() {
570 if (referrers == null)
571 return false;
572 if (referrers instanceof OsmPrimitive) {
573 return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
574 }
575 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
576 if (isOuterMemberOfMultipolygon(ref))
577 return true;
578 }
579 return false;
580 }
581
582 private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
583 if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) {
584 for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) {
585 if ("outer".equals(rm.getRole())) {
586 return true;
587 }
588 }
589 }
590 return false;
591 }
592
593 @Override
594 public void setHighlighted(boolean highlighted) {
595 if (isHighlighted() != highlighted) {
596 updateFlags(FLAG_HIGHLIGHTED, highlighted);
597 if (dataSet != null) {
598 dataSet.fireHighlightingChanged();
599 }
600 }
601 }
602
603 @Override
604 public boolean isHighlighted() {
605 return (flags & FLAG_HIGHLIGHTED) != 0;
606 }
607
608 /*---------------
609 * DIRECTION KEYS
610 *---------------*/
611
612 private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
613 try {
614 return SearchCompiler.compile(Config.getPref().get(prefName, defaultValue));
615 } catch (SearchParseError e) {
616 Logging.log(Logging.LEVEL_ERROR, "Unable to compile pattern for " + prefName + ", trying default pattern:", e);
617 }
618
619 try {
620 return SearchCompiler.compile(defaultValue);
621 } catch (SearchParseError e2) {
622 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
623 }
624 }
625
626 private void updateTagged() {
627 for (String key: keySet()) {
628 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
629 // but it's clearly not enough to consider an object as tagged (see #9261)
630 if (!isUninterestingKey(key) && !"area".equals(key)) {
631 updateFlagsNoLock(FLAG_TAGGED, true);
632 return;
633 }
634 }
635 updateFlagsNoLock(FLAG_TAGGED, false);
636 }
637
638 private void updateAnnotated() {
639 for (String key: keySet()) {
640 if (getWorkInProgressKeys().contains(key)) {
641 updateFlagsNoLock(FLAG_ANNOTATED, true);
642 return;
643 }
644 }
645 updateFlagsNoLock(FLAG_ANNOTATED, false);
646 }
647
648 @Override
649 public boolean isTagged() {
650 return (flags & FLAG_TAGGED) != 0;
651 }
652
653 @Override
654 public boolean isAnnotated() {
655 return (flags & FLAG_ANNOTATED) != 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 @Override
674 public boolean hasDirectionKeys() {
675 return (flags & FLAG_HAS_DIRECTIONS) != 0;
676 }
677
678 @Override
679 public boolean reversedDirection() {
680 return (flags & FLAG_DIRECTION_REVERSED) != 0;
681 }
682
683 /*------------
684 * Keys handling
685 ------------*/
686
687 @Override
688 public final void setKeys(TagMap keys) {
689 checkDatasetNotReadOnly();
690 boolean locked = writeLock();
691 try {
692 super.setKeys(keys);
693 } finally {
694 writeUnlock(locked);
695 }
696 }
697
698 @Override
699 public final void setKeys(Map<String, String> keys) {
700 checkDatasetNotReadOnly();
701 boolean locked = writeLock();
702 try {
703 super.setKeys(keys);
704 } finally {
705 writeUnlock(locked);
706 }
707 }
708
709 @Override
710 public final void put(String key, String value) {
711 checkDatasetNotReadOnly();
712 boolean locked = writeLock();
713 try {
714 super.put(key, value);
715 } finally {
716 writeUnlock(locked);
717 }
718 }
719
720 @Override
721 public final void remove(String key) {
722 checkDatasetNotReadOnly();
723 boolean locked = writeLock();
724 try {
725 super.remove(key);
726 } finally {
727 writeUnlock(locked);
728 }
729 }
730
731 @Override
732 public final void removeAll() {
733 checkDatasetNotReadOnly();
734 boolean locked = writeLock();
735 try {
736 super.removeAll();
737 } finally {
738 writeUnlock(locked);
739 }
740 }
741
742 @Override
743 protected void keysChangedImpl(Map<String, String> originalKeys) {
744 clearCachedStyle();
745 if (dataSet != null) {
746 for (OsmPrimitive ref : getReferrers()) {
747 ref.clearCachedStyle();
748 }
749 }
750 updateDirectionFlags();
751 updateTagged();
752 updateAnnotated();
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 The referrer to add
767 */
768 protected void addReferrer(OsmPrimitive referrer) {
769 checkDatasetNotReadOnly();
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 referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer);
782 }
783 }
784
785 /**
786 * Remove referrer. No action is taken if referrer is not registered
787 * @param referrer The referrer to remove
788 */
789 protected void removeReferrer(OsmPrimitive referrer) {
790 checkDatasetNotReadOnly();
791 if (referrers instanceof OsmPrimitive) {
792 if (referrers == referrer) {
793 referrers = null;
794 }
795 } else if (referrers instanceof OsmPrimitive[]) {
796 OsmPrimitive[] orig = (OsmPrimitive[]) referrers;
797 int idx = -1;
798 for (int i = 0; i < orig.length; i++) {
799 if (orig[i] == referrer) {
800 idx = i;
801 break;
802 }
803 }
804 if (idx == -1)
805 return;
806
807 if (orig.length == 2) {
808 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
809 } else { // downsize the array
810 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
811 System.arraycopy(orig, 0, smaller, 0, idx);
812 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
813 referrers = smaller;
814 }
815 }
816 }
817
818 @Override
819 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
820 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
821 // when way is cloned
822
823 if (dataSet == null && allowWithoutDataset)
824 return Collections.emptyList();
825
826 checkDataset();
827 Object referrers = this.referrers;
828 List<OsmPrimitive> result = new ArrayList<>();
829 if (referrers != null) {
830 if (referrers instanceof OsmPrimitive) {
831 OsmPrimitive ref = (OsmPrimitive) referrers;
832 if (ref.dataSet == dataSet) {
833 result.add(ref);
834 }
835 } else {
836 for (OsmPrimitive o:(OsmPrimitive[]) referrers) {
837 if (dataSet == o.dataSet) {
838 result.add(o);
839 }
840 }
841 }
842 }
843 return result;
844 }
845
846 @Override
847 public final List<OsmPrimitive> getReferrers() {
848 return getReferrers(false);
849 }
850
851 /**
852 * <p>Visits {@code visitor} for all referrers.</p>
853 *
854 * @param visitor the visitor. Ignored, if null.
855 * @since 12809
856 */
857 public void visitReferrers(OsmPrimitiveVisitor visitor) {
858 if (visitor != null)
859 doVisitReferrers(o -> o.accept(visitor));
860 }
861
862 @Override
863 public void visitReferrers(PrimitiveVisitor visitor) {
864 if (visitor != null)
865 doVisitReferrers(o -> o.accept(visitor));
866 }
867
868 private void doVisitReferrers(Consumer<OsmPrimitive> visitor) {
869 if (this.referrers == null)
870 return;
871 else if (this.referrers instanceof OsmPrimitive) {
872 OsmPrimitive ref = (OsmPrimitive) this.referrers;
873 if (ref.dataSet == dataSet) {
874 visitor.accept(ref);
875 }
876 } else if (this.referrers instanceof OsmPrimitive[]) {
877 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
878 for (OsmPrimitive ref: refs) {
879 if (ref.dataSet == dataSet) {
880 visitor.accept(ref);
881 }
882 }
883 }
884 }
885
886 /**
887 * Return true, if this primitive is a node referred by at least n ways
888 * @param n Minimal number of ways to return true. Must be positive
889 * @return {@code true} if this primitive is referred by at least n ways
890 */
891 protected final boolean isNodeReferredByWays(int n) {
892 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
893 // when way is cloned
894 Object referrers = this.referrers;
895 if (referrers == null) return false;
896 checkDataset();
897 if (referrers instanceof OsmPrimitive)
898 return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
899 else {
900 int counter = 0;
901 for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
902 if (dataSet == o.dataSet && o instanceof Way && ++counter >= n)
903 return true;
904 }
905 return false;
906 }
907 }
908
909 /*-----------------
910 * OTHER METHODS
911 *----------------*/
912
913 /**
914 * Implementation of the visitor scheme. Subclasses have to call the correct
915 * visitor function.
916 * @param visitor The visitor from which the visit() function must be called.
917 * @since 12809
918 */
919 public abstract void accept(OsmPrimitiveVisitor visitor);
920
921 /**
922 * Get and write all attributes from the parameter. Does not fire any listener, so
923 * use this only in the data initializing phase
924 * @param other other primitive
925 */
926 public void cloneFrom(OsmPrimitive other) {
927 // write lock is provided by subclasses
928 if (id != other.id && dataSet != null)
929 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
930
931 super.cloneFrom(other);
932 clearCachedStyle();
933 }
934
935 /**
936 * Merges the technical and semantical attributes from <code>other</code> onto this.
937 *
938 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
939 * have an assigned OSM id, the IDs have to be the same.
940 *
941 * @param other the other primitive. Must not be null.
942 * @throws IllegalArgumentException if other is null.
943 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
944 * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
945 */
946 public void mergeFrom(OsmPrimitive other) {
947 checkDatasetNotReadOnly();
948 boolean locked = writeLock();
949 try {
950 CheckParameterUtil.ensureParameterNotNull(other, "other");
951 if (other.isNew() ^ isNew())
952 throw new DataIntegrityProblemException(
953 tr("Cannot merge because either of the participating primitives is new and the other is not"));
954 if (!other.isNew() && other.getId() != id)
955 throw new DataIntegrityProblemException(
956 tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
957
958 setKeys(other.hasKeys() ? other.getKeys() : null);
959 timestamp = other.timestamp;
960 version = other.version;
961 setIncomplete(other.isIncomplete());
962 flags = other.flags;
963 user = other.user;
964 changesetId = other.changesetId;
965 } finally {
966 writeUnlock(locked);
967 }
968 }
969
970 /**
971 * Replies true if this primitive and other are equal with respect to their semantic attributes.
972 * <ol>
973 * <li>equal id</li>
974 * <li>both are complete or both are incomplete</li>
975 * <li>both have the same tags</li>
976 * </ol>
977 * @param other other primitive to compare
978 * @return true if this primitive and other are equal with respect to their semantic attributes.
979 */
980 public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
981 return hasEqualSemanticAttributes(other, true);
982 }
983
984 boolean hasEqualSemanticFlags(final OsmPrimitive other) {
985 if (!isNew() && id != other.id)
986 return false;
987 return !(isIncomplete() ^ other.isIncomplete()); // exclusive or operator for performance (see #7159)
988 }
989
990 boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
991 return hasEqualSemanticFlags(other)
992 && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys()));
993 }
994
995 /**
996 * Replies true if this primitive and other are equal with respect to their technical attributes.
997 * The attributes:
998 * <ol>
999 * <li>deleted</li>
1000 * <li>modified</li>
1001 * <li>timestamp</li>
1002 * <li>version</li>
1003 * <li>visible</li>
1004 * <li>user</li>
1005 * </ol>
1006 * have to be equal
1007 * @param other the other primitive
1008 * @return true if this primitive and other are equal with respect to their technical attributes
1009 */
1010 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1011 // CHECKSTYLE.OFF: BooleanExpressionComplexity
1012 return other != null
1013 && timestamp == other.timestamp
1014 && version == other.version
1015 && changesetId == other.changesetId
1016 && isDeleted() == other.isDeleted()
1017 && isModified() == other.isModified()
1018 && isVisible() == other.isVisible()
1019 && Objects.equals(user, other.user);
1020 // CHECKSTYLE.ON: BooleanExpressionComplexity
1021 }
1022
1023 /**
1024 * Loads (clone) this primitive from provided PrimitiveData
1025 * @param data The object which should be cloned
1026 */
1027 public void load(PrimitiveData data) {
1028 checkDatasetNotReadOnly();
1029 // Write lock is provided by subclasses
1030 setKeys(data.hasKeys() ? data.getKeys() : null);
1031 setRawTimestamp(data.getRawTimestamp());
1032 user = data.getUser();
1033 setChangesetId(data.getChangesetId());
1034 setDeleted(data.isDeleted());
1035 setModified(data.isModified());
1036 setVisible(data.isVisible());
1037 setIncomplete(data.isIncomplete());
1038 version = data.getVersion();
1039 }
1040
1041 /**
1042 * Save parameters of this primitive to the transport object
1043 * @return The saved object data
1044 */
1045 public abstract PrimitiveData save();
1046
1047 /**
1048 * Save common parameters of primitives to the transport object
1049 * @param data The object to save the data into
1050 */
1051 protected void saveCommonAttributes(PrimitiveData data) {
1052 data.setId(id);
1053 data.setKeys(hasKeys() ? getKeys() : null);
1054 data.setRawTimestamp(getRawTimestamp());
1055 data.setUser(user);
1056 data.setDeleted(isDeleted());
1057 data.setModified(isModified());
1058 data.setVisible(isVisible());
1059 data.setIncomplete(isIncomplete());
1060 data.setChangesetId(changesetId);
1061 data.setVersion(version);
1062 }
1063
1064 /**
1065 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1066 */
1067 public abstract void updatePosition();
1068
1069 /*----------------
1070 * OBJECT METHODS
1071 *---------------*/
1072
1073 @Override
1074 protected String getFlagsAsString() {
1075 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1076
1077 if (isDisabled()) {
1078 if (isDisabledAndHidden()) {
1079 builder.append('h');
1080 } else {
1081 builder.append('d');
1082 }
1083 }
1084 if (isTagged()) {
1085 builder.append('T');
1086 }
1087 if (hasDirectionKeys()) {
1088 if (reversedDirection()) {
1089 builder.append('<');
1090 } else {
1091 builder.append('>');
1092 }
1093 }
1094 return builder.toString();
1095 }
1096
1097 /**
1098 * Equal, if the id (and class) is equal.
1099 *
1100 * An primitive is equal to its incomplete counter part.
1101 */
1102 @Override
1103 public boolean equals(Object obj) {
1104 if (this == obj) {
1105 return true;
1106 } else if (obj == null || getClass() != obj.getClass()) {
1107 return false;
1108 } else {
1109 OsmPrimitive that = (OsmPrimitive) obj;
1110 return id == that.id;
1111 }
1112 }
1113
1114 /**
1115 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1116 *
1117 * An primitive has the same hashcode as its incomplete counterpart.
1118 */
1119 @Override
1120 public int hashCode() {
1121 return Long.hashCode(id);
1122 }
1123
1124 @Override
1125 public Collection<String> getTemplateKeys() {
1126 Collection<String> keySet = keySet();
1127 List<String> result = new ArrayList<>(keySet.size() + 2);
1128 result.add(SPECIAL_VALUE_ID);
1129 result.add(SPECIAL_VALUE_LOCAL_NAME);
1130 result.addAll(keySet);
1131 return result;
1132 }
1133
1134 @Override
1135 public Object getTemplateValue(String name, boolean special) {
1136 if (special) {
1137 String lc = name.toLowerCase(Locale.ENGLISH);
1138 if (SPECIAL_VALUE_ID.equals(lc))
1139 return getId();
1140 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1141 return getLocalName();
1142 else
1143 return null;
1144
1145 } else
1146 return getIgnoreCase(name);
1147 }
1148
1149 @Override
1150 public boolean evaluateCondition(Match condition) {
1151 return condition.match(this);
1152 }
1153
1154 /**
1155 * Replies the set of referring relations
1156 * @param primitives primitives to fetch relations from
1157 *
1158 * @return the set of referring relations
1159 */
1160 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1161 Set<Relation> ret = new HashSet<>();
1162 for (OsmPrimitive w : primitives) {
1163 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1164 }
1165 return ret;
1166 }
1167
1168 /**
1169 * Determines if this primitive has tags denoting an area.
1170 * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1171 * @since 6491
1172 */
1173 public final boolean hasAreaTags() {
1174 return hasKey("landuse", "amenity", "building", "building:part")
1175 || hasTag("area", OsmUtils.TRUE_VALUE)
1176 || hasTag("waterway", "riverbank")
1177 || hasTagDifferent("leisure", "picnic_table", "slipway", "firepit")
1178 || hasTag("natural", "water", "wood", "scrub", "wetland", "grassland", "heath", "rock", "bare_rock",
1179 "sand", "beach", "scree", "bay", "glacier", "shingle", "fell", "reef", "stone",
1180 "mud", "landslide", "sinkhole", "crevasse", "desert");
1181 }
1182
1183 /**
1184 * Determines if this primitive semantically concerns an area.
1185 * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1186 * @since 6491
1187 */
1188 public abstract boolean concernsArea();
1189
1190 /**
1191 * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1192 * @return {@code true} if this primitive lies outside of the downloaded area
1193 */
1194 public abstract boolean isOutsideDownloadArea();
1195
1196 /**
1197 * If necessary, extend the bbox to contain this primitive
1198 * @param box a bbox instance
1199 * @param visited a set of visited members or null
1200 * @since 11269
1201 */
1202 protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited);
1203}
Note: See TracBrowser for help on using the repository browser.