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