001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm.history;
003
004import java.text.MessageFormat;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.Date;
009import java.util.List;
010
011import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
012import org.openstreetmap.josm.data.osm.PrimitiveId;
013import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
014import org.openstreetmap.josm.tools.CheckParameterUtil;
015
016/**
017 * Represents the history of an OSM primitive. The history consists
018 * of a list of object snapshots with a specific version.
019 *
020 */
021public class History{
022    private static interface FilterPredicate {
023        boolean matches(HistoryOsmPrimitive primitive);
024    }
025
026    private static History filter(History history, FilterPredicate predicate) {
027        List<HistoryOsmPrimitive> out = new ArrayList<HistoryOsmPrimitive>();
028        for (HistoryOsmPrimitive primitive: history.versions) {
029            if (predicate.matches(primitive)) {
030                out.add(primitive);
031            }
032        }
033        return new History(history.id, history.type,out);
034    }
035
036    /** the list of object snapshots */
037    private List<HistoryOsmPrimitive> versions;
038    /** the object id */
039    private final long id;
040    private final OsmPrimitiveType type;
041
042    /**
043     * Creates a new history for an OSM primitive
044     *
045     * @param id the id. >0 required.
046     * @param type the primitive type. Must not be null.
047     * @param versions a list of versions. Can be null.
048     * @throws IllegalArgumentException thrown if id <= 0
049     * @throws IllegalArgumentException if type is null
050     *
051     */
052    protected History(long id, OsmPrimitiveType type, List<HistoryOsmPrimitive> versions) {
053        if (id <= 0)
054            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id));
055        CheckParameterUtil.ensureParameterNotNull(type, "type");
056        this.id = id;
057        this.type = type;
058        this.versions = new ArrayList<HistoryOsmPrimitive>();
059        if (versions != null) {
060            this.versions.addAll(versions);
061        }
062    }
063
064    public History sortAscending() {
065        List<HistoryOsmPrimitive> copy = new ArrayList<HistoryOsmPrimitive>(versions);
066        Collections.sort(
067                copy,
068                new Comparator<HistoryOsmPrimitive>() {
069                    @Override
070                    public int compare(HistoryOsmPrimitive o1, HistoryOsmPrimitive o2) {
071                        return o1.compareTo(o2);
072                    }
073                }
074                );
075        return new History(id, type, copy);
076    }
077
078    public History sortDescending() {
079        List<HistoryOsmPrimitive> copy = new ArrayList<HistoryOsmPrimitive>(versions);
080        Collections.sort(
081                copy,
082                new Comparator<HistoryOsmPrimitive>() {
083                    @Override
084                    public int compare(HistoryOsmPrimitive o1, HistoryOsmPrimitive o2) {
085                        return o2.compareTo(o1);
086                    }
087                }
088                );
089        return new History(id, type,copy);
090    }
091
092    public History from(final Date fromDate) {
093        return filter(
094                this,
095                new FilterPredicate() {
096                    @Override
097                    public boolean matches(HistoryOsmPrimitive primitive) {
098                        return primitive.getTimestamp().compareTo(fromDate) >= 0;
099                    }
100                }
101                );
102    }
103
104    public History until(final Date untilDate) {
105        return filter(
106                this,
107                new FilterPredicate() {
108                    @Override
109                    public boolean matches(HistoryOsmPrimitive primitive) {
110                        return primitive.getTimestamp().compareTo(untilDate) <= 0;
111                    }
112                }
113                );
114    }
115
116    public History between(Date fromDate, Date untilDate) {
117        return this.from(fromDate).until(untilDate);
118    }
119
120    public History from(final long fromVersion) {
121        return filter(
122                this,
123                new FilterPredicate() {
124                    @Override
125                    public boolean matches(HistoryOsmPrimitive primitive) {
126                        return primitive.getVersion() >= fromVersion;
127                    }
128                }
129                );
130    }
131
132    public History until(final long untilVersion) {
133        return filter(
134                this,
135                new FilterPredicate() {
136                    @Override
137                    public boolean matches(HistoryOsmPrimitive primitive) {
138                        return primitive.getVersion() <= untilVersion;
139                    }
140                }
141                );
142    }
143
144    public History between(long fromVersion, long untilVersion) {
145        return this.from(fromVersion).until(untilVersion);
146    }
147
148    public History forUserId(final long uid) {
149        return filter(
150                this,
151                new FilterPredicate() {
152                    @Override
153                    public boolean matches(HistoryOsmPrimitive primitive) {
154                        return primitive.getUser() != null && primitive.getUser().getId() == uid;
155                    }
156                }
157                );
158    }
159
160    public long getId() {
161        return id;
162    }
163
164    /**
165     * Replies the primitive id for this history.
166     *
167     * @return the primitive id
168     */
169    public PrimitiveId getPrimitiveId() {
170        return new SimplePrimitiveId(id, type);
171    }
172
173    public boolean contains(long version){
174        for (HistoryOsmPrimitive primitive: versions) {
175            if (primitive.matches(id,version))
176                return true;
177        }
178        return false;
179    }
180
181    /**
182     * Replies the history primitive with version <code>version</code>. null,
183     * if no such primitive exists.
184     *
185     * @param version the version
186     * @return the history primitive with version <code>version</code>
187     */
188    public HistoryOsmPrimitive getByVersion(long version) {
189        for (HistoryOsmPrimitive primitive: versions) {
190            if (primitive.matches(id,version))
191                return primitive;
192        }
193        return null;
194    }
195
196    public HistoryOsmPrimitive getByDate(Date date) {
197        History h = sortAscending();
198
199        if (h.versions.isEmpty())
200            return null;
201        if (h.get(0).getTimestamp().compareTo(date)> 0)
202            return null;
203        for (int i = 1; i < h.versions.size();i++) {
204            if (h.get(i-1).getTimestamp().compareTo(date) <= 0
205                    && h.get(i).getTimestamp().compareTo(date) >= 0)
206                return h.get(i);
207        }
208        return h.getLatest();
209    }
210
211    public HistoryOsmPrimitive get(int idx) {
212        if (idx < 0 || idx >= versions.size())
213            throw new IndexOutOfBoundsException(MessageFormat.format("Parameter ''{0}'' in range 0..{1} expected. Got ''{2}''.", "idx", versions.size()-1, idx));
214        return versions.get(idx);
215    }
216
217    public HistoryOsmPrimitive getEarliest() {
218        if (isEmpty())
219            return null;
220        return sortAscending().versions.get(0);
221    }
222
223    public HistoryOsmPrimitive getLatest() {
224        if (isEmpty())
225            return null;
226        return sortDescending().versions.get(0);
227    }
228
229    public int getNumVersions() {
230        return versions.size();
231    }
232
233    public boolean isEmpty() {
234        return versions.isEmpty();
235    }
236
237    public OsmPrimitiveType getType() {
238        return type;
239    }
240
241    @Override
242    public String toString() {
243        StringBuilder result = new StringBuilder("History ["
244                + (type != null ? "type=" + type + ", " : "") + "id=" + id);
245        if (versions != null) {
246            result.append(", versions=\n");
247            for (HistoryOsmPrimitive v : versions) {
248                result.append("\t").append(v).append(",\n");
249            }
250        }
251        result.append("]");
252        return result.toString();
253    }
254}