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