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