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