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

Last change on this file since 15007 was 15007, checked in by Don-vip, 5 years ago

remove deprecated API

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