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

Last change on this file since 14905 was 14905, checked in by GerdP, 5 years ago

fix #17440: remove confusing code
fixes several sonar lint issues "Local variables should not shadow class fields"

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