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

Last change on this file since 16992 was 16992, checked in by Klumbumbus, 4 years ago

fix #19713 - Add junction=circular (icon self created, PD and CC0 licensed) and remove default oneway arrows in case of oneway=no; add preset links between roundabout, mini-roundabout and circular junction

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