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 * @since 1670 020 */ 021public class History { 022 023 private static interface FilterPredicate { 024 boolean matches(HistoryOsmPrimitive primitive); 025 } 026 027 private static History filter(History history, FilterPredicate predicate) { 028 List<HistoryOsmPrimitive> out = new ArrayList<>(); 029 for (HistoryOsmPrimitive primitive: history.versions) { 030 if (predicate.matches(primitive)) { 031 out.add(primitive); 032 } 033 } 034 return new History(history.id, history.type,out); 035 } 036 037 /** the list of object snapshots */ 038 private List<HistoryOsmPrimitive> versions; 039 /** the object id */ 040 private final long id; 041 /** the object type */ 042 private final OsmPrimitiveType type; 043 044 /** 045 * Creates a new history for an OSM primitive. 046 * 047 * @param id the id. > 0 required. 048 * @param type the primitive type. Must not be null. 049 * @param versions a list of versions. Can be null. 050 * @throws IllegalArgumentException thrown if id <= 0 051 * @throws IllegalArgumentException if type is null 052 * 053 */ 054 protected History(long id, OsmPrimitiveType type, List<HistoryOsmPrimitive> versions) { 055 if (id <= 0) 056 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id)); 057 CheckParameterUtil.ensureParameterNotNull(type, "type"); 058 this.id = id; 059 this.type = type; 060 this.versions = new ArrayList<>(); 061 if (versions != null) { 062 this.versions.addAll(versions); 063 } 064 } 065 066 /** 067 * Returns a new copy of this history, sorted in ascending order. 068 * @return a new copy of this history, sorted in ascending order 069 */ 070 public History sortAscending() { 071 List<HistoryOsmPrimitive> copy = new ArrayList<>(versions); 072 Collections.sort( 073 copy, 074 new Comparator<HistoryOsmPrimitive>() { 075 @Override 076 public int compare(HistoryOsmPrimitive o1, HistoryOsmPrimitive o2) { 077 return o1.compareTo(o2); 078 } 079 } 080 ); 081 return new History(id, type, copy); 082 } 083 084 /** 085 * Returns a new copy of this history, sorted in descending order. 086 * @return a new copy of this history, sorted in descending order 087 */ 088 public History sortDescending() { 089 List<HistoryOsmPrimitive> copy = new ArrayList<>(versions); 090 Collections.sort( 091 copy, 092 new Comparator<HistoryOsmPrimitive>() { 093 @Override 094 public int compare(HistoryOsmPrimitive o1, HistoryOsmPrimitive o2) { 095 return o2.compareTo(o1); 096 } 097 } 098 ); 099 return new History(id, type,copy); 100 } 101 102 /** 103 * Returns a new partial copy of this history, from the given date 104 * @param fromDate the starting date 105 * @return a new partial copy of this history, from the given date 106 */ 107 public History from(final Date fromDate) { 108 return filter( 109 this, 110 new FilterPredicate() { 111 @Override 112 public boolean matches(HistoryOsmPrimitive primitive) { 113 return primitive.getTimestamp().compareTo(fromDate) >= 0; 114 } 115 } 116 ); 117 } 118 119 /** 120 * Returns a new partial copy of this history, until the given date 121 * @param untilDate the end date 122 * @return a new partial copy of this history, until the given date 123 */ 124 public History until(final Date untilDate) { 125 return filter( 126 this, 127 new FilterPredicate() { 128 @Override 129 public boolean matches(HistoryOsmPrimitive primitive) { 130 return primitive.getTimestamp().compareTo(untilDate) <= 0; 131 } 132 } 133 ); 134 } 135 136 /** 137 * Returns a new partial copy of this history, between the given dates 138 * @param fromDate the starting date 139 * @param untilDate the end date 140 * @return a new partial copy of this history, between the given dates 141 */ 142 public History between(Date fromDate, Date untilDate) { 143 return this.from(fromDate).until(untilDate); 144 } 145 146 /** 147 * Returns a new partial copy of this history, from the given version number 148 * @param fromVersion the starting version number 149 * @return a new partial copy of this history, from the given version number 150 */ 151 public History from(final long fromVersion) { 152 return filter( 153 this, 154 new FilterPredicate() { 155 @Override 156 public boolean matches(HistoryOsmPrimitive primitive) { 157 return primitive.getVersion() >= fromVersion; 158 } 159 } 160 ); 161 } 162 163 /** 164 * Returns a new partial copy of this history, to the given version number 165 * @param untilVersion the ending version number 166 * @return a new partial copy of this history, to the given version number 167 */ 168 public History until(final long untilVersion) { 169 return filter( 170 this, 171 new FilterPredicate() { 172 @Override 173 public boolean matches(HistoryOsmPrimitive primitive) { 174 return primitive.getVersion() <= untilVersion; 175 } 176 } 177 ); 178 } 179 180 /** 181 * Returns a new partial copy of this history, betwwen the given version numbers 182 * @param fromVersion the starting version number 183 * @param untilVersion the ending version number 184 * @return a new partial copy of this history, between the given version numbers 185 */ 186 public History between(long fromVersion, long untilVersion) { 187 return this.from(fromVersion).until(untilVersion); 188 } 189 190 /** 191 * Returns a new partial copy of this history, for the given user id 192 * @param uid the user id 193 * @return a new partial copy of this history, for the given user id 194 */ 195 public History forUserId(final long uid) { 196 return filter( 197 this, 198 new FilterPredicate() { 199 @Override 200 public boolean matches(HistoryOsmPrimitive primitive) { 201 return primitive.getUser() != null && primitive.getUser().getId() == uid; 202 } 203 } 204 ); 205 } 206 207 /** 208 * Replies the primitive id for this history. 209 * 210 * @return the primitive id 211 * @see #getPrimitiveId 212 * @see #getType 213 */ 214 public long getId() { 215 return id; 216 } 217 218 /** 219 * Replies the primitive id for this history. 220 * 221 * @return the primitive id 222 * @see #getId 223 */ 224 public PrimitiveId getPrimitiveId() { 225 return new SimplePrimitiveId(id, type); 226 } 227 228 /** 229 * Determines if this history contains a specific version number. 230 * @param version the version number to look for 231 * @return {@code true} if this history contains {@code version}, {@code false} otherwise 232 */ 233 public boolean contains(long version) { 234 for (HistoryOsmPrimitive primitive: versions) { 235 if (primitive.matches(id,version)) 236 return true; 237 } 238 return false; 239 } 240 241 /** 242 * Replies the history primitive with version <code>version</code>. null, 243 * if no such primitive exists. 244 * 245 * @param version the version 246 * @return the history primitive with version <code>version</code> 247 */ 248 public HistoryOsmPrimitive getByVersion(long version) { 249 for (HistoryOsmPrimitive primitive: versions) { 250 if (primitive.matches(id,version)) 251 return primitive; 252 } 253 return null; 254 } 255 256 /** 257 * Replies the history primitive at given <code>date</code>. null, 258 * if no such primitive exists. 259 * 260 * @param date the date 261 * @return the history primitive at given <code>date</code> 262 */ 263 public HistoryOsmPrimitive getByDate(Date date) { 264 History h = sortAscending(); 265 266 if (h.versions.isEmpty()) 267 return null; 268 if (h.get(0).getTimestamp().compareTo(date)> 0) 269 return null; 270 for (int i = 1; i < h.versions.size();i++) { 271 if (h.get(i-1).getTimestamp().compareTo(date) <= 0 272 && h.get(i).getTimestamp().compareTo(date) >= 0) 273 return h.get(i); 274 } 275 return h.getLatest(); 276 } 277 278 /** 279 * Replies the history primitive at index <code>idx</code>. 280 * 281 * @param idx the index 282 * @return the history primitive at index <code>idx</code> 283 * @throws IndexOutOfBoundsException if index out or range 284 */ 285 public HistoryOsmPrimitive get(int idx) throws IndexOutOfBoundsException { 286 if (idx < 0 || idx >= versions.size()) 287 throw new IndexOutOfBoundsException(MessageFormat.format( 288 "Parameter ''{0}'' in range 0..{1} expected. Got ''{2}''.", "idx", versions.size()-1, idx)); 289 return versions.get(idx); 290 } 291 292 /** 293 * Replies the earliest entry of this history. 294 * @return the earliest entry of this history 295 */ 296 public HistoryOsmPrimitive getEarliest() { 297 if (isEmpty()) 298 return null; 299 return sortAscending().versions.get(0); 300 } 301 302 /** 303 * Replies the latest entry of this history. 304 * @return the latest entry of this history 305 */ 306 public HistoryOsmPrimitive getLatest() { 307 if (isEmpty()) 308 return null; 309 return sortDescending().versions.get(0); 310 } 311 312 /** 313 * Replies the number of versions. 314 * @return the number of versions 315 */ 316 public int getNumVersions() { 317 return versions.size(); 318 } 319 320 /** 321 * Returns true if this history contains no version. 322 * @return {@code true} if this history contains no version, {@code false} otherwise 323 */ 324 public final boolean isEmpty() { 325 return versions.isEmpty(); 326 } 327 328 /** 329 * Replies the primitive type for this history. 330 * @return the primitive type 331 * @see #getId 332 */ 333 public OsmPrimitiveType getType() { 334 return type; 335 } 336 337 @Override 338 public String toString() { 339 StringBuilder result = new StringBuilder("History [" 340 + (type != null ? "type=" + type + ", " : "") + "id=" + id); 341 if (versions != null) { 342 result.append(", versions=\n"); 343 for (HistoryOsmPrimitive v : versions) { 344 result.append("\t").append(v).append(",\n"); 345 } 346 } 347 result.append("]"); 348 return result.toString(); 349 } 350}