001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.MessageFormat; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.Date; 012import java.util.List; 013import java.util.Locale; 014import java.util.Map; 015import java.util.Objects; 016import java.util.Set; 017import java.util.function.Consumer; 018import java.util.stream.Collectors; 019import java.util.stream.Stream; 020 021import org.openstreetmap.josm.data.osm.search.SearchCompiler; 022import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 023import org.openstreetmap.josm.data.osm.search.SearchParseError; 024import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor; 025import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 026import org.openstreetmap.josm.gui.mappaint.StyleCache; 027import org.openstreetmap.josm.spi.preferences.Config; 028import org.openstreetmap.josm.tools.CheckParameterUtil; 029import org.openstreetmap.josm.tools.Logging; 030import org.openstreetmap.josm.tools.Utils; 031import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 032 033/** 034 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}). 035 * 036 * It can be created, deleted and uploaded to the OSM-Server. 037 * 038 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass 039 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given 040 * by the server environment and not an extendible data stuff. 041 * 042 * @author imi 043 */ 044public abstract class OsmPrimitive extends AbstractPrimitive implements TemplateEngineDataProvider { 045 private static final String SPECIAL_VALUE_ID = "id"; 046 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname"; 047 048 /** 049 * A tagged way that matches this pattern has a direction. 050 * @see #FLAG_HAS_DIRECTIONS 051 */ 052 static volatile Match directionKeys; 053 054 /** 055 * A tagged way that matches this pattern has a direction that is reversed. 056 * <p> 057 * This pattern should be a subset of {@link #directionKeys} 058 * @see #FLAG_DIRECTION_REVERSED 059 */ 060 private static volatile Match reversedDirectionKeys; 061 062 static { 063 String reversedDirectionDefault = "oneway=\"-1\""; 064 065 String directionDefault = "oneway? | "+ 066 "(aerialway=chair_lift & -oneway=no) | "+ 067 "(aerialway=rope_tow & -oneway=no) | "+ 068 "(aerialway=magic_carpet & -oneway=no) | "+ 069 "(aerialway=zip_line & -oneway=no) | "+ 070 "(aerialway=drag_lift & -oneway=no) | "+ 071 "(aerialway=t-bar & -oneway=no) | "+ 072 "(aerialway=j-bar & -oneway=no) | "+ 073 "(aerialway=platter & -oneway=no) | "+ 074 "waterway=stream | waterway=river | waterway=ditch | waterway=drain | waterway=tidal_channel | "+ 075 "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+ 076 "junction=circular | junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+ 077 "(highway=motorway_link & -oneway=no & -oneway=reversible)"; 078 079 reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault); 080 directionKeys = compileDirectionKeys("tags.direction", directionDefault); 081 } 082 083 /** 084 * Replies the collection of referring primitives for the primitives in <code>primitives</code>. 085 * 086 * @param primitives the collection of primitives. 087 * @return the collection of referring primitives for the primitives in <code>primitives</code>; 088 * empty set if primitives is null or if there are no referring primitives 089 */ 090 public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) { 091 return (primitives != null ? primitives.stream() : Stream.<OsmPrimitive>empty()) 092 .flatMap(p -> p.referrers(OsmPrimitive.class)) 093 .collect(Collectors.toSet()); 094 } 095 096 /** 097 * Creates a new primitive for the given id. 098 * 099 * If allowNegativeId is set, provided id can be < 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 < 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 < 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 > 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 < 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. > 0 required 233 * @param version the version > 0 required 234 * @throws IllegalArgumentException if id <= 0 235 * @throws IllegalArgumentException if version <= 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 /* FLAGS 316 /* ------*/ 317 318 private void updateFlagsNoLock(short flag, boolean value) { 319 super.updateFlags(flag, value); 320 } 321 322 @Override 323 protected final void updateFlags(short flag, boolean value) { 324 boolean locked = writeLock(); 325 try { 326 updateFlagsNoLock(flag, value); 327 } finally { 328 writeUnlock(locked); 329 } 330 } 331 332 /** 333 * Make the primitive disabled (e.g. if a filter applies). 334 * 335 * To enable the primitive again, use unsetDisabledState. 336 * @param hidden if the primitive should be completely hidden from view or 337 * just shown in gray color. 338 * @return true, any flag has changed; false if you try to set the disabled 339 * state to the value that is already preset 340 */ 341 public boolean setDisabledState(boolean hidden) { 342 boolean locked = writeLock(); 343 try { 344 int oldFlags = flags; 345 updateFlagsNoLock(FLAG_DISABLED, true); 346 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden); 347 return oldFlags != flags; 348 } finally { 349 writeUnlock(locked); 350 } 351 } 352 353 /** 354 * Remove the disabled flag from the primitive. 355 * Afterwards, the primitive is displayed normally and can be selected again. 356 * @return {@code true} if a change occurred 357 */ 358 public boolean unsetDisabledState() { 359 boolean locked = writeLock(); 360 try { 361 int oldFlags = flags; 362 updateFlagsNoLock(FLAG_DISABLED, false); 363 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, false); 364 return oldFlags != flags; 365 } finally { 366 writeUnlock(locked); 367 } 368 } 369 370 /** 371 * Set binary property used internally by the filter mechanism. 372 * @param isExplicit new "disabled type" flag value 373 */ 374 public void setDisabledType(boolean isExplicit) { 375 updateFlags(FLAG_DISABLED_TYPE, isExplicit); 376 } 377 378 /** 379 * Set binary property used internally by the filter mechanism. 380 * @param isExplicit new "hidden type" flag value 381 */ 382 public void setHiddenType(boolean isExplicit) { 383 updateFlags(FLAG_HIDDEN_TYPE, isExplicit); 384 } 385 386 /** 387 * Set binary property used internally by the filter mechanism. 388 * @param isPreserved new "preserved" flag value 389 * @since 13309 390 */ 391 public void setPreserved(boolean isPreserved) { 392 updateFlags(FLAG_PRESERVED, isPreserved); 393 } 394 395 @Override 396 public boolean isDisabled() { 397 return (flags & FLAG_DISABLED) != 0; 398 } 399 400 @Override 401 public boolean isDisabledAndHidden() { 402 return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0); 403 } 404 405 /** 406 * Get binary property used internally by the filter mechanism. 407 * @return {@code true} if this object has the "hidden type" flag enabled 408 */ 409 public boolean getHiddenType() { 410 return (flags & FLAG_HIDDEN_TYPE) != 0; 411 } 412 413 /** 414 * Get binary property used internally by the filter mechanism. 415 * @return {@code true} if this object has the "disabled type" flag enabled 416 */ 417 public boolean getDisabledType() { 418 return (flags & FLAG_DISABLED_TYPE) != 0; 419 } 420 421 @Override 422 public boolean isPreserved() { 423 return (flags & FLAG_PRESERVED) != 0; 424 } 425 426 @Override 427 public boolean isSelectable() { 428 // not synchronized -> check disabled twice just to be sure we did not have a race condition. 429 return !isDisabled() && isDrawable() && !isDisabled(); 430 } 431 432 @Override 433 public boolean isDrawable() { 434 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0; 435 } 436 437 @Override 438 public void setModified(boolean modified) { 439 checkDatasetNotReadOnly(); 440 boolean locked = writeLock(); 441 try { 442 super.setModified(modified); 443 if (dataSet != null) { 444 dataSet.firePrimitiveFlagsChanged(this); 445 } 446 clearCachedStyle(); 447 } finally { 448 writeUnlock(locked); 449 } 450 } 451 452 @Override 453 public void setVisible(boolean visible) { 454 checkDatasetNotReadOnly(); 455 boolean locked = writeLock(); 456 try { 457 super.setVisible(visible); 458 clearCachedStyle(); 459 } finally { 460 writeUnlock(locked); 461 } 462 } 463 464 @Override 465 public void setDeleted(boolean deleted) { 466 checkDatasetNotReadOnly(); 467 boolean locked = writeLock(); 468 try { 469 super.setDeleted(deleted); 470 if (dataSet != null) { 471 if (deleted) { 472 dataSet.firePrimitivesRemoved(Collections.singleton(this), false); 473 } else { 474 dataSet.firePrimitivesAdded(Collections.singleton(this), false); 475 } 476 } 477 clearCachedStyle(); 478 } finally { 479 writeUnlock(locked); 480 } 481 } 482 483 @Override 484 protected final void setIncomplete(boolean incomplete) { 485 checkDatasetNotReadOnly(); 486 boolean locked = writeLock(); 487 try { 488 if (dataSet != null && incomplete != this.isIncomplete()) { 489 if (incomplete) { 490 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true); 491 } else { 492 dataSet.firePrimitivesAdded(Collections.singletonList(this), true); 493 } 494 } 495 super.setIncomplete(incomplete); 496 } finally { 497 writeUnlock(locked); 498 } 499 } 500 501 @Override 502 public boolean isSelected() { 503 return dataSet != null && dataSet.isSelected(this); 504 } 505 506 @Override 507 public boolean isMemberOfSelected() { 508 if (referrers == null) 509 return false; 510 if (referrers instanceof OsmPrimitive) 511 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected(); 512 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) { 513 if (ref instanceof Relation && ref.isSelected()) 514 return true; 515 } 516 return false; 517 } 518 519 @Override 520 public boolean isOuterMemberOfSelected() { 521 if (referrers == null) 522 return false; 523 if (referrers instanceof OsmPrimitive) { 524 return isOuterMemberOfMultipolygon((OsmPrimitive) referrers); 525 } 526 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) { 527 if (isOuterMemberOfMultipolygon(ref)) 528 return true; 529 } 530 return false; 531 } 532 533 private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) { 534 if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) { 535 for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) { 536 if ("outer".equals(rm.getRole())) { 537 return true; 538 } 539 } 540 } 541 return false; 542 } 543 544 @Override 545 public void setHighlighted(boolean highlighted) { 546 if (isHighlighted() != highlighted) { 547 updateFlags(FLAG_HIGHLIGHTED, highlighted); 548 if (dataSet != null) { 549 dataSet.fireHighlightingChanged(); 550 } 551 } 552 } 553 554 @Override 555 public boolean isHighlighted() { 556 return (flags & FLAG_HIGHLIGHTED) != 0; 557 } 558 559 /*--------------- 560 * DIRECTION KEYS 561 *---------------*/ 562 563 private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError { 564 try { 565 return SearchCompiler.compile(Config.getPref().get(prefName, defaultValue)); 566 } catch (SearchParseError e) { 567 Logging.log(Logging.LEVEL_ERROR, "Unable to compile pattern for " + prefName + ", trying default pattern:", e); 568 } 569 570 try { 571 return SearchCompiler.compile(defaultValue); 572 } catch (SearchParseError e2) { 573 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2); 574 } 575 } 576 577 private void updateTagged() { 578 for (String key: keySet()) { 579 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects) 580 // but it's clearly not enough to consider an object as tagged (see #9261) 581 if (!isUninterestingKey(key) && !"area".equals(key)) { 582 updateFlagsNoLock(FLAG_TAGGED, true); 583 return; 584 } 585 } 586 updateFlagsNoLock(FLAG_TAGGED, false); 587 } 588 589 private void updateAnnotated() { 590 for (String key: keySet()) { 591 if (getWorkInProgressKeys().contains(key)) { 592 updateFlagsNoLock(FLAG_ANNOTATED, true); 593 return; 594 } 595 } 596 updateFlagsNoLock(FLAG_ANNOTATED, false); 597 } 598 599 @Override 600 public boolean isTagged() { 601 return (flags & FLAG_TAGGED) != 0; 602 } 603 604 @Override 605 public boolean isAnnotated() { 606 return (flags & FLAG_ANNOTATED) != 0; 607 } 608 609 private void updateDirectionFlags() { 610 boolean hasDirections = false; 611 boolean directionReversed = false; 612 if (reversedDirectionKeys.match(this)) { 613 hasDirections = true; 614 directionReversed = true; 615 } 616 if (directionKeys.match(this)) { 617 hasDirections = true; 618 } 619 620 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed); 621 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections); 622 } 623 624 @Override 625 public boolean hasDirectionKeys() { 626 return (flags & FLAG_HAS_DIRECTIONS) != 0; 627 } 628 629 @Override 630 public boolean reversedDirection() { 631 return (flags & FLAG_DIRECTION_REVERSED) != 0; 632 } 633 634 /*------------ 635 * Keys handling 636 ------------*/ 637 638 @Override 639 public final void setKeys(TagMap keys) { 640 checkDatasetNotReadOnly(); 641 boolean locked = writeLock(); 642 try { 643 super.setKeys(keys); 644 } finally { 645 writeUnlock(locked); 646 } 647 } 648 649 @Override 650 public final void setKeys(Map<String, String> keys) { 651 checkDatasetNotReadOnly(); 652 boolean locked = writeLock(); 653 try { 654 super.setKeys(keys); 655 } finally { 656 writeUnlock(locked); 657 } 658 } 659 660 @Override 661 public final void put(String key, String value) { 662 checkDatasetNotReadOnly(); 663 boolean locked = writeLock(); 664 try { 665 super.put(key, value); 666 } finally { 667 writeUnlock(locked); 668 } 669 } 670 671 @Override 672 public final void remove(String key) { 673 checkDatasetNotReadOnly(); 674 boolean locked = writeLock(); 675 try { 676 super.remove(key); 677 } finally { 678 writeUnlock(locked); 679 } 680 } 681 682 @Override 683 public final void removeAll() { 684 checkDatasetNotReadOnly(); 685 boolean locked = writeLock(); 686 try { 687 super.removeAll(); 688 } finally { 689 writeUnlock(locked); 690 } 691 } 692 693 @Override 694 protected void keysChangedImpl(Map<String, String> originalKeys) { 695 clearCachedStyle(); 696 if (dataSet != null) { 697 for (OsmPrimitive ref : getReferrers()) { 698 ref.clearCachedStyle(); 699 } 700 } 701 updateDirectionFlags(); 702 updateTagged(); 703 updateAnnotated(); 704 if (dataSet != null) { 705 dataSet.fireTagsChanged(this, originalKeys); 706 } 707 } 708 709 /*------------ 710 * Referrers 711 ------------*/ 712 713 private Object referrers; 714 715 /** 716 * Add new referrer. If referrer is already included then no action is taken 717 * @param referrer The referrer to add 718 */ 719 protected void addReferrer(OsmPrimitive referrer) { 720 checkDatasetNotReadOnly(); 721 if (referrers == null) { 722 referrers = referrer; 723 } else if (referrers instanceof OsmPrimitive) { 724 if (referrers != referrer) { 725 referrers = new OsmPrimitive[] {(OsmPrimitive) referrers, referrer}; 726 } 727 } else { 728 for (OsmPrimitive primitive:(OsmPrimitive[]) referrers) { 729 if (primitive == referrer) 730 return; 731 } 732 referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer); 733 } 734 } 735 736 /** 737 * Remove referrer. No action is taken if referrer is not registered 738 * @param referrer The referrer to remove 739 */ 740 protected void removeReferrer(OsmPrimitive referrer) { 741 checkDatasetNotReadOnly(); 742 if (referrers instanceof OsmPrimitive) { 743 if (referrers == referrer) { 744 referrers = null; 745 } 746 } else if (referrers instanceof OsmPrimitive[]) { 747 OsmPrimitive[] orig = (OsmPrimitive[]) referrers; 748 int idx = -1; 749 for (int i = 0; i < orig.length; i++) { 750 if (orig[i] == referrer) { 751 idx = i; 752 break; 753 } 754 } 755 if (idx == -1) 756 return; 757 758 if (orig.length == 2) { 759 referrers = orig[1-idx]; // idx is either 0 or 1, take the other 760 } else { // downsize the array 761 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1]; 762 System.arraycopy(orig, 0, smaller, 0, idx); 763 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx); 764 referrers = smaller; 765 } 766 } 767 } 768 769 private <T extends OsmPrimitive> Stream<T> referrers(boolean allowWithoutDataset, Class<T> filter) { 770 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example 771 // when way is cloned 772 773 if (dataSet == null && allowWithoutDataset) { 774 return Stream.empty(); 775 } 776 checkDataset(); 777 if (referrers == null) { 778 return Stream.empty(); 779 } 780 final Stream<OsmPrimitive> stream = referrers instanceof OsmPrimitive // NOPMD 781 ? Stream.of((OsmPrimitive) referrers) 782 : Arrays.stream((OsmPrimitive[]) referrers); 783 return stream 784 .filter(p -> p.dataSet == dataSet) 785 .filter(filter::isInstance) 786 .map(filter::cast); 787 } 788 789 /** 790 * Gets all primitives in the current dataset that reference this primitive. 791 * @param filter restrict primitives to subclasses 792 * @param <T> type of primitives 793 * @return the referrers as Stream 794 * @since 14654 795 */ 796 public final <T extends OsmPrimitive> Stream<T> referrers(Class<T> filter) { 797 return referrers(false, filter); 798 } 799 800 @Override 801 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) { 802 return referrers(allowWithoutDataset, OsmPrimitive.class) 803 .collect(Collectors.toList()); 804 } 805 806 @Override 807 public final List<OsmPrimitive> getReferrers() { 808 return getReferrers(false); 809 } 810 811 /** 812 * <p>Visits {@code visitor} for all referrers.</p> 813 * 814 * @param visitor the visitor. Ignored, if null. 815 * @since 12809 816 */ 817 public void visitReferrers(OsmPrimitiveVisitor visitor) { 818 if (visitor != null) 819 doVisitReferrers(o -> o.accept(visitor)); 820 } 821 822 @Override 823 public void visitReferrers(PrimitiveVisitor visitor) { 824 if (visitor != null) 825 doVisitReferrers(o -> o.accept(visitor)); 826 } 827 828 private void doVisitReferrers(Consumer<OsmPrimitive> visitor) { 829 if (this.referrers == null) 830 return; 831 else if (this.referrers instanceof OsmPrimitive) { 832 OsmPrimitive ref = (OsmPrimitive) this.referrers; 833 if (ref.dataSet == dataSet) { 834 visitor.accept(ref); 835 } 836 } else if (this.referrers instanceof OsmPrimitive[]) { 837 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers; 838 for (OsmPrimitive ref: refs) { 839 if (ref.dataSet == dataSet) { 840 visitor.accept(ref); 841 } 842 } 843 } 844 } 845 846 /** 847 * Return true, if this primitive is a node referred by at least n ways 848 * @param n Minimal number of ways to return true. Must be positive 849 * @return {@code true} if this primitive is referred by at least n ways 850 */ 851 protected final boolean isNodeReferredByWays(int n) { 852 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example 853 // when way is cloned 854 if (referrers == null) return false; 855 checkDataset(); 856 if (referrers instanceof OsmPrimitive) 857 return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet; 858 else { 859 int counter = 0; 860 for (OsmPrimitive o : (OsmPrimitive[]) referrers) { 861 if (dataSet == o.dataSet && o instanceof Way && ++counter >= n) 862 return true; 863 } 864 return false; 865 } 866 } 867 868 /*----------------- 869 * OTHER METHODS 870 *----------------*/ 871 872 /** 873 * Implementation of the visitor scheme. Subclasses have to call the correct 874 * visitor function. 875 * @param visitor The visitor from which the visit() function must be called. 876 * @since 12809 877 */ 878 public abstract void accept(OsmPrimitiveVisitor visitor); 879 880 /** 881 * Get and write all attributes from the parameter. Does not fire any listener, so 882 * use this only in the data initializing phase 883 * @param other other primitive 884 */ 885 public void cloneFrom(OsmPrimitive other) { 886 // write lock is provided by subclasses 887 if (id != other.id && dataSet != null) 888 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset"); 889 890 super.cloneFrom(other); 891 clearCachedStyle(); 892 } 893 894 /** 895 * Merges the technical and semantical attributes from <code>other</code> onto this. 896 * 897 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> 898 * have an assigned OSM id, the IDs have to be the same. 899 * 900 * @param other the other primitive. Must not be null. 901 * @throws IllegalArgumentException if other is null. 902 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not 903 * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId() 904 */ 905 public void mergeFrom(OsmPrimitive other) { 906 checkDatasetNotReadOnly(); 907 boolean locked = writeLock(); 908 try { 909 CheckParameterUtil.ensureParameterNotNull(other, "other"); 910 if (other.isNew() ^ isNew()) 911 throw new DataIntegrityProblemException( 912 tr("Cannot merge because either of the participating primitives is new and the other is not")); 913 if (!other.isNew() && other.getId() != id) 914 throw new DataIntegrityProblemException( 915 tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId())); 916 917 setKeys(other.hasKeys() ? other.getKeys() : null); 918 timestamp = other.timestamp; 919 version = other.version; 920 setIncomplete(other.isIncomplete()); 921 flags = other.flags; 922 user = other.user; 923 changesetId = other.changesetId; 924 } finally { 925 writeUnlock(locked); 926 } 927 } 928 929 /** 930 * Replies true if this primitive and other are equal with respect to their semantic attributes. 931 * <ol> 932 * <li>equal id</li> 933 * <li>both are complete or both are incomplete</li> 934 * <li>both have the same tags</li> 935 * </ol> 936 * @param other other primitive to compare 937 * @return true if this primitive and other are equal with respect to their semantic attributes. 938 */ 939 public final boolean hasEqualSemanticAttributes(OsmPrimitive other) { 940 return hasEqualSemanticAttributes(other, true); 941 } 942 943 boolean hasEqualSemanticFlags(final OsmPrimitive other) { 944 if (!isNew() && id != other.id) 945 return false; 946 return !(isIncomplete() ^ other.isIncomplete()); // exclusive or operator for performance (see #7159) 947 } 948 949 boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) { 950 return hasEqualSemanticFlags(other) 951 && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys())); 952 } 953 954 /** 955 * Replies true if this primitive and other are equal with respect to their technical attributes. 956 * The attributes: 957 * <ol> 958 * <li>deleted</li> 959 * <li>modified</li> 960 * <li>timestamp</li> 961 * <li>version</li> 962 * <li>visible</li> 963 * <li>user</li> 964 * </ol> 965 * have to be equal 966 * @param other the other primitive 967 * @return true if this primitive and other are equal with respect to their technical attributes 968 */ 969 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) { 970 // CHECKSTYLE.OFF: BooleanExpressionComplexity 971 return other != null 972 && timestamp == other.timestamp 973 && version == other.version 974 && changesetId == other.changesetId 975 && isDeleted() == other.isDeleted() 976 && isModified() == other.isModified() 977 && isVisible() == other.isVisible() 978 && Objects.equals(user, other.user); 979 // CHECKSTYLE.ON: BooleanExpressionComplexity 980 } 981 982 /** 983 * Loads (clone) this primitive from provided PrimitiveData 984 * @param data The object which should be cloned 985 */ 986 public void load(PrimitiveData data) { 987 checkDatasetNotReadOnly(); 988 // Write lock is provided by subclasses 989 setKeys(data.hasKeys() ? data.getKeys() : null); 990 setRawTimestamp(data.getRawTimestamp()); 991 user = data.getUser(); 992 setChangesetId(data.getChangesetId()); 993 setDeleted(data.isDeleted()); 994 setModified(data.isModified()); 995 setVisible(data.isVisible()); 996 setIncomplete(data.isIncomplete()); 997 version = data.getVersion(); 998 } 999 1000 /** 1001 * Save parameters of this primitive to the transport object 1002 * @return The saved object data 1003 */ 1004 public abstract PrimitiveData save(); 1005 1006 /** 1007 * Save common parameters of primitives to the transport object 1008 * @param data The object to save the data into 1009 */ 1010 protected void saveCommonAttributes(PrimitiveData data) { 1011 data.setId(id); 1012 data.setKeys(hasKeys() ? getKeys() : null); 1013 data.setRawTimestamp(getRawTimestamp()); 1014 data.setUser(user); 1015 data.setDeleted(isDeleted()); 1016 data.setModified(isModified()); 1017 data.setVisible(isVisible()); 1018 data.setIncomplete(isIncomplete()); 1019 data.setChangesetId(changesetId); 1020 data.setVersion(version); 1021 } 1022 1023 /** 1024 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...) 1025 */ 1026 public abstract void updatePosition(); 1027 1028 /*---------------- 1029 * OBJECT METHODS 1030 *---------------*/ 1031 1032 @Override 1033 protected String getFlagsAsString() { 1034 StringBuilder builder = new StringBuilder(super.getFlagsAsString()); 1035 1036 if (isDisabled()) { 1037 if (isDisabledAndHidden()) { 1038 builder.append('h'); 1039 } else { 1040 builder.append('d'); 1041 } 1042 } 1043 if (isTagged()) { 1044 builder.append('T'); 1045 } 1046 if (hasDirectionKeys()) { 1047 if (reversedDirection()) { 1048 builder.append('<'); 1049 } else { 1050 builder.append('>'); 1051 } 1052 } 1053 return builder.toString(); 1054 } 1055 1056 /** 1057 * Equal, if the id (and class) is equal. 1058 * 1059 * An primitive is equal to its incomplete counter part. 1060 */ 1061 @Override 1062 public boolean equals(Object obj) { 1063 if (this == obj) { 1064 return true; 1065 } else if (obj == null || getClass() != obj.getClass()) { 1066 return false; 1067 } else { 1068 OsmPrimitive that = (OsmPrimitive) obj; 1069 return id == that.id; 1070 } 1071 } 1072 1073 /** 1074 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0. 1075 * 1076 * An primitive has the same hashcode as its incomplete counterpart. 1077 */ 1078 @Override 1079 public int hashCode() { 1080 return Long.hashCode(id); 1081 } 1082 1083 @Override 1084 public Collection<String> getTemplateKeys() { 1085 Collection<String> keySet = keySet(); 1086 List<String> result = new ArrayList<>(keySet.size() + 2); 1087 result.add(SPECIAL_VALUE_ID); 1088 result.add(SPECIAL_VALUE_LOCAL_NAME); 1089 result.addAll(keySet); 1090 return result; 1091 } 1092 1093 @Override 1094 public Object getTemplateValue(String name, boolean special) { 1095 if (special) { 1096 String lc = name.toLowerCase(Locale.ENGLISH); 1097 if (SPECIAL_VALUE_ID.equals(lc)) 1098 return getId(); 1099 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc)) 1100 return getLocalName(); 1101 else 1102 return null; 1103 1104 } else 1105 return getIgnoreCase(name); 1106 } 1107 1108 @Override 1109 public boolean evaluateCondition(Match condition) { 1110 return condition.match(this); 1111 } 1112 1113 /** 1114 * Replies the set of referring relations 1115 * @param primitives primitives to fetch relations from 1116 * 1117 * @return the set of referring relations 1118 */ 1119 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) { 1120 return primitives.stream() 1121 .flatMap(p -> p.referrers(Relation.class)) 1122 .collect(Collectors.toSet()); 1123 } 1124 1125 /** 1126 * Determines if this primitive has tags denoting an area. 1127 * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise. 1128 * @since 6491 1129 */ 1130 public final boolean hasAreaTags() { 1131 return hasKey("landuse", "amenity", "building", "building:part") 1132 || hasTag("area", OsmUtils.TRUE_VALUE) 1133 || hasTag("waterway", "riverbank") 1134 || hasTagDifferent("leisure", "picnic_table", "slipway", "firepit") 1135 || hasTag("natural", "water", "wood", "scrub", "wetland", "grassland", "heath", "rock", "bare_rock", 1136 "sand", "beach", "scree", "bay", "glacier", "shingle", "fell", "reef", "stone", 1137 "mud", "landslide", "sinkhole", "crevasse", "desert"); 1138 } 1139 1140 /** 1141 * Determines if this primitive semantically concerns an area. 1142 * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise. 1143 * @since 6491 1144 */ 1145 public abstract boolean concernsArea(); 1146 1147 /** 1148 * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}. 1149 * @return {@code true} if this primitive lies outside of the downloaded area 1150 */ 1151 public abstract boolean isOutsideDownloadArea(); 1152 1153 /** 1154 * If necessary, extend the bbox to contain this primitive 1155 * @param box a bbox instance 1156 * @param visited a set of visited members or null 1157 * @since 11269 1158 */ 1159 protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited); 1160}