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 /* ------- 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. 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}