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<>(); 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<>(); 059 if (versions != null) { 060 this.versions.addAll(versions); 061 } 062 } 063 064 public History sortAscending() { 065 List<HistoryOsmPrimitive> copy = new ArrayList<>(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<>(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) throws IndexOutOfBoundsException { 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 /** 234 * Returns true if this history contains no version. 235 * @return {@code true} if this history contains no version, {@code false} otherwise 236 */ 237 public final boolean isEmpty() { 238 return versions.isEmpty(); 239 } 240 241 public OsmPrimitiveType getType() { 242 return type; 243 } 244 245 @Override 246 public String toString() { 247 StringBuilder result = new StringBuilder("History [" 248 + (type != null ? "type=" + type + ", " : "") + "id=" + id); 249 if (versions != null) { 250 result.append(", versions=\n"); 251 for (HistoryOsmPrimitive v : versions) { 252 result.append("\t").append(v).append(",\n"); 253 } 254 } 255 result.append("]"); 256 return result.toString(); 257 } 258}