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