// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.openstreetmap.josm.tools.LanguageInfo;
import org.openstreetmap.josm.tools.Utils;
/**
* Abstract class to represent common features of the datatypes primitives.
*
* @since 4099
*/
public abstract class AbstractPrimitive implements IPrimitive {
/**
* This is a visitor that can be used to loop over the keys/values of this primitive.
*
* @author Michael Zangl
* @since 8742
* @since 10600 (functional interface)
*/
@FunctionalInterface
public interface KeyValueVisitor {
/**
* This method gets called for every tag received.
*
* @param primitive This primitive
* @param key The key
* @param value The value
*/
void visitKeyValue(AbstractPrimitive primitive, String key, String value);
}
private static final AtomicLong idCounter = new AtomicLong(0);
static long generateUniqueId() {
return idCounter.decrementAndGet();
}
/**
* This flag shows, that the properties have been changed by the user
* and on upload the object will be send to the server.
*/
protected static final short FLAG_MODIFIED = 1 << 0;
/**
* This flag is false, if the object is marked
* as deleted on the server.
*/
protected static final short FLAG_VISIBLE = 1 << 1;
/**
* An object that was deleted by the user.
* Deleted objects are usually hidden on the map and a request
* for deletion will be send to the server on upload.
* An object usually cannot be deleted if it has non-deleted
* objects still referring to it.
*/
protected static final short FLAG_DELETED = 1 << 2;
/**
* A primitive is incomplete if we know its id and type, but nothing more.
* Typically some members of a relation are incomplete until they are
* fetched from the server.
*/
protected static final short FLAG_INCOMPLETE = 1 << 3;
/**
* An object can be disabled by the filter mechanism.
* Then it will show in a shade of gray on the map or it is completely
* hidden from the view.
* Disabled objects usually cannot be selected or modified
* while the filter is active.
*/
protected static final short FLAG_DISABLED = 1 << 4;
/**
* This flag is only relevant if an object is disabled by the
* filter mechanism (i.e. FLAG_DISABLED is set).
* Then it indicates, whether it is completely hidden or
* just shown in gray color.
*
* When the primitive is not disabled, this flag should be
* unset as well (for efficient access).
*/
protected static final short FLAG_HIDE_IF_DISABLED = 1 << 5;
/**
* Flag used internally by the filter mechanism.
*/
protected static final short FLAG_DISABLED_TYPE = 1 << 6;
/**
* Flag used internally by the filter mechanism.
*/
protected static final short FLAG_HIDDEN_TYPE = 1 << 7;
/**
* This flag is set if the primitive is a way and
* according to the tags, the direction of the way is important.
* (e.g. one way street.)
*/
protected static final short FLAG_HAS_DIRECTIONS = 1 << 8;
/**
* If the primitive is tagged.
* Some trivial tags like source=* are ignored here.
*/
protected static final short FLAG_TAGGED = 1 << 9;
/**
* This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
* It shows, that direction of the arrows should be reversed.
* (E.g. oneway=-1.)
*/
protected static final short FLAG_DIRECTION_REVERSED = 1 << 10;
/**
* When hovering over ways and nodes in add mode, the
* "target" objects are visually highlighted. This flag indicates
* that the primitive is currently highlighted.
*/
protected static final short FLAG_HIGHLIGHTED = 1 << 11;
/**
* If the primitive is annotated with a tag such as note, fixme, etc.
* Match the "work in progress" tags in default map style.
*/
protected static final short FLAG_ANNOTATED = 1 << 12;
/**
* Put several boolean flags to one short int field to save memory.
* Other bits of this field are used in subclasses.
*/
protected volatile short flags = FLAG_VISIBLE; // visible per default
/*-------------------
* OTHER PROPERTIES
*-------------------*/
/**
* Unique identifier in OSM. This is used to identify objects on the server.
* An id of 0 means an unknown id. The object has not been uploaded yet to
* know what id it will get.
*/
protected long id;
/**
* User that last modified this primitive, as specified by the server.
* Never changed by JOSM.
*/
protected User user;
/**
* Contains the version number as returned by the API. Needed to
* ensure update consistency
*/
protected int version;
/**
* The id of the changeset this primitive was last uploaded to.
* 0 if it wasn't uploaded to a changeset yet of if the changeset
* id isn't known.
*/
protected int changesetId;
protected int timestamp;
/**
* Get and write all attributes from the parameter. Does not fire any listener, so
* use this only in the data initializing phase
* @param other the primitive to clone data from
*/
public void cloneFrom(AbstractPrimitive other) {
setKeys(other.getKeys());
id = other.id;
if (id <= 0) {
// reset version and changeset id
version = 0;
changesetId = 0;
}
timestamp = other.timestamp;
if (id > 0) {
version = other.version;
}
flags = other.flags;
user = other.user;
if (id > 0 && other.changesetId > 0) {
// #4208: sometimes we cloned from other with id < 0 *and*
// an assigned changeset id. Don't know why yet. For primitives
// with id < 0 we don't propagate the changeset id any more.
//
setChangesetId(other.changesetId);
}
}
@Override
public int getVersion() {
return version;
}
@Override
public long getId() {
long id = this.id;
return id >= 0 ? id : 0;
}
/**
* Gets a unique id representing this object.
*
* @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
*/
@Override
public long getUniqueId() {
return id;
}
/**
* Determines if this primitive is new.
* @return {@code true} if this primitive is new (not yet uploaded the server, id <= 0)
*/
@Override
public boolean isNew() {
return id <= 0;
}
@Override
public boolean isNewOrUndeleted() {
return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
}
@Override
public void setOsmId(long id, int version) {
if (id <= 0)
throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
if (version <= 0)
throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
this.id = id;
this.version = version;
this.setIncomplete(false);
}
/**
* Clears the metadata, including id and version known to the OSM API.
* The id is a new unique id. The version, changeset and timestamp are set to 0.
* incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
* of calling this method.
* @since 6140
*/
public void clearOsmMetadata() {
// Not part of dataset - no lock necessary
this.id = generateUniqueId();
this.version = 0;
this.user = null;
this.changesetId = 0; // reset changeset id on a new object
this.timestamp = 0;
this.setIncomplete(false);
this.setDeleted(false);
this.setVisible(true);
}
@Override
public User getUser() {
return user;
}
@Override
public void setUser(User user) {
this.user = user;
}
@Override
public int getChangesetId() {
return changesetId;
}
@Override
public void setChangesetId(int changesetId) {
if (this.changesetId == changesetId)
return;
if (changesetId < 0)
throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
if (changesetId > 0 && isNew())
throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
this.changesetId = changesetId;
}
@Override
public PrimitiveId getPrimitiveId() {
return new SimplePrimitiveId(getUniqueId(), getType());
}
public OsmPrimitiveType getDisplayType() {
return getType();
}
@Override
public void setTimestamp(Date timestamp) {
this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
}
@Override
public void setRawTimestamp(int timestamp) {
this.timestamp = timestamp;
}
@Override
public Date getTimestamp() {
return new Date(TimeUnit.SECONDS.toMillis(timestamp));
}
@Override
public int getRawTimestamp() {
return timestamp;
}
@Override
public boolean isTimestampEmpty() {
return timestamp == 0;
}
/* -------
/* FLAGS
/* ------*/
protected void updateFlags(short flag, boolean value) {
if (value) {
flags |= flag;
} else {
flags &= (short) ~flag;
}
}
@Override
public void setModified(boolean modified) {
updateFlags(FLAG_MODIFIED, modified);
}
@Override
public boolean isModified() {
return (flags & FLAG_MODIFIED) != 0;
}
@Override
public boolean isDeleted() {
return (flags & FLAG_DELETED) != 0;
}
@Override
public boolean isUndeleted() {
return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
}
@Override
public boolean isUsable() {
return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
}
@Override
public boolean isVisible() {
return (flags & FLAG_VISIBLE) != 0;
}
@Override
public void setVisible(boolean visible) {
if (!visible && isNew())
throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
updateFlags(FLAG_VISIBLE, visible);
}
@Override
public void setDeleted(boolean deleted) {
updateFlags(FLAG_DELETED, deleted);
setModified(deleted ^ !isVisible());
}
/**
* If set to true, this object is incomplete, which means only the id
* and type is known (type is the objects instance class)
* @param incomplete incomplete flag value
*/
protected void setIncomplete(boolean incomplete) {
updateFlags(FLAG_INCOMPLETE, incomplete);
}
@Override
public boolean isIncomplete() {
return (flags & FLAG_INCOMPLETE) != 0;
}
protected String getFlagsAsString() {
StringBuilder builder = new StringBuilder();
if (isIncomplete()) {
builder.append('I');
}
if (isModified()) {
builder.append('M');
}
if (isVisible()) {
builder.append('V');
}
if (isDeleted()) {
builder.append('D');
}
return builder.toString();
}
/*------------
* Keys handling
------------*/
/**
* The key/value list for this primitive.
* <p>
* Note that the keys field is synchronized using RCU.
* Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
* <p>
* In short this means that you should not rely on this variable being the same value when read again and your should always
* copy it on writes.
* <p>
* Further reading:
* <ul>
* <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
* <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
* http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
* <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
* https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
* {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
* </ul>
*/
protected volatile String[] keys;
/**
* Replies the map of key/value pairs. Never replies null. The map can be empty, though.
*
* @return tags of this primitive. Changes made in returned map are not mapped
* back to the primitive, use setKeys() to modify the keys
* @see #visitKeys(KeyValueVisitor)
*/
@Override
public TagMap getKeys() {
return new TagMap(keys);
}
/**
* Calls the visitor for every key/value pair of this primitive.
*
* @param visitor The visitor to call.
* @see #getKeys()
* @since 8742
*/
public void visitKeys(KeyValueVisitor visitor) {
final String[] keys = this.keys;
if (keys != null) {
for (int i = 0; i < keys.length; i += 2) {
visitor.visitKeyValue(this, keys[i], keys[i + 1]);
}
}
}
/**
* Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
* Old key/value pairs are removed.
* If <code>keys</code> is null, clears existing key/value pairs.
* <p>
* Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
* from multiple threads.
*
* @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
*/
@Override
public void setKeys(Map<String, String> keys) {
Map<String, String> originalKeys = getKeys();
if (keys == null || keys.isEmpty()) {
this.keys = null;
keysChangedImpl(originalKeys);
return;
}
String[] newKeys = new String[keys.size() * 2];
int index = 0;
for (Entry<String, String> entry:keys.entrySet()) {
newKeys[index++] = entry.getKey();
newKeys[index++] = entry.getValue();
}
this.keys = newKeys;
keysChangedImpl(originalKeys);
}
/**
* Copy the keys from a TagMap.
* @param keys The new key map.
*/
public void setKeys(TagMap keys) {
Map<String, String> originalKeys = getKeys();
if (keys == null) {
this.keys = null;
} else {
String[] arr = keys.getTagsArray();
if (arr.length == 0) {
this.keys = null;
} else {
this.keys = arr;
}
}
keysChangedImpl(originalKeys);
}
/**
* Set the given value to the given key. If key is null, does nothing. If value is null,
* removes the key and behaves like {@link #remove(String)}.
* <p>
* Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
* from multiple threads.
*
* @param key The key, for which the value is to be set. Can be null or empty, does nothing in this case.
* @param value The value for the key. If null, removes the respective key/value pair.
*
* @see #remove(String)
*/
@Override
public void put(String key, String value) {
Map<String, String> originalKeys = getKeys();
if (key == null || Utils.isStripEmpty(key))
return;
else if (value == null) {
remove(key);
} else if (keys == null) {
keys = new String[] {key, value};
keysChangedImpl(originalKeys);
} else {
int keyIndex = indexOfKey(keys, key);
int tagArrayLength = keys.length;
if (keyIndex < 0) {
keyIndex = tagArrayLength;
tagArrayLength += 2;
}
// Do not try to optimize this array creation if the key already exists.
// We would need to convert the keys array to be an AtomicReferenceArray
// Or we would at least need a volatile write after the array was modified to
// ensure that changes are visible by other threads.
String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
newKeys[keyIndex] = key;
newKeys[keyIndex + 1] = value;
keys = newKeys;
keysChangedImpl(originalKeys);
}
}
/**
* Scans a key/value array for a given key.
* @param keys The key array. It is not modified. It may be null to indicate an emtpy array.
* @param key The key to search for.
* @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
*/
private static int indexOfKey(String[] keys, String key) {
if (keys == null) {
return -1;
}
for (int i = 0; i < keys.length; i += 2) {
if (keys[i].equals(key)) {
return i;
}
}
return -1;
}
/**
* Remove the given key from the list
* <p>
* Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
* from multiple threads.
*
* @param key the key to be removed. Ignored, if key is null.
*/
@Override
public void remove(String key) {
if (key == null || keys == null) return;
if (!hasKey(key))
return;
Map<String, String> originalKeys = getKeys();
if (keys.length == 2) {
keys = null;
keysChangedImpl(originalKeys);
return;
}
String[] newKeys = new String[keys.length - 2];
int j = 0;
for (int i = 0; i < keys.length; i += 2) {
if (!keys[i].equals(key)) {
newKeys[j++] = keys[i];
newKeys[j++] = keys[i+1];
}
}
keys = newKeys;
keysChangedImpl(originalKeys);
}
/**
* Removes all keys from this primitive.
* <p>
* Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
* from multiple threads.
*/
@Override
public void removeAll() {
if (keys != null) {
Map<String, String> originalKeys = getKeys();
keys = null;
keysChangedImpl(originalKeys);
}
}
/**
* Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
* Replies null, if there is no value for the given key.
*
* @param key the key. Can be null, replies null in this case.
* @return the value for key <code>key</code>.
*/
@Override
public final String get(String key) {
String[] keys = this.keys;
if (key == null)
return null;
if (keys == null)
return null;
for (int i = 0; i < keys.length; i += 2) {
if (keys[i].equals(key)) return keys[i+1];
}
return null;
}
/**
* Returns true if the {@code key} corresponds to an OSM true value.
* @param key OSM key
* @return {@code true} if the {@code key} corresponds to an OSM true value
* @see OsmUtils#isTrue(String)
*/
public final boolean isKeyTrue(String key) {
return OsmUtils.isTrue(get(key));
}
/**
* Returns true if the {@code key} corresponds to an OSM false value.
* @param key OSM key
* @return {@code true} if the {@code key} corresponds to an OSM false value
* @see OsmUtils#isFalse(String)
*/
public final boolean isKeyFalse(String key) {
return OsmUtils.isFalse(get(key));
}
public final String getIgnoreCase(String key) {
String[] keys = this.keys;
if (key == null)
return null;
if (keys == null)
return null;
for (int i = 0; i < keys.length; i += 2) {
if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
}
return null;
}
public final int getNumKeys() {
String[] keys = this.keys;
return keys == null ? 0 : keys.length / 2;
}
@Override
public final Collection<String> keySet() {
final String[] keys = this.keys;
if (keys == null) {
return Collections.emptySet();
}
if (keys.length == 1) {
return Collections.singleton(keys[0]);
}
final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
for (int i = 0; i < keys.length; i += 2) {
result.add(keys[i]);
}
return result;
}
/**
* Replies true, if the map of key/value pairs of this primitive is not empty.
*
* @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
*/
@Override
public final boolean hasKeys() {
return keys != null;
}
/**
* Replies true if this primitive has a tag with key <code>key</code>.
*
* @param key the key
* @return true, if this primitive has a tag with key <code>key</code>
*/
@Override
public boolean hasKey(String key) {
return key != null && indexOfKey(keys, key) >= 0;
}
/**
* Replies true if this primitive has a tag any of the <code>keys</code>.
*
* @param keys the keys
* @return true, if this primitive has a tag with any of the <code>keys</code>
* @since 11587
*/
public boolean hasKey(String... keys) {
return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
}
/**
* What to do, when the tags have changed by one of the tag-changing methods.
* @param originalKeys original tags
*/
protected abstract void keysChangedImpl(Map<String, String> originalKeys);
@Override
public String getName() {
return get("name");
}
@Override
public String getLocalName() {
for (String s : LanguageInfo.getLanguageCodes(null)) {
String val = get("name:" + s);
if (val != null)
return val;
}
return getName();
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and {@code value}.
* @param key the key forming the tag.
* @param value value forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and {@code value}.
*/
public boolean hasTag(String key, String value) {
return Objects.equals(value, get(key));
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
* @param key the key forming the tag.
* @param values one or many values forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
*/
public boolean hasTag(String key, String... values) {
return hasTag(key, Arrays.asList(values));
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
* @param key the key forming the tag.
* @param values one or many values forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
*/
public boolean hasTag(String key, Collection<String> values) {
return values.contains(get(key));
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and a value different from {@code value}.
* @param key the key forming the tag.
* @param value value not forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and a value different from {@code value}.
* @since 11608
*/
public boolean hasTagDifferent(String key, String value) {
String v = get(key);
return v != null && !v.equals(value);
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
* @param key the key forming the tag.
* @param values one or many values forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
* @since 11608
*/
public boolean hasTagDifferent(String key, String... values) {
return hasTagDifferent(key, Arrays.asList(values));
}
/**
* Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
* @param key the key forming the tag.
* @param values one or many values forming the tag.
* @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
* @since 11608
*/
public boolean hasTagDifferent(String key, Collection<String> values) {
String v = get(key);
return v != null && !values.contains(v);
}
}