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