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