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