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.HashMap; 013import java.util.HashSet; 014import java.util.LinkedHashSet; 015import java.util.LinkedList; 016import java.util.List; 017import java.util.Map; 018import java.util.Set; 019 020import org.openstreetmap.josm.Main; 021import org.openstreetmap.josm.actions.search.SearchCompiler; 022import org.openstreetmap.josm.actions.search.SearchCompiler.Match; 023import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 024import org.openstreetmap.josm.data.osm.visitor.Visitor; 025import org.openstreetmap.josm.gui.mappaint.StyleCache; 026import org.openstreetmap.josm.tools.CheckParameterUtil; 027import org.openstreetmap.josm.tools.Predicate; 028import org.openstreetmap.josm.tools.Utils; 029import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 030 031/** 032 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}). 033 * 034 * It can be created, deleted and uploaded to the OSM-Server. 035 * 036 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass 037 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given 038 * by the server environment and not an extendible data stuff. 039 * 040 * @author imi 041 */ 042public abstract class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider { 043 private static final String SPECIAL_VALUE_ID = "id"; 044 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname"; 045 046 047 /** 048 * An object can be disabled by the filter mechanism. 049 * Then it will show in a shade of gray on the map or it is completely 050 * hidden from the view. 051 * Disabled objects usually cannot be selected or modified 052 * while the filter is active. 053 */ 054 protected static final int FLAG_DISABLED = 1 << 4; 055 056 /** 057 * This flag is only relevant if an object is disabled by the 058 * filter mechanism (i.e. FLAG_DISABLED is set). 059 * Then it indicates, whether it is completely hidden or 060 * just shown in gray color. 061 * 062 * When the primitive is not disabled, this flag should be 063 * unset as well (for efficient access). 064 */ 065 protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5; 066 067 /** 068 * Flag used internally by the filter mechanism. 069 */ 070 protected static final int FLAG_DISABLED_TYPE = 1 << 6; 071 072 /** 073 * Flag used internally by the filter mechanism. 074 */ 075 protected static final int FLAG_HIDDEN_TYPE = 1 << 7; 076 077 /** 078 * This flag is set if the primitive is a way and 079 * according to the tags, the direction of the way is important. 080 * (e.g. one way street.) 081 */ 082 protected static final int FLAG_HAS_DIRECTIONS = 1 << 8; 083 084 /** 085 * If the primitive is tagged. 086 * Some trivial tags like source=* are ignored here. 087 */ 088 protected static final int FLAG_TAGGED = 1 << 9; 089 090 /** 091 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set. 092 * It shows, that direction of the arrows should be reversed. 093 * (E.g. oneway=-1.) 094 */ 095 protected static final int FLAG_DIRECTION_REVERSED = 1 << 10; 096 097 /** 098 * When hovering over ways and nodes in add mode, the 099 * "target" objects are visually highlighted. This flag indicates 100 * that the primitive is currently highlighted. 101 */ 102 protected static final int FLAG_HIGHLIGHTED = 1 << 11; 103 104 /** 105 * If the primitive is annotated with a tag such as note, fixme, etc. 106 * Match the "work in progress" tags in default map style. 107 */ 108 protected static final int FLAG_ANNOTATED = 1 << 12; 109 110 /** 111 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in 112 * another collection of {@link OsmPrimitive}s. The result collection is a list. 113 * 114 * If <code>list</code> is null, replies an empty list. 115 * 116 * @param <T> 117 * @param list the original list 118 * @param type the type to filter for 119 * @return the sub-list of OSM primitives of type <code>type</code> 120 */ 121 public static <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) { 122 if (list == null) return Collections.emptyList(); 123 List<T> ret = new LinkedList<>(); 124 for(OsmPrimitive p: list) { 125 if (type.isInstance(p)) { 126 ret.add(type.cast(p)); 127 } 128 } 129 return ret; 130 } 131 132 /** 133 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in 134 * another collection of {@link OsmPrimitive}s. The result collection is a set. 135 * 136 * If <code>list</code> is null, replies an empty set. 137 * 138 * @param <T> type of data (must be one of the {@link OsmPrimitive} types 139 * @param set the original collection 140 * @param type the type to filter for 141 * @return the sub-set of OSM primitives of type <code>type</code> 142 */ 143 public static <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) { 144 Set<T> ret = new LinkedHashSet<>(); 145 if (set != null) { 146 for(OsmPrimitive p: set) { 147 if (type.isInstance(p)) { 148 ret.add(type.cast(p)); 149 } 150 } 151 } 152 return ret; 153 } 154 155 /** 156 * Replies the collection of referring primitives for the primitives in <code>primitives</code>. 157 * 158 * @param primitives the collection of primitives. 159 * @return the collection of referring primitives for the primitives in <code>primitives</code>; 160 * empty set if primitives is null or if there are no referring primitives 161 */ 162 public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) { 163 HashSet<OsmPrimitive> ret = new HashSet<>(); 164 if (primitives == null || primitives.isEmpty()) return ret; 165 for (OsmPrimitive p: primitives) { 166 ret.addAll(p.getReferrers()); 167 } 168 return ret; 169 } 170 171 /** 172 * Some predicates, that describe conditions on primitives. 173 */ 174 public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() { 175 @Override public boolean evaluate(OsmPrimitive primitive) { 176 return primitive.isUsable(); 177 } 178 }; 179 180 public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() { 181 @Override public boolean evaluate(OsmPrimitive primitive) { 182 return primitive.isSelectable(); 183 } 184 }; 185 186 public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() { 187 @Override public boolean evaluate(OsmPrimitive primitive) { 188 return !primitive.isDeleted(); 189 } 190 }; 191 192 public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() { 193 @Override public boolean evaluate(OsmPrimitive primitive) { 194 return !primitive.isDeleted() && !primitive.isIncomplete(); 195 } 196 }; 197 198 public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() { 199 @Override public boolean evaluate(OsmPrimitive primitive) { 200 return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation); 201 } 202 }; 203 204 public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() { 205 @Override public boolean evaluate(OsmPrimitive primitive) { 206 return primitive.isModified(); 207 } 208 }; 209 210 public static final Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() { 211 @Override public boolean evaluate(OsmPrimitive primitive) { 212 return primitive.getClass() == Node.class; 213 } 214 }; 215 216 public static final Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() { 217 @Override public boolean evaluate(OsmPrimitive primitive) { 218 return primitive.getClass() == Way.class; 219 } 220 }; 221 222 public static final Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() { 223 @Override public boolean evaluate(OsmPrimitive primitive) { 224 return primitive.getClass() == Relation.class; 225 } 226 }; 227 228 public static final Predicate<OsmPrimitive> multipolygonPredicate = new Predicate<OsmPrimitive>() { 229 @Override public boolean evaluate(OsmPrimitive primitive) { 230 return primitive.getClass() == Relation.class && ((Relation) primitive).isMultipolygon(); 231 } 232 }; 233 234 public static final Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() { 235 @Override public boolean evaluate(OsmPrimitive primitive) { 236 return true; 237 } 238 }; 239 240 /** 241 * Creates a new primitive for the given id. 242 * 243 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing. 244 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or 245 * positive number. 246 * 247 * @param id the id 248 * @param allowNegativeId 249 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false 250 */ 251 protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException { 252 if (allowNegativeId) { 253 this.id = id; 254 } else { 255 if (id < 0) 256 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id)); 257 else if (id == 0) { 258 this.id = generateUniqueId(); 259 } else { 260 this.id = id; 261 } 262 263 } 264 this.version = 0; 265 this.setIncomplete(id > 0); 266 } 267 268 /** 269 * Creates a new primitive for the given id and version. 270 * 271 * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing. 272 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or 273 * positive number. 274 * 275 * If id is not > 0 version is ignored and set to 0. 276 * 277 * @param id 278 * @param version 279 * @param allowNegativeId 280 * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false 281 */ 282 protected OsmPrimitive(long id, int version, boolean allowNegativeId) throws IllegalArgumentException { 283 this(id, allowNegativeId); 284 this.version = (id > 0 ? version : 0); 285 setIncomplete(id > 0 && version == 0); 286 } 287 288 289 /*---------- 290 * MAPPAINT 291 *--------*/ 292 public StyleCache mappaintStyle = null; 293 public int mappaintCacheIdx; 294 295 /* This should not be called from outside. Fixing the UI to add relevant 296 get/set functions calling this implicitely is preferred, so we can have 297 transparent cache handling in the future. */ 298 public void clearCachedStyle() 299 { 300 mappaintStyle = null; 301 } 302 /* end of mappaint data */ 303 304 /*--------- 305 * DATASET 306 *---------*/ 307 308 /** the parent dataset */ 309 private DataSet dataSet; 310 311 /** 312 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods 313 * @param dataSet 314 */ 315 void setDataset(DataSet dataSet) { 316 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet) 317 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset"); 318 this.dataSet = dataSet; 319 } 320 321 /** 322 * 323 * @return DataSet this primitive is part of. 324 */ 325 public DataSet getDataSet() { 326 return dataSet; 327 } 328 329 /** 330 * Throws exception if primitive is not part of the dataset 331 */ 332 public void checkDataset() { 333 if (dataSet == null) 334 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString()); 335 } 336 337 protected boolean writeLock() { 338 if (dataSet != null) { 339 dataSet.beginUpdate(); 340 return true; 341 } else 342 return false; 343 } 344 345 protected void writeUnlock(boolean locked) { 346 if (locked) { 347 // It shouldn't be possible for dataset to become null because method calling setDataset would need write lock which is owned by this thread 348 dataSet.endUpdate(); 349 } 350 } 351 352 /** 353 * Sets the id and the version of this primitive if it is known to the OSM API. 354 * 355 * Since we know the id and its version it can't be incomplete anymore. incomplete 356 * is set to false. 357 * 358 * @param id the id. > 0 required 359 * @param version the version > 0 required 360 * @throws IllegalArgumentException thrown if id <= 0 361 * @throws IllegalArgumentException thrown if version <= 0 362 * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset 363 */ 364 @Override 365 public void setOsmId(long id, int version) { 366 boolean locked = writeLock(); 367 try { 368 if (id <= 0) 369 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id)); 370 if (version <= 0) 371 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version)); 372 if (dataSet != null && id != this.id) { 373 DataSet datasetCopy = dataSet; 374 // Reindex primitive 375 datasetCopy.removePrimitive(this); 376 this.id = id; 377 datasetCopy.addPrimitive(this); 378 } 379 super.setOsmId(id, version); 380 } finally { 381 writeUnlock(locked); 382 } 383 } 384 385 /** 386 * Clears the metadata, including id and version known to the OSM API. 387 * The id is a new unique id. The version, changeset and timestamp are set to 0. 388 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead 389 * 390 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}. 391 * 392 * @throws DataIntegrityProblemException If primitive was already added to the dataset 393 * @since 6140 394 */ 395 @Override 396 public void clearOsmMetadata() { 397 if (dataSet != null) 398 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset"); 399 super.clearOsmMetadata(); 400 } 401 402 @Override 403 public void setUser(User user) { 404 boolean locked = writeLock(); 405 try { 406 super.setUser(user); 407 } finally { 408 writeUnlock(locked); 409 } 410 } 411 412 @Override 413 public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException { 414 boolean locked = writeLock(); 415 try { 416 int old = this.changesetId; 417 super.setChangesetId(changesetId); 418 if (dataSet != null) { 419 dataSet.fireChangesetIdChanged(this, old, changesetId); 420 } 421 } finally { 422 writeUnlock(locked); 423 } 424 } 425 426 @Override 427 public void setTimestamp(Date timestamp) { 428 boolean locked = writeLock(); 429 try { 430 super.setTimestamp(timestamp); 431 } finally { 432 writeUnlock(locked); 433 } 434 } 435 436 437 /* ------- 438 /* FLAGS 439 /* ------*/ 440 441 private void updateFlagsNoLock (int flag, boolean value) { 442 super.updateFlags(flag, value); 443 } 444 445 @Override 446 protected final void updateFlags(int flag, boolean value) { 447 boolean locked = writeLock(); 448 try { 449 updateFlagsNoLock(flag, value); 450 } finally { 451 writeUnlock(locked); 452 } 453 } 454 455 /** 456 * Make the primitive disabled (e.g. if a filter applies). 457 * 458 * To enable the primitive again, use unsetDisabledState. 459 * @param hidden if the primitive should be completely hidden from view or 460 * just shown in gray color. 461 * @return true, any flag has changed; false if you try to set the disabled 462 * state to the value that is already preset 463 */ 464 public boolean setDisabledState(boolean hidden) { 465 boolean locked = writeLock(); 466 try { 467 int oldFlags = flags; 468 updateFlagsNoLock(FLAG_DISABLED, true); 469 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden); 470 return oldFlags != flags; 471 } finally { 472 writeUnlock(locked); 473 } 474 } 475 476 /** 477 * Remove the disabled flag from the primitive. 478 * Afterwards, the primitive is displayed normally and can be selected 479 * again. 480 */ 481 public boolean unsetDisabledState() { 482 boolean locked = writeLock(); 483 try { 484 int oldFlags = flags; 485 updateFlagsNoLock(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false); 486 return oldFlags != flags; 487 } finally { 488 writeUnlock(locked); 489 } 490 } 491 492 /** 493 * Set binary property used internally by the filter mechanism. 494 */ 495 public void setDisabledType(boolean isExplicit) { 496 updateFlags(FLAG_DISABLED_TYPE, isExplicit); 497 } 498 499 /** 500 * Set binary property used internally by the filter mechanism. 501 */ 502 public void setHiddenType(boolean isExplicit) { 503 updateFlags(FLAG_HIDDEN_TYPE, isExplicit); 504 } 505 506 /** 507 * Replies true, if this primitive is disabled. (E.g. a filter 508 * applies) 509 */ 510 public boolean isDisabled() { 511 return (flags & FLAG_DISABLED) != 0; 512 } 513 514 /** 515 * Replies true, if this primitive is disabled and marked as 516 * completely hidden on the map. 517 */ 518 public boolean isDisabledAndHidden() { 519 return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0)); 520 } 521 522 /** 523 * Get binary property used internally by the filter mechanism. 524 */ 525 public boolean getHiddenType() { 526 return (flags & FLAG_HIDDEN_TYPE) != 0; 527 } 528 529 /** 530 * Get binary property used internally by the filter mechanism. 531 */ 532 public boolean getDisabledType() { 533 return (flags & FLAG_DISABLED_TYPE) != 0; 534 } 535 536 public boolean isSelectable() { 537 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0; 538 } 539 540 public boolean isDrawable() { 541 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0; 542 } 543 544 @Override 545 public void setVisible(boolean visible) throws IllegalStateException { 546 boolean locked = writeLock(); 547 try { 548 super.setVisible(visible); 549 } finally { 550 writeUnlock(locked); 551 } 552 } 553 554 @Override 555 public void setDeleted(boolean deleted) { 556 boolean locked = writeLock(); 557 try { 558 super.setDeleted(deleted); 559 if (dataSet != null) { 560 if (deleted) { 561 dataSet.firePrimitivesRemoved(Collections.singleton(this), false); 562 } else { 563 dataSet.firePrimitivesAdded(Collections.singleton(this), false); 564 } 565 } 566 } finally { 567 writeUnlock(locked); 568 } 569 } 570 571 @Override 572 protected final void setIncomplete(boolean incomplete) { 573 boolean locked = writeLock(); 574 try { 575 if (dataSet != null && incomplete != this.isIncomplete()) { 576 if (incomplete) { 577 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true); 578 } else { 579 dataSet.firePrimitivesAdded(Collections.singletonList(this), true); 580 } 581 } 582 super.setIncomplete(incomplete); 583 } finally { 584 writeUnlock(locked); 585 } 586 } 587 588 public boolean isSelected() { 589 return dataSet != null && dataSet.isSelected(this); 590 } 591 592 public boolean isMemberOfSelected() { 593 if (referrers == null) 594 return false; 595 if (referrers instanceof OsmPrimitive) 596 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected(); 597 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) { 598 if (ref instanceof Relation && ref.isSelected()) 599 return true; 600 } 601 return false; 602 } 603 604 public void setHighlighted(boolean highlighted) { 605 if (isHighlighted() != highlighted) { 606 updateFlags(FLAG_HIGHLIGHTED, highlighted); 607 if (dataSet != null) { 608 dataSet.fireHighlightingChanged(this); 609 } 610 } 611 } 612 613 public boolean isHighlighted() { 614 return (flags & FLAG_HIGHLIGHTED) != 0; 615 } 616 617 /*--------------------------------------------------- 618 * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS 619 *--------------------------------------------------*/ 620 621 private static volatile Collection<String> workinprogress = null; 622 private static volatile Collection<String> uninteresting = null; 623 private static volatile Collection<String> discardable = null; 624 625 /** 626 * Returns a list of "uninteresting" keys that do not make an object 627 * "tagged". Entries that end with ':' are causing a whole namespace to be considered 628 * "uninteresting". Only the first level namespace is considered. 629 * Initialized by isUninterestingKey() 630 * @return The list of uninteresting keys. 631 */ 632 public static Collection<String> getUninterestingKeys() { 633 if (uninteresting == null) { 634 LinkedList<String> l = new LinkedList<>(Arrays.asList( 635 "source", "source_ref", "source:", "comment", 636 "converted_by", "watch", "watch:", 637 "description", "attribution")); 638 l.addAll(getDiscardableKeys()); 639 l.addAll(getWorkInProgressKeys()); 640 uninteresting = Main.pref.getCollection("tags.uninteresting", l); 641 } 642 return uninteresting; 643 } 644 645 /** 646 * Returns a list of keys which have been deemed uninteresting to the point 647 * that they can be silently removed from data which is being edited. 648 * @return The list of discardable keys. 649 */ 650 public static Collection<String> getDiscardableKeys() { 651 if (discardable == null) { 652 discardable = Main.pref.getCollection("tags.discardable", 653 Arrays.asList( 654 "created_by", 655 "geobase:datasetName", 656 "geobase:uuid", 657 "KSJ2:ADS", 658 "KSJ2:ARE", 659 "KSJ2:AdminArea", 660 "KSJ2:COP_label", 661 "KSJ2:DFD", 662 "KSJ2:INT", 663 "KSJ2:INT_label", 664 "KSJ2:LOC", 665 "KSJ2:LPN", 666 "KSJ2:OPC", 667 "KSJ2:PubFacAdmin", 668 "KSJ2:RAC", 669 "KSJ2:RAC_label", 670 "KSJ2:RIC", 671 "KSJ2:RIN", 672 "KSJ2:WSC", 673 "KSJ2:coordinate", 674 "KSJ2:curve_id", 675 "KSJ2:curve_type", 676 "KSJ2:filename", 677 "KSJ2:lake_id", 678 "KSJ2:lat", 679 "KSJ2:long", 680 "KSJ2:river_id", 681 "odbl", 682 "odbl:note", 683 "SK53_bulk:load", 684 "sub_sea:type", 685 "tiger:source", 686 "tiger:separated", 687 "tiger:tlid", 688 "tiger:upload_uuid", 689 "yh:LINE_NAME", 690 "yh:LINE_NUM", 691 "yh:STRUCTURE", 692 "yh:TOTYUMONO", 693 "yh:TYPE", 694 "yh:WIDTH_RANK" 695 )); 696 } 697 return discardable; 698 } 699 700 /** 701 * Returns a list of "work in progress" keys that do not make an object 702 * "tagged" but "annotated". 703 * @return The list of work in progress keys. 704 * @since 5754 705 */ 706 public static Collection<String> getWorkInProgressKeys() { 707 if (workinprogress == null) { 708 workinprogress = Main.pref.getCollection("tags.workinprogress", 709 Arrays.asList("note", "fixme", "FIXME")); 710 } 711 return workinprogress; 712 } 713 714 /** 715 * Determines if key is considered "uninteresting". 716 * @param key The key to check 717 * @return true if key is considered "uninteresting". 718 */ 719 public static boolean isUninterestingKey(String key) { 720 getUninterestingKeys(); 721 if (uninteresting.contains(key)) 722 return true; 723 int pos = key.indexOf(':'); 724 if (pos > 0) 725 return uninteresting.contains(key.substring(0, pos + 1)); 726 return false; 727 } 728 729 /** 730 * Returns {@link #getKeys()} for which {@code key} does not fulfill {@link #isUninterestingKey}. 731 */ 732 public Map<String, String> getInterestingTags() { 733 Map<String, String> result = new HashMap<>(); 734 String[] keys = this.keys; 735 if (keys != null) { 736 for (int i = 0; i < keys.length; i += 2) { 737 if (!isUninterestingKey(keys[i])) { 738 result.put(keys[i], keys[i + 1]); 739 } 740 } 741 } 742 return result; 743 } 744 745 private static volatile Match directionKeys = null; 746 private static volatile Match reversedDirectionKeys = null; 747 748 /** 749 * Contains a list of direction-dependent keys that make an object 750 * direction dependent. 751 * Initialized by checkDirectionTagged() 752 */ 753 static { 754 String reversedDirectionDefault = "oneway=\"-1\""; 755 756 String directionDefault = "oneway? | (aerialway=* aerialway!=station) | "+ 757 "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+ 758 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+ 759 "junction=roundabout | (highway=motorway_link & -oneway=no)"; 760 761 try { 762 reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false); 763 } catch (ParseError e) { 764 Main.error("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage()); 765 766 try { 767 reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false); 768 } catch (ParseError e2) { 769 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2); 770 } 771 } 772 try { 773 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false); 774 } catch (ParseError e) { 775 Main.error("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage()); 776 777 try { 778 directionKeys = SearchCompiler.compile(directionDefault, false, false); 779 } catch (ParseError e2) { 780 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2); 781 } 782 } 783 } 784 785 private void updateTagged() { 786 if (keys != null) { 787 for (String key: keySet()) { 788 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects) 789 // but it's clearly not enough to consider an object as tagged (see #9261) 790 if (!isUninterestingKey(key) && !"area".equals(key)) { 791 updateFlagsNoLock(FLAG_TAGGED, true); 792 return; 793 } 794 } 795 } 796 updateFlagsNoLock(FLAG_TAGGED, false); 797 } 798 799 private void updateAnnotated() { 800 if (keys != null) { 801 for (String key: keySet()) { 802 if (getWorkInProgressKeys().contains(key)) { 803 updateFlagsNoLock(FLAG_ANNOTATED, true); 804 return; 805 } 806 } 807 } 808 updateFlagsNoLock(FLAG_ANNOTATED, false); 809 } 810 811 /** 812 * Determines if this object is considered "tagged". To be "tagged", an object 813 * must have one or more "interesting" tags. "created_by" and "source" 814 * are typically considered "uninteresting" and do not make an object 815 * "tagged". 816 * @return true if this object is considered "tagged" 817 */ 818 public boolean isTagged() { 819 return (flags & FLAG_TAGGED) != 0; 820 } 821 822 /** 823 * Determines if this object is considered "annotated". To be "annotated", an object 824 * must have one or more "work in progress" tags, such as "note" or "fixme". 825 * @return true if this object is considered "annotated" 826 * @since 5754 827 */ 828 public boolean isAnnotated() { 829 return (flags & FLAG_ANNOTATED) != 0; 830 } 831 832 private void updateDirectionFlags() { 833 boolean hasDirections = false; 834 boolean directionReversed = false; 835 if (reversedDirectionKeys.match(this)) { 836 hasDirections = true; 837 directionReversed = true; 838 } 839 if (directionKeys.match(this)) { 840 hasDirections = true; 841 } 842 843 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed); 844 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections); 845 } 846 847 /** 848 * true if this object has direction dependent tags (e.g. oneway) 849 */ 850 public boolean hasDirectionKeys() { 851 return (flags & FLAG_HAS_DIRECTIONS) != 0; 852 } 853 854 public boolean reversedDirection() { 855 return (flags & FLAG_DIRECTION_REVERSED) != 0; 856 } 857 858 /*------------ 859 * Keys handling 860 ------------*/ 861 862 @Override 863 public final void setKeys(Map<String, String> keys) { 864 boolean locked = writeLock(); 865 try { 866 super.setKeys(keys); 867 } finally { 868 writeUnlock(locked); 869 } 870 } 871 872 @Override 873 public final void put(String key, String value) { 874 boolean locked = writeLock(); 875 try { 876 super.put(key, value); 877 } finally { 878 writeUnlock(locked); 879 } 880 } 881 882 @Override 883 public final void remove(String key) { 884 boolean locked = writeLock(); 885 try { 886 super.remove(key); 887 } finally { 888 writeUnlock(locked); 889 } 890 } 891 892 @Override 893 public final void removeAll() { 894 boolean locked = writeLock(); 895 try { 896 super.removeAll(); 897 } finally { 898 writeUnlock(locked); 899 } 900 } 901 902 @Override 903 protected void keysChangedImpl(Map<String, String> originalKeys) { 904 clearCachedStyle(); 905 if (dataSet != null) { 906 for (OsmPrimitive ref : getReferrers()) { 907 ref.clearCachedStyle(); 908 } 909 } 910 updateDirectionFlags(); 911 updateTagged(); 912 updateAnnotated(); 913 if (dataSet != null) { 914 dataSet.fireTagsChanged(this, originalKeys); 915 } 916 } 917 918 /*------------ 919 * Referrers 920 ------------*/ 921 922 private Object referrers; 923 924 /** 925 * Add new referrer. If referrer is already included then no action is taken 926 * @param referrer The referrer to add 927 */ 928 protected void addReferrer(OsmPrimitive referrer) { 929 if (referrers == null) { 930 referrers = referrer; 931 } else if (referrers instanceof OsmPrimitive) { 932 if (referrers != referrer) { 933 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer }; 934 } 935 } else { 936 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) { 937 if (primitive == referrer) 938 return; 939 } 940 referrers = Utils.addInArrayCopy((OsmPrimitive[])referrers, referrer); 941 } 942 } 943 944 /** 945 * Remove referrer. No action is taken if referrer is not registered 946 * @param referrer The referrer to remove 947 */ 948 protected void removeReferrer(OsmPrimitive referrer) { 949 if (referrers instanceof OsmPrimitive) { 950 if (referrers == referrer) { 951 referrers = null; 952 } 953 } else if (referrers instanceof OsmPrimitive[]) { 954 OsmPrimitive[] orig = (OsmPrimitive[])referrers; 955 int idx = -1; 956 for (int i=0; i<orig.length; i++) { 957 if (orig[i] == referrer) { 958 idx = i; 959 break; 960 } 961 } 962 if (idx == -1) 963 return; 964 965 if (orig.length == 2) { 966 referrers = orig[1-idx]; // idx is either 0 or 1, take the other 967 } else { // downsize the array 968 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1]; 969 System.arraycopy(orig, 0, smaller, 0, idx); 970 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx); 971 referrers = smaller; 972 } 973 } 974 } 975 976 /** 977 * Find primitives that reference this primitive. Returns only primitives that are included in the same 978 * dataset as this primitive. <br> 979 * 980 * For example following code will add wnew as referer to all nodes of existingWay, but this method will 981 * not return wnew because it's not part of the dataset <br> 982 * 983 * <code>Way wnew = new Way(existingWay)</code> 984 * 985 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false, 986 * exception will be thrown in this case 987 * 988 * @return a collection of all primitives that reference this primitive. 989 */ 990 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) { 991 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example 992 // when way is cloned 993 994 if (dataSet == null && allowWithoutDataset) 995 return Collections.emptyList(); 996 997 checkDataset(); 998 Object referrers = this.referrers; 999 List<OsmPrimitive> result = new ArrayList<>(); 1000 if (referrers != null) { 1001 if (referrers instanceof OsmPrimitive) { 1002 OsmPrimitive ref = (OsmPrimitive)referrers; 1003 if (ref.dataSet == dataSet) { 1004 result.add(ref); 1005 } 1006 } else { 1007 for (OsmPrimitive o:(OsmPrimitive[])referrers) { 1008 if (dataSet == o.dataSet) { 1009 result.add(o); 1010 } 1011 } 1012 } 1013 } 1014 return result; 1015 } 1016 1017 public final List<OsmPrimitive> getReferrers() { 1018 return getReferrers(false); 1019 } 1020 1021 /** 1022 * <p>Visits {@code visitor} for all referrers.</p> 1023 * 1024 * @param visitor the visitor. Ignored, if null. 1025 */ 1026 public void visitReferrers(Visitor visitor){ 1027 if (visitor == null) return; 1028 if (this.referrers == null) 1029 return; 1030 else if (this.referrers instanceof OsmPrimitive) { 1031 OsmPrimitive ref = (OsmPrimitive) this.referrers; 1032 if (ref.dataSet == dataSet) { 1033 ref.accept(visitor); 1034 } 1035 } else if (this.referrers instanceof OsmPrimitive[]) { 1036 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers; 1037 for (OsmPrimitive ref: refs) { 1038 if (ref.dataSet == dataSet) { 1039 ref.accept(visitor); 1040 } 1041 } 1042 } 1043 } 1044 1045 /** 1046 Return true, if this primitive is referred by at least n ways 1047 @param n Minimal number of ways to return true. Must be positive 1048 */ 1049 public final boolean isReferredByWays(int n) { 1050 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example 1051 // when way is cloned 1052 Object referrers = this.referrers; 1053 if (referrers == null) return false; 1054 checkDataset(); 1055 if (referrers instanceof OsmPrimitive) 1056 return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet; 1057 else { 1058 int counter=0; 1059 for (OsmPrimitive o : (OsmPrimitive[])referrers) { 1060 if (dataSet == o.dataSet && o instanceof Way) { 1061 if (++counter >= n) 1062 return true; 1063 } 1064 } 1065 return false; 1066 } 1067 } 1068 1069 1070 /*----------------- 1071 * OTHER METHODS 1072 *----------------*/ 1073 1074 /** 1075 * Implementation of the visitor scheme. Subclasses have to call the correct 1076 * visitor function. 1077 * @param visitor The visitor from which the visit() function must be called. 1078 */ 1079 public abstract void accept(Visitor visitor); 1080 1081 /** 1082 * Get and write all attributes from the parameter. Does not fire any listener, so 1083 * use this only in the data initializing phase 1084 */ 1085 public void cloneFrom(OsmPrimitive other) { 1086 // write lock is provided by subclasses 1087 if (id != other.id && dataSet != null) 1088 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset"); 1089 1090 super.cloneFrom(other); 1091 clearCachedStyle(); 1092 } 1093 1094 /** 1095 * Merges the technical and semantical attributes from <code>other</code> onto this. 1096 * 1097 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> 1098 * have an assigend OSM id, the IDs have to be the same. 1099 * 1100 * @param other the other primitive. Must not be null. 1101 * @throws IllegalArgumentException thrown if other is null. 1102 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not 1103 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId() 1104 */ 1105 public void mergeFrom(OsmPrimitive other) { 1106 boolean locked = writeLock(); 1107 try { 1108 CheckParameterUtil.ensureParameterNotNull(other, "other"); 1109 if (other.isNew() ^ isNew()) 1110 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not")); 1111 if (! other.isNew() && other.getId() != id) 1112 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId())); 1113 1114 setKeys(other.getKeys()); 1115 timestamp = other.timestamp; 1116 version = other.version; 1117 setIncomplete(other.isIncomplete()); 1118 flags = other.flags; 1119 user= other.user; 1120 changesetId = other.changesetId; 1121 } finally { 1122 writeUnlock(locked); 1123 } 1124 } 1125 1126 /** 1127 * Replies true if other isn't null and has the same interesting tags (key/value-pairs) as this. 1128 * 1129 * @param other the other object primitive 1130 * @return true if other isn't null and has the same interesting tags (key/value-pairs) as this. 1131 */ 1132 public boolean hasSameInterestingTags(OsmPrimitive other) { 1133 // We cannot directly use Arrays.equals(keys, other.keys) as keys is not ordered by key 1134 // but we can at least check if both arrays are null or of the same size before creating 1135 // and comparing the key maps (costly operation, see #7159) 1136 return (keys == null && other.keys == null) 1137 || (keys != null && other.keys != null && keys.length == other.keys.length 1138 && (keys.length == 0 || getInterestingTags().equals(other.getInterestingTags()))); 1139 } 1140 1141 /** 1142 * Replies true if this primitive and other are equal with respect to their 1143 * semantic attributes. 1144 * <ol> 1145 * <li>equal id</li> 1146 * <li>both are complete or both are incomplete</li> 1147 * <li>both have the same tags</li> 1148 * </ol> 1149 * @param other 1150 * @return true if this primitive and other are equal with respect to their 1151 * semantic attributes. 1152 */ 1153 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 1154 if (!isNew() && id != other.id) 1155 return false; 1156 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159) 1157 return false; 1158 // can't do an equals check on the internal keys array because it is not ordered 1159 // 1160 return hasSameInterestingTags(other); 1161 } 1162 1163 /** 1164 * Replies true if this primitive and other are equal with respect to their 1165 * technical attributes. The attributes: 1166 * <ol> 1167 * <li>deleted</li> 1168 * <li>modified</li> 1169 * <li>timestamp</li> 1170 * <li>version</li> 1171 * <li>visible</li> 1172 * <li>user</li> 1173 * </ol> 1174 * have to be equal 1175 * @param other the other primitive 1176 * @return true if this primitive and other are equal with respect to their 1177 * technical attributes 1178 */ 1179 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) { 1180 if (other == null) return false; 1181 1182 return 1183 isDeleted() == other.isDeleted() 1184 && isModified() == other.isModified() 1185 && timestamp == other.timestamp 1186 && version == other.version 1187 && isVisible() == other.isVisible() 1188 && (user == null ? other.user==null : user==other.user) 1189 && changesetId == other.changesetId; 1190 } 1191 1192 /** 1193 * Loads (clone) this primitive from provided PrimitiveData 1194 * @param data The object which should be cloned 1195 */ 1196 public void load(PrimitiveData data) { 1197 // Write lock is provided by subclasses 1198 setKeys(data.getKeys()); 1199 setTimestamp(data.getTimestamp()); 1200 user = data.getUser(); 1201 setChangesetId(data.getChangesetId()); 1202 setDeleted(data.isDeleted()); 1203 setModified(data.isModified()); 1204 setIncomplete(data.isIncomplete()); 1205 version = data.getVersion(); 1206 } 1207 1208 /** 1209 * Save parameters of this primitive to the transport object 1210 * @return The saved object data 1211 */ 1212 public abstract PrimitiveData save(); 1213 1214 /** 1215 * Save common parameters of primitives to the transport object 1216 * @param data The object to save the data into 1217 */ 1218 protected void saveCommonAttributes(PrimitiveData data) { 1219 data.setId(id); 1220 data.setKeys(getKeys()); 1221 data.setTimestamp(getTimestamp()); 1222 data.setUser(user); 1223 data.setDeleted(isDeleted()); 1224 data.setModified(isModified()); 1225 data.setVisible(isVisible()); 1226 data.setIncomplete(isIncomplete()); 1227 data.setChangesetId(changesetId); 1228 data.setVersion(version); 1229 } 1230 1231 /** 1232 * Fetch the bounding box of the primitive 1233 * @return Bounding box of the object 1234 */ 1235 public abstract BBox getBBox(); 1236 1237 /** 1238 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...) 1239 */ 1240 public abstract void updatePosition(); 1241 1242 /*---------------- 1243 * OBJECT METHODS 1244 *---------------*/ 1245 1246 @Override 1247 protected String getFlagsAsString() { 1248 StringBuilder builder = new StringBuilder(super.getFlagsAsString()); 1249 1250 if (isDisabled()) { 1251 if (isDisabledAndHidden()) { 1252 builder.append("h"); 1253 } else { 1254 builder.append("d"); 1255 } 1256 } 1257 if (isTagged()) { 1258 builder.append("T"); 1259 } 1260 if (hasDirectionKeys()) { 1261 if (reversedDirection()) { 1262 builder.append("<"); 1263 } else { 1264 builder.append(">"); 1265 } 1266 } 1267 return builder.toString(); 1268 } 1269 1270 /** 1271 * Equal, if the id (and class) is equal. 1272 * 1273 * An primitive is equal to its incomplete counter part. 1274 */ 1275 @Override public boolean equals(Object obj) { 1276 if (obj instanceof OsmPrimitive) 1277 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass(); 1278 return false; 1279 } 1280 1281 /** 1282 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0. 1283 * 1284 * An primitive has the same hashcode as its incomplete counterpart. 1285 */ 1286 @Override public final int hashCode() { 1287 return (int)id; 1288 } 1289 1290 /** 1291 * Replies the display name of a primitive formatted by <code>formatter</code> 1292 * 1293 * @return the display name 1294 */ 1295 public abstract String getDisplayName(NameFormatter formatter); 1296 1297 @Override 1298 public Collection<String> getTemplateKeys() { 1299 Collection<String> keySet = keySet(); 1300 List<String> result = new ArrayList<>(keySet.size() + 2); 1301 result.add(SPECIAL_VALUE_ID); 1302 result.add(SPECIAL_VALUE_LOCAL_NAME); 1303 result.addAll(keySet); 1304 return result; 1305 } 1306 1307 @Override 1308 public Object getTemplateValue(String name, boolean special) { 1309 if (special) { 1310 String lc = name.toLowerCase(); 1311 if (SPECIAL_VALUE_ID.equals(lc)) 1312 return getId(); 1313 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc)) 1314 return getLocalName(); 1315 else 1316 return null; 1317 1318 } else 1319 return getIgnoreCase(name); 1320 } 1321 1322 @Override 1323 public boolean evaluateCondition(Match condition) { 1324 return condition.match(this); 1325 } 1326 1327 /** 1328 * Replies the set of referring relations 1329 * 1330 * @return the set of referring relations 1331 */ 1332 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) { 1333 HashSet<Relation> ret = new HashSet<>(); 1334 for (OsmPrimitive w : primitives) { 1335 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)); 1336 } 1337 return ret; 1338 } 1339 1340 /** 1341 * Determines if this primitive has tags denoting an area. 1342 * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise. 1343 * @since 6491 1344 */ 1345 public final boolean hasAreaTags() { 1346 return hasKey("landuse") 1347 || "yes".equals(get("area")) 1348 || "riverbank".equals(get("waterway")) 1349 || hasKey("natural") 1350 || hasKey("amenity") 1351 || hasKey("leisure") 1352 || hasKey("building") 1353 || hasKey("building:part"); 1354 } 1355 1356 /** 1357 * Determines if this primitive semantically concerns an area. 1358 * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise. 1359 * @since 6491 1360 */ 1361 public abstract boolean concernsArea(); 1362 1363 /** 1364 * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}. 1365 * @return {@code true} if this primitive lies outside of the downloaded area 1366 */ 1367 public abstract boolean isOutsideDownloadArea(); 1368}