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.Arrays; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.Date; 011import java.util.HashSet; 012import java.util.Map; 013import java.util.Map.Entry; 014import java.util.Objects; 015import java.util.Set; 016import java.util.concurrent.TimeUnit; 017import java.util.concurrent.atomic.AtomicLong; 018 019import org.openstreetmap.josm.tools.LanguageInfo; 020import org.openstreetmap.josm.tools.Utils; 021 022/** 023* Abstract class to represent common features of the datatypes primitives. 024* 025* @since 4099 026*/ 027public abstract class AbstractPrimitive implements IPrimitive { 028 029 /** 030 * This is a visitor that can be used to loop over the keys/values of this primitive. 031 * 032 * @author Michael Zangl 033 * @since 8742 034 * @since 10600 (functional interface) 035 */ 036 @FunctionalInterface 037 public interface KeyValueVisitor { 038 039 /** 040 * This method gets called for every tag received. 041 * 042 * @param primitive This primitive 043 * @param key The key 044 * @param value The value 045 */ 046 void visitKeyValue(AbstractPrimitive primitive, String key, String value); 047 } 048 049 private static final AtomicLong idCounter = new AtomicLong(0); 050 051 static long generateUniqueId() { 052 return idCounter.decrementAndGet(); 053 } 054 055 /** 056 * This flag shows, that the properties have been changed by the user 057 * and on upload the object will be send to the server. 058 */ 059 protected static final short FLAG_MODIFIED = 1 << 0; 060 061 /** 062 * This flag is false, if the object is marked 063 * as deleted on the server. 064 */ 065 protected static final short FLAG_VISIBLE = 1 << 1; 066 067 /** 068 * An object that was deleted by the user. 069 * Deleted objects are usually hidden on the map and a request 070 * for deletion will be send to the server on upload. 071 * An object usually cannot be deleted if it has non-deleted 072 * objects still referring to it. 073 */ 074 protected static final short FLAG_DELETED = 1 << 2; 075 076 /** 077 * A primitive is incomplete if we know its id and type, but nothing more. 078 * Typically some members of a relation are incomplete until they are 079 * fetched from the server. 080 */ 081 protected static final short FLAG_INCOMPLETE = 1 << 3; 082 083 /** 084 * An object can be disabled by the filter mechanism. 085 * Then it will show in a shade of gray on the map or it is completely 086 * hidden from the view. 087 * Disabled objects usually cannot be selected or modified 088 * while the filter is active. 089 */ 090 protected static final short FLAG_DISABLED = 1 << 4; 091 092 /** 093 * This flag is only relevant if an object is disabled by the 094 * filter mechanism (i.e. FLAG_DISABLED is set). 095 * Then it indicates, whether it is completely hidden or 096 * just shown in gray color. 097 * 098 * When the primitive is not disabled, this flag should be 099 * unset as well (for efficient access). 100 */ 101 protected static final short FLAG_HIDE_IF_DISABLED = 1 << 5; 102 103 /** 104 * Flag used internally by the filter mechanism. 105 */ 106 protected static final short FLAG_DISABLED_TYPE = 1 << 6; 107 108 /** 109 * Flag used internally by the filter mechanism. 110 */ 111 protected static final short FLAG_HIDDEN_TYPE = 1 << 7; 112 113 /** 114 * This flag is set if the primitive is a way and 115 * according to the tags, the direction of the way is important. 116 * (e.g. one way street.) 117 */ 118 protected static final short FLAG_HAS_DIRECTIONS = 1 << 8; 119 120 /** 121 * If the primitive is tagged. 122 * Some trivial tags like source=* are ignored here. 123 */ 124 protected static final short FLAG_TAGGED = 1 << 9; 125 126 /** 127 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set. 128 * It shows, that direction of the arrows should be reversed. 129 * (E.g. oneway=-1.) 130 */ 131 protected static final short FLAG_DIRECTION_REVERSED = 1 << 10; 132 133 /** 134 * When hovering over ways and nodes in add mode, the 135 * "target" objects are visually highlighted. This flag indicates 136 * that the primitive is currently highlighted. 137 */ 138 protected static final short FLAG_HIGHLIGHTED = 1 << 11; 139 140 /** 141 * If the primitive is annotated with a tag such as note, fixme, etc. 142 * Match the "work in progress" tags in default map style. 143 */ 144 protected static final short FLAG_ANNOTATED = 1 << 12; 145 146 /** 147 * Put several boolean flags to one short int field to save memory. 148 * Other bits of this field are used in subclasses. 149 */ 150 protected volatile short flags = FLAG_VISIBLE; // visible per default 151 152 /*------------------- 153 * OTHER PROPERTIES 154 *-------------------*/ 155 156 /** 157 * Unique identifier in OSM. This is used to identify objects on the server. 158 * An id of 0 means an unknown id. The object has not been uploaded yet to 159 * know what id it will get. 160 */ 161 protected long id; 162 163 /** 164 * User that last modified this primitive, as specified by the server. 165 * Never changed by JOSM. 166 */ 167 protected User user; 168 169 /** 170 * Contains the version number as returned by the API. Needed to 171 * ensure update consistency 172 */ 173 protected int version; 174 175 /** 176 * The id of the changeset this primitive was last uploaded to. 177 * 0 if it wasn't uploaded to a changeset yet of if the changeset 178 * id isn't known. 179 */ 180 protected int changesetId; 181 182 protected int timestamp; 183 184 /** 185 * Get and write all attributes from the parameter. Does not fire any listener, so 186 * use this only in the data initializing phase 187 * @param other the primitive to clone data from 188 */ 189 public void cloneFrom(AbstractPrimitive other) { 190 setKeys(other.getKeys()); 191 id = other.id; 192 if (id <= 0) { 193 // reset version and changeset id 194 version = 0; 195 changesetId = 0; 196 } 197 timestamp = other.timestamp; 198 if (id > 0) { 199 version = other.version; 200 } 201 flags = other.flags; 202 user = other.user; 203 if (id > 0 && other.changesetId > 0) { 204 // #4208: sometimes we cloned from other with id < 0 *and* 205 // an assigned changeset id. Don't know why yet. For primitives 206 // with id < 0 we don't propagate the changeset id any more. 207 // 208 setChangesetId(other.changesetId); 209 } 210 } 211 212 @Override 213 public int getVersion() { 214 return version; 215 } 216 217 @Override 218 public long getId() { 219 long id = this.id; 220 return id >= 0 ? id : 0; 221 } 222 223 /** 224 * Gets a unique id representing this object. 225 * 226 * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new 227 */ 228 @Override 229 public long getUniqueId() { 230 return id; 231 } 232 233 /** 234 * Determines if this primitive is new. 235 * @return {@code true} if this primitive is new (not yet uploaded the server, id <= 0) 236 */ 237 @Override 238 public boolean isNew() { 239 return id <= 0; 240 } 241 242 @Override 243 public boolean isNewOrUndeleted() { 244 return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0); 245 } 246 247 @Override 248 public void setOsmId(long id, int version) { 249 if (id <= 0) 250 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id)); 251 if (version <= 0) 252 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version)); 253 this.id = id; 254 this.version = version; 255 this.setIncomplete(false); 256 } 257 258 /** 259 * Clears the metadata, including id and version known to the OSM API. 260 * The id is a new unique id. The version, changeset and timestamp are set to 0. 261 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead 262 * of calling this method. 263 * @since 6140 264 */ 265 public void clearOsmMetadata() { 266 // Not part of dataset - no lock necessary 267 this.id = generateUniqueId(); 268 this.version = 0; 269 this.user = null; 270 this.changesetId = 0; // reset changeset id on a new object 271 this.timestamp = 0; 272 this.setIncomplete(false); 273 this.setDeleted(false); 274 this.setVisible(true); 275 } 276 277 @Override 278 public User getUser() { 279 return user; 280 } 281 282 @Override 283 public void setUser(User user) { 284 this.user = user; 285 } 286 287 @Override 288 public int getChangesetId() { 289 return changesetId; 290 } 291 292 @Override 293 public void setChangesetId(int changesetId) { 294 if (this.changesetId == changesetId) 295 return; 296 if (changesetId < 0) 297 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId)); 298 if (isNew() && changesetId > 0) 299 throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId)); 300 301 this.changesetId = changesetId; 302 } 303 304 @Override 305 public PrimitiveId getPrimitiveId() { 306 return new SimplePrimitiveId(getUniqueId(), getType()); 307 } 308 309 public OsmPrimitiveType getDisplayType() { 310 return getType(); 311 } 312 313 @Override 314 public void setTimestamp(Date timestamp) { 315 this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime()); 316 } 317 318 @Override 319 public void setRawTimestamp(int timestamp) { 320 this.timestamp = timestamp; 321 } 322 323 @Override 324 public Date getTimestamp() { 325 return new Date(TimeUnit.SECONDS.toMillis(timestamp)); 326 } 327 328 @Override 329 public int getRawTimestamp() { 330 return timestamp; 331 } 332 333 @Override 334 public boolean isTimestampEmpty() { 335 return timestamp == 0; 336 } 337 338 /* ------- 339 /* FLAGS 340 /* ------*/ 341 342 protected void updateFlags(short flag, boolean value) { 343 if (value) { 344 flags |= flag; 345 } else { 346 flags &= (short) ~flag; 347 } 348 } 349 350 @Override 351 public void setModified(boolean modified) { 352 updateFlags(FLAG_MODIFIED, modified); 353 } 354 355 @Override 356 public boolean isModified() { 357 return (flags & FLAG_MODIFIED) != 0; 358 } 359 360 @Override 361 public boolean isDeleted() { 362 return (flags & FLAG_DELETED) != 0; 363 } 364 365 @Override 366 public boolean isUndeleted() { 367 return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0; 368 } 369 370 @Override 371 public boolean isUsable() { 372 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0; 373 } 374 375 @Override 376 public boolean isVisible() { 377 return (flags & FLAG_VISIBLE) != 0; 378 } 379 380 @Override 381 public void setVisible(boolean visible) { 382 if (isNew() && !visible) 383 throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible.")); 384 updateFlags(FLAG_VISIBLE, visible); 385 } 386 387 @Override 388 public void setDeleted(boolean deleted) { 389 updateFlags(FLAG_DELETED, deleted); 390 setModified(deleted ^ !isVisible()); 391 } 392 393 /** 394 * If set to true, this object is incomplete, which means only the id 395 * and type is known (type is the objects instance class) 396 * @param incomplete incomplete flag value 397 */ 398 protected void setIncomplete(boolean incomplete) { 399 updateFlags(FLAG_INCOMPLETE, incomplete); 400 } 401 402 @Override 403 public boolean isIncomplete() { 404 return (flags & FLAG_INCOMPLETE) != 0; 405 } 406 407 protected String getFlagsAsString() { 408 StringBuilder builder = new StringBuilder(); 409 410 if (isIncomplete()) { 411 builder.append('I'); 412 } 413 if (isModified()) { 414 builder.append('M'); 415 } 416 if (isVisible()) { 417 builder.append('V'); 418 } 419 if (isDeleted()) { 420 builder.append('D'); 421 } 422 return builder.toString(); 423 } 424 425 /*------------ 426 * Keys handling 427 ------------*/ 428 429 /** 430 * The key/value list for this primitive. 431 * <p> 432 * Note that the keys field is synchronized using RCU. 433 * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves. 434 * <p> 435 * In short this means that you should not rely on this variable being the same value when read again and your should always 436 * copy it on writes. 437 * <p> 438 * Further reading: 439 * <ul> 440 * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li> 441 * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe"> 442 * http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li> 443 * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update"> 444 * https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector, 445 * {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li> 446 * </ul> 447 */ 448 protected volatile String[] keys; 449 450 /** 451 * Replies the map of key/value pairs. Never replies null. The map can be empty, though. 452 * 453 * @return tags of this primitive. Changes made in returned map are not mapped 454 * back to the primitive, use setKeys() to modify the keys 455 * @see #visitKeys(KeyValueVisitor) 456 */ 457 @Override 458 public TagMap getKeys() { 459 return new TagMap(keys); 460 } 461 462 /** 463 * Calls the visitor for every key/value pair of this primitive. 464 * 465 * @param visitor The visitor to call. 466 * @see #getKeys() 467 * @since 8742 468 */ 469 public void visitKeys(KeyValueVisitor visitor) { 470 final String[] keys = this.keys; 471 if (keys != null) { 472 for (int i = 0; i < keys.length; i += 2) { 473 visitor.visitKeyValue(this, keys[i], keys[i + 1]); 474 } 475 } 476 } 477 478 /** 479 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>. 480 * Old key/value pairs are removed. 481 * If <code>keys</code> is null, clears existing key/value pairs. 482 * <p> 483 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used 484 * from multiple threads. 485 * 486 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs. 487 */ 488 @Override 489 public void setKeys(Map<String, String> keys) { 490 Map<String, String> originalKeys = getKeys(); 491 if (keys == null || keys.isEmpty()) { 492 this.keys = null; 493 keysChangedImpl(originalKeys); 494 return; 495 } 496 String[] newKeys = new String[keys.size() * 2]; 497 int index = 0; 498 for (Entry<String, String> entry:keys.entrySet()) { 499 newKeys[index++] = entry.getKey(); 500 newKeys[index++] = entry.getValue(); 501 } 502 this.keys = newKeys; 503 keysChangedImpl(originalKeys); 504 } 505 506 /** 507 * Copy the keys from a TagMap. 508 * @param keys The new key map. 509 */ 510 public void setKeys(TagMap keys) { 511 Map<String, String> originalKeys = getKeys(); 512 if (keys == null) { 513 this.keys = null; 514 } else { 515 String[] arr = keys.getTagsArray(); 516 if (arr.length == 0) { 517 this.keys = null; 518 } else { 519 this.keys = arr; 520 } 521 } 522 keysChangedImpl(originalKeys); 523 } 524 525 /** 526 * Set the given value to the given key. If key is null, does nothing. If value is null, 527 * removes the key and behaves like {@link #remove(String)}. 528 * <p> 529 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used 530 * from multiple threads. 531 * 532 * @param key The key, for which the value is to be set. Can be null or empty, does nothing in this case. 533 * @param value The value for the key. If null, removes the respective key/value pair. 534 * 535 * @see #remove(String) 536 */ 537 @Override 538 public void put(String key, String value) { 539 Map<String, String> originalKeys = getKeys(); 540 if (key == null || Utils.strip(key).isEmpty()) 541 return; 542 else if (value == null) { 543 remove(key); 544 } else if (keys == null) { 545 keys = new String[] {key, value}; 546 keysChangedImpl(originalKeys); 547 } else { 548 int keyIndex = indexOfKey(keys, key); 549 int tagArrayLength = keys.length; 550 if (keyIndex < 0) { 551 keyIndex = tagArrayLength; 552 tagArrayLength += 2; 553 } 554 555 // Do not try to optimize this array creation if the key already exists. 556 // We would need to convert the keys array to be an AtomicReferenceArray 557 // Or we would at least need a volatile write after the array was modified to 558 // ensure that changes are visible by other threads. 559 String[] newKeys = Arrays.copyOf(keys, tagArrayLength); 560 newKeys[keyIndex] = key; 561 newKeys[keyIndex + 1] = value; 562 keys = newKeys; 563 keysChangedImpl(originalKeys); 564 } 565 } 566 567 /** 568 * Scans a key/value array for a given key. 569 * @param keys The key array. It is not modified. It may be null to indicate an emtpy array. 570 * @param key The key to search for. 571 * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found. 572 */ 573 private static int indexOfKey(String[] keys, String key) { 574 if (keys == null) { 575 return -1; 576 } 577 for (int i = 0; i < keys.length; i += 2) { 578 if (keys[i].equals(key)) { 579 return i; 580 } 581 } 582 return -1; 583 } 584 585 /** 586 * Remove the given key from the list 587 * <p> 588 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used 589 * from multiple threads. 590 * 591 * @param key the key to be removed. Ignored, if key is null. 592 */ 593 @Override 594 public void remove(String key) { 595 if (key == null || keys == null) return; 596 if (!hasKey(key)) 597 return; 598 Map<String, String> originalKeys = getKeys(); 599 if (keys.length == 2) { 600 keys = null; 601 keysChangedImpl(originalKeys); 602 return; 603 } 604 String[] newKeys = new String[keys.length - 2]; 605 int j = 0; 606 for (int i = 0; i < keys.length; i += 2) { 607 if (!keys[i].equals(key)) { 608 newKeys[j++] = keys[i]; 609 newKeys[j++] = keys[i+1]; 610 } 611 } 612 keys = newKeys; 613 keysChangedImpl(originalKeys); 614 } 615 616 /** 617 * Removes all keys from this primitive. 618 * <p> 619 * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used 620 * from multiple threads. 621 */ 622 @Override 623 public void removeAll() { 624 if (keys != null) { 625 Map<String, String> originalKeys = getKeys(); 626 keys = null; 627 keysChangedImpl(originalKeys); 628 } 629 } 630 631 /** 632 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null. 633 * Replies null, if there is no value for the given key. 634 * 635 * @param key the key. Can be null, replies null in this case. 636 * @return the value for key <code>key</code>. 637 */ 638 @Override 639 public final String get(String key) { 640 String[] keys = this.keys; 641 if (key == null) 642 return null; 643 if (keys == null) 644 return null; 645 for (int i = 0; i < keys.length; i += 2) { 646 if (keys[i].equals(key)) return keys[i+1]; 647 } 648 return null; 649 } 650 651 /** 652 * Returns true if the {@code key} corresponds to an OSM true value. 653 * @param key OSM key 654 * @return {@code true} if the {@code key} corresponds to an OSM true value 655 * @see OsmUtils#isTrue(String) 656 */ 657 public final boolean isKeyTrue(String key) { 658 return OsmUtils.isTrue(get(key)); 659 } 660 661 /** 662 * Returns true if the {@code key} corresponds to an OSM false value. 663 * @param key OSM key 664 * @return {@code true} if the {@code key} corresponds to an OSM false value 665 * @see OsmUtils#isFalse(String) 666 */ 667 public final boolean isKeyFalse(String key) { 668 return OsmUtils.isFalse(get(key)); 669 } 670 671 public final String getIgnoreCase(String key) { 672 String[] keys = this.keys; 673 if (key == null) 674 return null; 675 if (keys == null) 676 return null; 677 for (int i = 0; i < keys.length; i += 2) { 678 if (keys[i].equalsIgnoreCase(key)) return keys[i+1]; 679 } 680 return null; 681 } 682 683 public final int getNumKeys() { 684 String[] keys = this.keys; 685 return keys == null ? 0 : keys.length / 2; 686 } 687 688 @Override 689 public final Collection<String> keySet() { 690 final String[] keys = this.keys; 691 if (keys == null) { 692 return Collections.emptySet(); 693 } 694 if (keys.length == 1) { 695 return Collections.singleton(keys[0]); 696 } 697 698 final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2)); 699 for (int i = 0; i < keys.length; i += 2) { 700 result.add(keys[i]); 701 } 702 return result; 703 } 704 705 /** 706 * Replies true, if the map of key/value pairs of this primitive is not empty. 707 * 708 * @return true, if the map of key/value pairs of this primitive is not empty; false 709 * otherwise 710 */ 711 @Override 712 public final boolean hasKeys() { 713 return keys != null; 714 } 715 716 /** 717 * Replies true if this primitive has a tag with key <code>key</code>. 718 * 719 * @param key the key 720 * @return true, if his primitive has a tag with key <code>key</code> 721 */ 722 public boolean hasKey(String key) { 723 return key != null && indexOfKey(keys, key) >= 0; 724 } 725 726 /** 727 * What to do, when the tags have changed by one of the tag-changing methods. 728 * @param originalKeys original tags 729 */ 730 protected abstract void keysChangedImpl(Map<String, String> originalKeys); 731 732 @Override 733 public String getName() { 734 return get("name"); 735 } 736 737 @Override 738 public String getLocalName() { 739 for (String s : LanguageInfo.getLanguageCodes(null)) { 740 String val = get("name:" + s); 741 if (val != null) 742 return val; 743 } 744 745 return getName(); 746 } 747 748 /** 749 * Tests whether this primitive contains a tag consisting of {@code key} and {@code values}. 750 * @param key the key forming the tag. 751 * @param value value forming the tag. 752 * @return true iff primitive contains a tag consisting of {@code key} and {@code value}. 753 */ 754 public boolean hasTag(String key, String value) { 755 return Objects.equals(value, get(key)); 756 } 757 758 /** 759 * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}. 760 * @param key the key forming the tag. 761 * @param values one or many values forming the tag. 762 * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}. 763 */ 764 public boolean hasTag(String key, String... values) { 765 return hasTag(key, Arrays.asList(values)); 766 } 767 768 /** 769 * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}. 770 * @param key the key forming the tag. 771 * @param values one or many values forming the tag. 772 * @return true iff primitive contains a tag consisting of {@code key} and any of {@code values}. 773 */ 774 public boolean hasTag(String key, Collection<String> values) { 775 return values.contains(get(key)); 776 } 777}