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 private static volatile Collection<String> uninteresting = null; 612 private static volatile Collection<String> discardable = null; 613 614 /** 615 * Contains a list of "uninteresting" keys that do not make an object 616 * "tagged". Entries that end with ':' are causing a whole namespace to be considered 617 * "uninteresting". Only the first level namespace is considered. 618 * Initialized by isUninterestingKey() 619 */ 620 public static Collection<String> getUninterestingKeys() { 621 if (uninteresting == null) { 622 uninteresting = Main.pref.getCollection("tags.uninteresting", 623 Arrays.asList("source", "source_ref", "source:", "note", "comment", 624 "converted_by", "created_by", "watch", "watch:", "fixme", "FIXME", 625 "description", "attribution")); 626 } 627 return uninteresting; 628 } 629 630 /** 631 * Returns a list of keys which have been deemed uninteresting to the point 632 * that they can be silently removed from data which is being edited. 633 */ 634 public static Collection<String> getDiscardableKeys() { 635 if(discardable == null) { 636 discardable = Main.pref.getCollection("tags.discardable", 637 Arrays.asList("created_by", 638 "tiger:upload_uuid", "tiger:tlid", "tiger:source", "tiger:separated", 639 "geobase:datasetName", "geobase:uuid", "sub_sea:type", 640 "odbl", "odbl:note", 641 "yh:LINE_NAME", "yh:LINE_NUM", "yh:STRUCTURE", "yh:TOTYUMONO", 642 "yh:TYPE", "yh:WIDTH_RANK")); 643 } 644 return discardable; 645 } 646 647 /** 648 * Returns true if key is considered "uninteresting". 649 */ 650 public static boolean isUninterestingKey(String key) { 651 getUninterestingKeys(); 652 if (uninteresting.contains(key)) 653 return true; 654 int pos = key.indexOf(':'); 655 if (pos > 0) 656 return uninteresting.contains(key.substring(0, pos + 1)); 657 return false; 658 } 659 660 private static volatile Match directionKeys = null; 661 private static volatile Match reversedDirectionKeys = null; 662 663 /** 664 * Contains a list of direction-dependent keys that make an object 665 * direction dependent. 666 * Initialized by checkDirectionTagged() 667 */ 668 static { 669 String reversedDirectionDefault = "oneway=\"-1\""; 670 671 String directionDefault = "oneway? | aerialway=* | "+ 672 "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+ 673 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+ 674 "junction=roundabout"; 675 676 try { 677 reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false); 678 } catch (ParseError e) { 679 System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage()); 680 681 try { 682 reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false); 683 } catch (ParseError e2) { 684 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage()); 685 } 686 } 687 try { 688 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false); 689 } catch (ParseError e) { 690 System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage()); 691 692 try { 693 directionKeys = SearchCompiler.compile(directionDefault, false, false); 694 } catch (ParseError e2) { 695 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage()); 696 } 697 } 698 } 699 700 private void updateTagged() { 701 if (keys != null) { 702 for (String key: keySet()) { 703 if (!isUninterestingKey(key)) { 704 updateFlagsNoLock(FLAG_TAGGED, true); 705 return; 706 } 707 } 708 } 709 updateFlagsNoLock(FLAG_TAGGED, false); 710 } 711 712 /** 713 * true if this object is considered "tagged". To be "tagged", an object 714 * must have one or more "interesting" tags. "created_by" and "source" 715 * are typically considered "uninteresting" and do not make an object 716 * "tagged". 717 */ 718 public boolean isTagged() { 719 return (flags & FLAG_TAGGED) != 0; 720 } 721 722 private void updateDirectionFlags() { 723 boolean hasDirections = false; 724 boolean directionReversed = false; 725 if (reversedDirectionKeys.match(this)) { 726 hasDirections = true; 727 directionReversed = true; 728 } 729 if (directionKeys.match(this)) { 730 hasDirections = true; 731 } 732 733 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed); 734 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections); 735 } 736 737 /** 738 * true if this object has direction dependent tags (e.g. oneway) 739 */ 740 public boolean hasDirectionKeys() { 741 return (flags & FLAG_HAS_DIRECTIONS) != 0; 742 } 743 744 public boolean reversedDirection() { 745 return (flags & FLAG_DIRECTION_REVERSED) != 0; 746 } 747 748 /*------------ 749 * Keys handling 750 ------------*/ 751 752 @Override 753 public final void setKeys(Map<String, String> keys) { 754 boolean locked = writeLock(); 755 try { 756 super.setKeys(keys); 757 } finally { 758 writeUnlock(locked); 759 } 760 } 761 762 @Override 763 public final void put(String key, String value) { 764 boolean locked = writeLock(); 765 try { 766 super.put(key, value); 767 } finally { 768 writeUnlock(locked); 769 } 770 } 771 772 @Override 773 public final void remove(String key) { 774 boolean locked = writeLock(); 775 try { 776 super.remove(key); 777 } finally { 778 writeUnlock(locked); 779 } 780 } 781 782 @Override 783 public final void removeAll() { 784 boolean locked = writeLock(); 785 try { 786 super.removeAll(); 787 } finally { 788 writeUnlock(locked); 789 } 790 } 791 792 @Override 793 protected final void keysChangedImpl(Map<String, String> originalKeys) { 794 clearCachedStyle(); 795 if (dataSet != null) { 796 for (OsmPrimitive ref : getReferrers()) { 797 ref.clearCachedStyle(); 798 } 799 } 800 updateDirectionFlags(); 801 updateTagged(); 802 if (dataSet != null) { 803 dataSet.fireTagsChanged(this, originalKeys); 804 } 805 } 806 807 /*------------ 808 * Referrers 809 ------------*/ 810 811 private Object referrers; 812 813 /** 814 * Add new referrer. If referrer is already included then no action is taken 815 * @param referrer 816 */ 817 protected void addReferrer(OsmPrimitive referrer) { 818 // Based on methods from josm-ng 819 if (referrers == null) { 820 referrers = referrer; 821 } else if (referrers instanceof OsmPrimitive) { 822 if (referrers != referrer) { 823 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer }; 824 } 825 } else { 826 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) { 827 if (primitive == referrer) 828 return; 829 } 830 OsmPrimitive[] orig = (OsmPrimitive[])referrers; 831 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1]; 832 System.arraycopy(orig, 0, bigger, 0, orig.length); 833 bigger[orig.length] = referrer; 834 referrers = bigger; 835 } 836 } 837 838 /** 839 * Remove referrer. No action is taken if referrer is not registered 840 * @param referrer 841 */ 842 protected void removeReferrer(OsmPrimitive referrer) { 843 // Based on methods from josm-ng 844 if (referrers instanceof OsmPrimitive) { 845 if (referrers == referrer) { 846 referrers = null; 847 } 848 } else if (referrers instanceof OsmPrimitive[]) { 849 OsmPrimitive[] orig = (OsmPrimitive[])referrers; 850 int idx = -1; 851 for (int i=0; i<orig.length; i++) { 852 if (orig[i] == referrer) { 853 idx = i; 854 break; 855 } 856 } 857 if (idx == -1) 858 return; 859 860 if (orig.length == 2) { 861 referrers = orig[1-idx]; // idx is either 0 or 1, take the other 862 } else { // downsize the array 863 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1]; 864 System.arraycopy(orig, 0, smaller, 0, idx); 865 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx); 866 referrers = smaller; 867 } 868 } 869 } 870 /** 871 * Find primitives that reference this primitive. Returns only primitives that are included in the same 872 * dataset as this primitive. <br> 873 * 874 * For example following code will add wnew as referer to all nodes of existingWay, but this method will 875 * not return wnew because it's not part of the dataset <br> 876 * 877 * <code>Way wnew = new Way(existingWay)</code> 878 * 879 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false, 880 * exception will be thrown in this case 881 * 882 * @return a collection of all primitives that reference this primitive. 883 */ 884 885 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) { 886 // Method copied from OsmPrimitive in josm-ng 887 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example 888 // when way is cloned 889 890 if (dataSet == null && allowWithoutDataset) 891 return Collections.emptyList(); 892 893 checkDataset(); 894 Object referrers = this.referrers; 895 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>(); 896 if (referrers != null) { 897 if (referrers instanceof OsmPrimitive) { 898 OsmPrimitive ref = (OsmPrimitive)referrers; 899 if (ref.dataSet == dataSet) { 900 result.add(ref); 901 } 902 } else { 903 for (OsmPrimitive o:(OsmPrimitive[])referrers) { 904 if (dataSet == o.dataSet) { 905 result.add(o); 906 } 907 } 908 } 909 } 910 return result; 911 } 912 913 public final List<OsmPrimitive> getReferrers() { 914 return getReferrers(false); 915 } 916 917 /** 918 * <p>Visits {@code visitor} for all referrers.</p> 919 * 920 * @param visitor the visitor. Ignored, if null. 921 */ 922 public void visitReferrers(Visitor visitor){ 923 if (visitor == null) return; 924 if (this.referrers == null) 925 return; 926 else if (this.referrers instanceof OsmPrimitive) { 927 OsmPrimitive ref = (OsmPrimitive) this.referrers; 928 if (ref.dataSet == dataSet) { 929 ref.visit(visitor); 930 } 931 } else if (this.referrers instanceof OsmPrimitive[]) { 932 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers; 933 for (OsmPrimitive ref: refs) { 934 if (ref.dataSet == dataSet) { 935 ref.visit(visitor); 936 } 937 } 938 } 939 } 940 941 /** 942 Return true, if this primitive is referred by at least n ways 943 @param n Minimal number of ways to return true. Must be positive 944 */ 945 public final boolean isReferredByWays(int n) { 946 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example 947 // when way is cloned 948 Object referrers = this.referrers; 949 if (referrers == null) return false; 950 checkDataset(); 951 if (referrers instanceof OsmPrimitive) 952 return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet; 953 else { 954 int counter=0; 955 for (OsmPrimitive o : (OsmPrimitive[])referrers) { 956 if (dataSet == o.dataSet && o instanceof Way) { 957 if (++counter >= n) 958 return true; 959 } 960 } 961 return false; 962 } 963 } 964 965 966 /*----------------- 967 * OTHER METHODS 968 *----------------/ 969 970 /** 971 * Implementation of the visitor scheme. Subclasses have to call the correct 972 * visitor function. 973 * @param visitor The visitor from which the visit() function must be called. 974 */ 975 abstract public void visit(Visitor visitor); 976 977 /** 978 * Get and write all attributes from the parameter. Does not fire any listener, so 979 * use this only in the data initializing phase 980 */ 981 public void cloneFrom(OsmPrimitive other) { 982 // write lock is provided by subclasses 983 if (id != other.id && dataSet != null) 984 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset"); 985 986 super.cloneFrom(other); 987 clearCachedStyle(); 988 } 989 990 /** 991 * Merges the technical and semantical attributes from <code>other</code> onto this. 992 * 993 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> 994 * have an assigend OSM id, the IDs have to be the same. 995 * 996 * @param other the other primitive. Must not be null. 997 * @throws IllegalArgumentException thrown if other is null. 998 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not 999 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId() 1000 */ 1001 public void mergeFrom(OsmPrimitive other) { 1002 boolean locked = writeLock(); 1003 try { 1004 CheckParameterUtil.ensureParameterNotNull(other, "other"); 1005 if (other.isNew() ^ isNew()) 1006 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not")); 1007 if (! other.isNew() && other.getId() != id) 1008 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId())); 1009 1010 setKeys(other.getKeys()); 1011 timestamp = other.timestamp; 1012 version = other.version; 1013 setIncomplete(other.isIncomplete()); 1014 flags = other.flags; 1015 user= other.user; 1016 changesetId = other.changesetId; 1017 } finally { 1018 writeUnlock(locked); 1019 } 1020 } 1021 1022 /** 1023 * Replies true if this primitive and other are equal with respect to their 1024 * semantic attributes. 1025 * <ol> 1026 * <li>equal id</ol> 1027 * <li>both are complete or both are incomplete</li> 1028 * <li>both have the same tags</li> 1029 * </ol> 1030 * @param other 1031 * @return true if this primitive and other are equal with respect to their 1032 * semantic attributes. 1033 */ 1034 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 1035 if (!isNew() && id != other.id) 1036 return false; 1037 // if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete()) 1038 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159) 1039 return false; 1040 // can't do an equals check on the internal keys array because it is not ordered 1041 // 1042 return hasSameTags(other); 1043 } 1044 1045 /** 1046 * Replies true if this primitive and other are equal with respect to their 1047 * technical attributes. The attributes: 1048 * <ol> 1049 * <li>deleted</ol> 1050 * <li>modified</ol> 1051 * <li>timestamp</ol> 1052 * <li>version</ol> 1053 * <li>visible</ol> 1054 * <li>user</ol> 1055 * </ol> 1056 * have to be equal 1057 * @param other the other primitive 1058 * @return true if this primitive and other are equal with respect to their 1059 * technical attributes 1060 */ 1061 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) { 1062 if (other == null) return false; 1063 1064 return 1065 isDeleted() == other.isDeleted() 1066 && isModified() == other.isModified() 1067 && timestamp == other.timestamp 1068 && version == other.version 1069 && isVisible() == other.isVisible() 1070 && (user == null ? other.user==null : user==other.user) 1071 && changesetId == other.changesetId; 1072 } 1073 1074 /** 1075 * Loads (clone) this primitive from provided PrimitiveData 1076 * @param data 1077 */ 1078 public void load(PrimitiveData data) { 1079 // Write lock is provided by subclasses 1080 setKeys(data.getKeys()); 1081 setTimestamp(data.getTimestamp()); 1082 user = data.getUser(); 1083 setChangesetId(data.getChangesetId()); 1084 setDeleted(data.isDeleted()); 1085 setModified(data.isModified()); 1086 setIncomplete(data.isIncomplete()); 1087 version = data.getVersion(); 1088 } 1089 1090 /** 1091 * Save parameters of this primitive to the transport object 1092 * @return 1093 */ 1094 public abstract PrimitiveData save(); 1095 1096 protected void saveCommonAttributes(PrimitiveData data) { 1097 data.setId(id); 1098 data.setKeys(getKeys()); 1099 data.setTimestamp(getTimestamp()); 1100 data.setUser(user); 1101 data.setDeleted(isDeleted()); 1102 data.setModified(isModified()); 1103 data.setVisible(isVisible()); 1104 data.setIncomplete(isIncomplete()); 1105 data.setChangesetId(changesetId); 1106 data.setVersion(version); 1107 } 1108 1109 public abstract BBox getBBox(); 1110 1111 /** 1112 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...) 1113 */ 1114 public abstract void updatePosition(); 1115 1116 /*---------------- 1117 * OBJECT METHODS 1118 *---------------*/ 1119 1120 @Override 1121 protected String getFlagsAsString() { 1122 StringBuilder builder = new StringBuilder(super.getFlagsAsString()); 1123 1124 if (isDisabled()) { 1125 if (isDisabledAndHidden()) { 1126 builder.append("h"); 1127 } else { 1128 builder.append("d"); 1129 } 1130 } 1131 if (isTagged()) { 1132 builder.append("T"); 1133 } 1134 if (hasDirectionKeys()) { 1135 if (reversedDirection()) { 1136 builder.append("<"); 1137 } else { 1138 builder.append(">"); 1139 } 1140 } 1141 return builder.toString(); 1142 } 1143 1144 /** 1145 * Equal, if the id (and class) is equal. 1146 * 1147 * An primitive is equal to its incomplete counter part. 1148 */ 1149 @Override public boolean equals(Object obj) { 1150 if (obj instanceof OsmPrimitive) 1151 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass(); 1152 return false; 1153 } 1154 1155 /** 1156 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0. 1157 * 1158 * An primitive has the same hashcode as its incomplete counterpart. 1159 */ 1160 @Override public final int hashCode() { 1161 return (int)id; 1162 } 1163 1164 /** 1165 * Replies the display name of a primitive formatted by <code>formatter</code> 1166 * 1167 * @return the display name 1168 */ 1169 public abstract String getDisplayName(NameFormatter formatter); 1170 1171 @Override 1172 public Collection<String> getTemplateKeys() { 1173 Collection<String> keySet = keySet(); 1174 List<String> result = new ArrayList<String>(keySet.size() + 2); 1175 result.add(SPECIAL_VALUE_ID); 1176 result.add(SPECIAL_VALUE_LOCAL_NAME); 1177 result.addAll(keySet); 1178 return result; 1179 } 1180 1181 @Override 1182 public Object getTemplateValue(String name, boolean special) { 1183 if (special) { 1184 String lc = name.toLowerCase(); 1185 if (SPECIAL_VALUE_ID.equals(lc)) 1186 return getId(); 1187 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc)) 1188 return getLocalName(); 1189 else 1190 return null; 1191 1192 } else 1193 return getIgnoreCase(name); 1194 } 1195 1196 @Override 1197 public boolean evaluateCondition(Match condition) { 1198 return condition.match(this); 1199 } 1200 1201 /** 1202 * Replies the set of referring relations 1203 * 1204 * @return the set of referring relations 1205 */ 1206 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) { 1207 HashSet<Relation> ret = new HashSet<Relation>(); 1208 for (OsmPrimitive w : primitives) { 1209 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)); 1210 } 1211 return ret; 1212 } 1213 }