// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm.history;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
import org.openstreetmap.josm.tools.CheckParameterUtil;
/**
* Represents the history of an OSM primitive. The history consists
* of a list of object snapshots with a specific version.
* @since 1670
*/
public class History {
@FunctionalInterface
private interface FilterPredicate {
boolean matches(HistoryOsmPrimitive primitive);
}
private static History filter(History history, FilterPredicate predicate) {
List<HistoryOsmPrimitive> out = new ArrayList<>();
for (HistoryOsmPrimitive primitive: history.versions) {
if (predicate.matches(primitive)) {
out.add(primitive);
}
}
return new History(history.id, history.type, out);
}
/** the list of object snapshots */
private final List<HistoryOsmPrimitive> versions;
/** the object id */
private final long id;
/** the object type */
private final OsmPrimitiveType type;
/**
* Creates a new history for an OSM primitive.
*
* @param id the id. > 0 required.
* @param type the primitive type. Must not be null.
* @param versions a list of versions. Can be null.
* @throws IllegalArgumentException if id <= 0
* @throws IllegalArgumentException if type is null
*/
protected History(long id, OsmPrimitiveType type, List<HistoryOsmPrimitive> versions) {
if (id <= 0)
throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id));
CheckParameterUtil.ensureParameterNotNull(type, "type");
this.id = id;
this.type = type;
this.versions = new ArrayList<>();
if (versions != null) {
this.versions.addAll(versions);
}
}
/**
* Returns a new copy of this history, sorted in ascending order.
* @return a new copy of this history, sorted in ascending order
*/
public History sortAscending() {
List<HistoryOsmPrimitive> copy = new ArrayList<>(versions);
copy.sort(Comparator.naturalOrder());
return new History(id, type, copy);
}
/**
* Returns a new copy of this history, sorted in descending order.
* @return a new copy of this history, sorted in descending order
*/
public History sortDescending() {
List<HistoryOsmPrimitive> copy = new ArrayList<>(versions);
copy.sort(Comparator.reverseOrder());
return new History(id, type, copy);
}
/**
* Returns a new partial copy of this history, from the given date
* @param fromDate the starting date
* @return a new partial copy of this history, from the given date
*/
public History from(final Date fromDate) {
return filter(this, primitive -> primitive.getTimestamp().compareTo(fromDate) >= 0);
}
/**
* Returns a new partial copy of this history, until the given date
* @param untilDate the end date
* @return a new partial copy of this history, until the given date
*/
public History until(final Date untilDate) {
return filter(this, primitive -> primitive.getTimestamp().compareTo(untilDate) <= 0);
}
/**
* Returns a new partial copy of this history, between the given dates
* @param fromDate the starting date
* @param untilDate the end date
* @return a new partial copy of this history, between the given dates
*/
public History between(Date fromDate, Date untilDate) {
return this.from(fromDate).until(untilDate);
}
/**
* Returns a new partial copy of this history, from the given version number
* @param fromVersion the starting version number
* @return a new partial copy of this history, from the given version number
*/
public History from(final long fromVersion) {
return filter(this, primitive -> primitive.getVersion() >= fromVersion);
}
/**
* Returns a new partial copy of this history, to the given version number
* @param untilVersion the ending version number
* @return a new partial copy of this history, to the given version number
*/
public History until(final long untilVersion) {
return filter(this, primitive -> primitive.getVersion() <= untilVersion);
}
/**
* Returns a new partial copy of this history, betwwen the given version numbers
* @param fromVersion the starting version number
* @param untilVersion the ending version number
* @return a new partial copy of this history, between the given version numbers
*/
public History between(long fromVersion, long untilVersion) {
return this.from(fromVersion).until(untilVersion);
}
/**
* Returns a new partial copy of this history, for the given user id
* @param uid the user id
* @return a new partial copy of this history, for the given user id
*/
public History forUserId(final long uid) {
return filter(this, primitive -> primitive.getUser() != null && primitive.getUser().getId() == uid);
}
/**
* Replies the primitive id for this history.
*
* @return the primitive id
* @see #getPrimitiveId
* @see #getType
*/
public long getId() {
return id;
}
/**
* Replies the primitive id for this history.
*
* @return the primitive id
* @see #getId
*/
public PrimitiveId getPrimitiveId() {
return new SimplePrimitiveId(id, type);
}
/**
* Determines if this history contains a specific version number.
* @param version the version number to look for
* @return {@code true} if this history contains {@code version}, {@code false} otherwise
*/
public boolean contains(long version) {
for (HistoryOsmPrimitive primitive: versions) {
if (primitive.matches(id, version))
return true;
}
return false;
}
/**
* Replies the history primitive with version <code>version</code>. null,
* if no such primitive exists.
*
* @param version the version
* @return the history primitive with version <code>version</code>
*/
public HistoryOsmPrimitive getByVersion(long version) {
for (HistoryOsmPrimitive primitive: versions) {
if (primitive.matches(id, version))
return primitive;
}
return null;
}
/**
* Replies the history primitive at given <code>date</code>. null,
* if no such primitive exists.
*
* @param date the date
* @return the history primitive at given <code>date</code>
*/
public HistoryOsmPrimitive getByDate(Date date) {
History h = sortAscending();
if (h.versions.isEmpty())
return null;
if (h.get(0).getTimestamp().compareTo(date) > 0)
return null;
for (int i = 1; i < h.versions.size(); i++) {
if (h.get(i-1).getTimestamp().compareTo(date) <= 0
&& h.get(i).getTimestamp().compareTo(date) >= 0)
return h.get(i);
}
return h.getLatest();
}
/**
* Replies the history primitive at index <code>idx</code>.
*
* @param idx the index
* @return the history primitive at index <code>idx</code>
* @throws IndexOutOfBoundsException if index out or range
*/
public HistoryOsmPrimitive get(int idx) {
if (idx < 0 || idx >= versions.size())
throw new IndexOutOfBoundsException(MessageFormat.format(
"Parameter ''{0}'' in range 0..{1} expected. Got ''{2}''.", "idx", versions.size()-1, idx));
return versions.get(idx);
}
/**
* Replies the earliest entry of this history.
* @return the earliest entry of this history
*/
public HistoryOsmPrimitive getEarliest() {
if (isEmpty())
return null;
return sortAscending().versions.get(0);
}
/**
* Replies the latest entry of this history.
* @return the latest entry of this history
*/
public HistoryOsmPrimitive getLatest() {
if (isEmpty())
return null;
return sortDescending().versions.get(0);
}
/**
* Replies the number of versions.
* @return the number of versions
*/
public int getNumVersions() {
return versions.size();
}
/**
* Returns true if this history contains no version.
* @return {@code true} if this history contains no version, {@code false} otherwise
*/
public final boolean isEmpty() {
return versions.isEmpty();
}
/**
* Replies the primitive type for this history.
* @return the primitive type
* @see #getId
*/
public OsmPrimitiveType getType() {
return type;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("History ["
+ (type != null ? ("type=" + type + ", ") : "") + "id=" + id);
if (versions != null) {
result.append(", versions=\n");
for (HistoryOsmPrimitive v : versions) {
result.append('\t').append(v).append(",\n");
}
}
result.append(']');
return result.toString();
}
}