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