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