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