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