// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.core.domain.v0_6; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.domain.common.SimpleTimestampContainer; import org.openstreetmap.osmosis.core.domain.common.TimestampContainer; import org.openstreetmap.osmosis.core.domain.common.TimestampFormat; import org.openstreetmap.osmosis.core.store.StoreClassRegister; import org.openstreetmap.osmosis.core.store.StoreReader; import org.openstreetmap.osmosis.core.store.StoreWriter; import org.openstreetmap.osmosis.core.store.Storeable; import org.openstreetmap.osmosis.core.util.LazyHashMap; import org.openstreetmap.osmosis.core.util.LongAsInt; /** * Contains data common to all entity types. This is separated from the entity class to allow it to * be instantiated before all the data required for a full entity is available. */ public class CommonEntityData implements Storeable { private long id; private int version; private int changesetId; private TimestampContainer timestampContainer; private OsmUser user; private TagCollection tags; private Map<String, Object> metaTags; private boolean readOnly; /** * Creates a new instance. * * @param id * The unique identifier. * @param version * The version of the entity. * @param timestamp * The last updated timestamp. * @param user * The user that last modified this entity. * @param changesetId * The id of the changeset that this version of the entity was created by. */ public CommonEntityData(long id, int version, Date timestamp, OsmUser user, long changesetId) { // Chain to the more specific constructor this(id, version, new SimpleTimestampContainer(timestamp), user, changesetId); } /** * Creates a new instance. * * @param id * The unique identifier. * @param version * The version of the entity. * @param timestampContainer * The container holding the timestamp in an alternative * timestamp representation. * @param user * The user that last modified this entity. * @param changesetId * The id of the changeset that this version of the entity was created by. */ public CommonEntityData( long id, int version, TimestampContainer timestampContainer, OsmUser user, long changesetId) { init(id, timestampContainer, user, version, changesetId); tags = new TagCollectionImpl(); metaTags = new LazyHashMap<String, Object>(); } /** * Creates a new instance. * * @param id * The unique identifier. * @param version * The version of the entity. * @param timestamp * The last updated timestamp. * @param user * The user that last modified this entity. * @param changesetId * The id of the changeset that this version of the entity was created by. * @param tags * The tags to apply to the object. */ public CommonEntityData( long id, int version, Date timestamp, OsmUser user, long changesetId, Collection<Tag> tags) { // Chain to the more specific constructor this(id, version, new SimpleTimestampContainer(timestamp), user, changesetId, tags); } /** * Creates a new instance. * * @param id * The unique identifier. * @param version * The version of the entity. * @param timestampContainer * The container holding the timestamp in an alternative * timestamp representation. * @param user * The user that last modified this entity. * @param changesetId * The id of the changeset that this version of the entity was created by. * @param tags * The tags to apply to the object. */ public CommonEntityData(long id, int version, TimestampContainer timestampContainer, OsmUser user, long changesetId, Collection<Tag> tags) { init(id, timestampContainer, user, version, changesetId); this.tags = new TagCollectionImpl(tags); metaTags = new LazyHashMap<String, Object>(); } /** * Initializes non-collection attributes. * * @param newId * The unique identifier. * @param newTimestampContainer * The container holding the timestamp in an alternative timestamp representation. * @param newUser * The user that last modified this entity. * @param newVersion * The version of the entity. * @param changesetId * The id of the changeset that this version of the entity was created by. */ private void init(long newId, TimestampContainer newTimestampContainer, OsmUser newUser, int newVersion, long newChangesetId) { this.id = newId; this.timestampContainer = newTimestampContainer; this.user = newUser; this.version = newVersion; this.changesetId = LongAsInt.longToInt(newChangesetId); } private static TimestampContainer readTimestampContainer(StoreReader sr, StoreClassRegister scr) { if (sr.readBoolean()) { return new SimpleTimestampContainer(new Date(sr.readLong())); } else { return null; } } private static OsmUser readOsmUser(StoreReader sr, StoreClassRegister scr) { OsmUser user; // We could follow the same approach as timestamps and use a boolean to // indicate the existence of a user or not. But most entities have a // user so this would increase the space consumed on disk. So I'm taking // a small hit in instantiating unnecessary OsmUser objects when no user // is available. user = new OsmUser(sr, scr); if (user.equals(OsmUser.NONE)) { return OsmUser.NONE; } else { return user; } } /** * Creates a new instance. * * @param sr * The store to read state from. * @param scr * Maintains the mapping between classes and their identifiers * within the store. */ public CommonEntityData(StoreReader sr, StoreClassRegister scr) { this( sr.readLong(), sr.readInteger(), readTimestampContainer(sr, scr), readOsmUser(sr, scr), sr.readInteger(), new TagCollectionImpl(sr, scr) ); int metaTagCount; metaTagCount = sr.readInteger(); metaTags = new LazyHashMap<String, Object>(); for (int i = 0; i < metaTagCount; i++) { metaTags.put(sr.readString(), sr.readString()); } } /** * {@inheritDoc} */ public void store(StoreWriter sw, StoreClassRegister scr) { sw.writeLong(id); sw.writeInteger(version); if (getTimestamp() != null) { sw.writeBoolean(true); sw.writeLong(timestampContainer.getTimestamp().getTime()); } else { sw.writeBoolean(false); } user.store(sw, scr); sw.writeInteger(changesetId); tags.store(sw, scr); sw.writeInteger(metaTags.size()); for (Entry<String, Object> tag : metaTags.entrySet()) { sw.writeString(tag.getKey()); sw.writeString(tag.getValue().toString()); } } /** * Compares the tags on this entity to the specified tags. The tag * comparison is based on a comparison of key and value in that order. * * @param comparisonTags * The tags to compare to. * @return 0 if equal, < 0 if considered "smaller", and > 0 if considered * "bigger". */ protected int compareTags(Collection<Tag> comparisonTags) { List<Tag> tags1; List<Tag> tags2; tags1 = new ArrayList<Tag>(tags); tags2 = new ArrayList<Tag>(comparisonTags); Collections.sort(tags1); Collections.sort(tags2); // The list with the most tags is considered bigger. if (tags1.size() != tags2.size()) { return tags1.size() - tags2.size(); } // Check the individual tags. for (int i = 0; i < tags1.size(); i++) { int result = tags1.get(i).compareTo(tags2.get(i)); if (result != 0) { return result; } } // There are no differences. return 0; } /** * Gets the identifier. * * @return The id. */ public long getId() { return id; } /** * Sets the identifier. * * @param id * The identifier. */ public void setId(long id) { assertWriteable(); this.id = id; } /** * Gets the version. * * @return The version. */ public int getVersion() { return version; } /** * Sets the version. * * @param version * The version. */ public void setVersion(int version) { assertWriteable(); this.version = version; } /** * Gets the timestamp in date form. This is the standard method for * retrieving timestamp information. * * @return The timestamp. */ public Date getTimestamp() { return timestampContainer.getTimestamp(); } /** * Sets the timestamp in date form. This is the standard method of updating a timestamp. * * @param timestamp * The timestamp. */ public void setTimestamp(Date timestamp) { assertWriteable(); timestampContainer = new SimpleTimestampContainer(timestamp); } /** * Gets the timestamp container object which may hold the timestamp in a * different format. This is most useful if creating new copies of entities * because it can avoid the need to parse timestamp information into Date * form. * * @return The timestamp container. */ public TimestampContainer getTimestampContainer() { return timestampContainer; } /** * Sets the timestamp container object allowing the timestamp to be held in a different format. * This should be used if a date is already held in a timestamp container, or if date parsing * can be avoided. * * @param timestampContainer * The timestamp container. */ public void setTimestampContainer(TimestampContainer timestampContainer) { assertWriteable(); this.timestampContainer = timestampContainer; } /** * Gets the timestamp in a string format. If the entity already contains a * string in string format it will return the original unparsed string * instead of formatting a date object. * * @param timestampFormat * The formatter to use for formatting the timestamp into a * string. * @return The timestamp string. */ public String getFormattedTimestamp(TimestampFormat timestampFormat) { return timestampContainer.getFormattedTimestamp(timestampFormat); } /** * Returns the user who last edited the entity. * * @return The user. */ public OsmUser getUser() { return user; } /** * Sets the last modification user. * * @param user * The user. */ public void setUser(OsmUser user) { assertWriteable(); this.user = user; } /** * Gets the id of the changeset that this version of the entity was created by. * * @return The changeset id. */ public long getChangesetId() { return changesetId; } /** * Sets the id of the changeset that this version of the entity was created by. * * @param changesetId * The changeset id. */ public void setChangesetId(long changesetId) { assertWriteable(); this.changesetId = LongAsInt.longToInt(changesetId); } /** * Returns the attached tags. If the class is read-only, the collection will * be read-only. * * @return The tags. */ public Collection<Tag> getTags() { return tags; } /** * Returns the attached meta tags. If the class is read-only, the collection will * be read-only. * * @return The metaTags. */ public Map<String, Object> getMetaTags() { return metaTags; } /** * Indicates if the object has been set to read-only. A read-only object * must be cloned in order to make updates. This allows objects shared * between multiple threads to be locked for thread safety. * * @return True if the object is read-only. */ public boolean isReadOnly() { return readOnly; } /** * Ensures that the object is writeable. If not an exception will be thrown. * This is intended to be called within all update methods. */ protected void assertWriteable() { if (readOnly) { throw new OsmosisRuntimeException( "The object has been marked as read-only. It must be cloned to make changes."); } } /** * Configures the object to be read-only. This should be called if the object is to be processed * by multiple threads concurrently. It updates the read-only status of the object, and makes * all collections unmodifiable. This must be overridden by sub-classes to make their own * collections unmodifiable. */ public void makeReadOnly() { if (!readOnly) { tags = new UnmodifiableTagCollection(tags); metaTags = Collections.unmodifiableMap(metaTags); readOnly = true; } } /** * Returns a writable instance of this object. If the object is read-only a clone is created, * if it is already writable then this object is returned. * * @return A writable instance of this object. */ public CommonEntityData getWriteableInstance() { if (isReadOnly()) { return new CommonEntityData(id, version, timestampContainer, user, changesetId, tags); } else { return this; } } }