001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003import static org.openstreetmap.josm.tools.I18n.tr; 004 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.Iterator; 008import java.util.Map; 009import java.util.Map.Entry; 010import java.util.Set; 011 012import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 013import org.openstreetmap.josm.tools.CheckParameterUtil; 014import org.openstreetmap.josm.tools.Logging; 015 016/** 017 * A ChangesetDataSet holds the content of a changeset. Typically, a primitive is modified only once in a changeset, 018 * but if there are multiple modifications, the first and last are kept. Further intermediate versions are not kept. 019 */ 020public class ChangesetDataSet { 021 022 /** 023 * Type of primitive modification. 024 */ 025 public enum ChangesetModificationType { 026 /** The primitive has been created */ 027 CREATED, 028 /** The primitive has been updated */ 029 UPDATED, 030 /** The primitive has been deleted */ 031 DELETED 032 } 033 034 /** 035 * An entry in the changeset dataset. 036 */ 037 public interface ChangesetDataSetEntry { 038 039 /** 040 * Returns the type of modification. 041 * @return the type of modification 042 */ 043 ChangesetModificationType getModificationType(); 044 045 /** 046 * Returns the affected history primitive. 047 * @return the affected history primitive 048 */ 049 HistoryOsmPrimitive getPrimitive(); 050 } 051 052 /** maps an id to either one {@link ChangesetDataSetEntry} or an array of {@link ChangesetDataSetEntry} */ 053 private final Map<PrimitiveId, Object> entryMap = new HashMap<>(); 054 055 /** 056 * Remembers a history primitive with the given modification type 057 * 058 * @param primitive the primitive. Must not be null. 059 * @param cmt the modification type. Must not be null. 060 * @throws IllegalArgumentException if primitive is null 061 * @throws IllegalArgumentException if cmt is null 062 * @throws IllegalArgumentException if the same primitive was already stored with a higher or equal version 063 */ 064 public void put(HistoryOsmPrimitive primitive, ChangesetModificationType cmt) { 065 CheckParameterUtil.ensureParameterNotNull(primitive, "primitive"); 066 CheckParameterUtil.ensureParameterNotNull(cmt, "cmt"); 067 DefaultChangesetDataSetEntry csEntry = new DefaultChangesetDataSetEntry(cmt, primitive); 068 Object val = entryMap.get(primitive.getPrimitiveId()); 069 ChangesetDataSetEntry[] entries; 070 if (val == null) { 071 entryMap.put(primitive.getPrimitiveId(), csEntry); 072 return; 073 } 074 if (val instanceof ChangesetDataSetEntry) { 075 entries = new ChangesetDataSetEntry[2]; 076 entries[0] = (ChangesetDataSetEntry) val; 077 if (primitive.getVersion() <= entries[0].getPrimitive().getVersion()) { 078 throw new IllegalArgumentException( 079 tr("Changeset {0}: Unexpected order of versions for {1}: v{2} is not higher than v{3}", 080 String.valueOf(primitive.getChangesetId()), primitive.getPrimitiveId(), 081 primitive.getVersion(), entries[0].getPrimitive().getVersion())); 082 } 083 } else { 084 entries = (ChangesetDataSetEntry[]) val; 085 } 086 if (entries[1] != null) { 087 Logging.info("Changeset {0}: Change of {1} v{2} is replaced by version v{3}", 088 String.valueOf(primitive.getChangesetId()), primitive.getPrimitiveId(), 089 entries[1].getPrimitive().getVersion(), primitive.getVersion()); 090 } 091 entries[1] = csEntry; 092 entryMap.put(primitive.getPrimitiveId(), entries); 093 } 094 095 /** 096 * Replies true if the changeset content contains the object with primitive <code>id</code>. 097 * @param id the id. 098 * @return true if the changeset content contains the object with primitive <code>id</code> 099 */ 100 public boolean contains(PrimitiveId id) { 101 if (id == null) return false; 102 return entryMap.containsKey(id); 103 } 104 105 /** 106 * Replies the last modification type for the object with id <code>id</code>. Replies null, if id is null or 107 * if the object with id <code>id</code> isn't in the changeset content. 108 * 109 * @param id the id 110 * @return the last modification type or null 111 */ 112 public ChangesetModificationType getModificationType(PrimitiveId id) { 113 ChangesetDataSetEntry e = getLastEntry(id); 114 return e != null ? e.getModificationType() : null; 115 } 116 117 /** 118 * Replies true if the primitive with id <code>id</code> was created in this 119 * changeset. Replies false, if id is null or not in the dataset. 120 * 121 * @param id the id 122 * @return true if the primitive with id <code>id</code> was created in this 123 * changeset. 124 */ 125 public boolean isCreated(PrimitiveId id) { 126 ChangesetDataSetEntry e = getFirstEntry(id); 127 return e != null && e.getModificationType() == ChangesetModificationType.CREATED; 128 } 129 130 /** 131 * Replies true if the primitive with id <code>id</code> was updated in this 132 * changeset. Replies false, if id is null or not in the dataset. 133 * 134 * @param id the id 135 * @return true if the primitive with id <code>id</code> was updated in this 136 * changeset. 137 */ 138 public boolean isUpdated(PrimitiveId id) { 139 ChangesetDataSetEntry e = getLastEntry(id); 140 return e != null && e.getModificationType() == ChangesetModificationType.UPDATED; 141 } 142 143 /** 144 * Replies true if the primitive with id <code>id</code> was deleted in this 145 * changeset. Replies false, if id is null or not in the dataset. 146 * 147 * @param id the id 148 * @return true if the primitive with id <code>id</code> was deleted in this 149 * changeset. 150 */ 151 public boolean isDeleted(PrimitiveId id) { 152 ChangesetDataSetEntry e = getLastEntry(id); 153 return e != null && e.getModificationType() == ChangesetModificationType.DELETED; 154 } 155 156 /** 157 * Replies the number of primitives in the dataset. 158 * 159 * @return the number of primitives in the dataset. 160 */ 161 public int size() { 162 return entryMap.size(); 163 } 164 165 /** 166 * Replies the {@link HistoryOsmPrimitive} with id <code>id</code> from this dataset. 167 * null, if there is no such primitive in the data set. If the primitive was modified 168 * multiple times, the last version is returned. 169 * 170 * @param id the id 171 * @return the {@link HistoryOsmPrimitive} with id <code>id</code> from this dataset 172 */ 173 public HistoryOsmPrimitive getPrimitive(PrimitiveId id) { 174 ChangesetDataSetEntry e = getLastEntry(id); 175 return e != null ? e.getPrimitive() : null; 176 } 177 178 /** 179 * Returns an unmodifiable set of all primitives in this dataset. 180 * @return an unmodifiable set of all primitives in this dataset. 181 * @since 14946 182 */ 183 public Set<PrimitiveId> getIds() { 184 return Collections.unmodifiableSet(entryMap.keySet()); 185 } 186 187 /** 188 * Replies the first {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset. 189 * null, if there is no such primitive in the data set. 190 * @param id the id 191 * @return the first {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset or null. 192 * @since 14946 193 */ 194 public ChangesetDataSetEntry getFirstEntry(PrimitiveId id) { 195 return getEntry(id, 0); 196 } 197 198 /** 199 * Replies the last {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset. 200 * null, if there is no such primitive in the data set. 201 * @param id the id 202 * @return the last {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset or null. 203 * @since 14946 204 */ 205 public ChangesetDataSetEntry getLastEntry(PrimitiveId id) { 206 return getEntry(id, 1); 207 } 208 209 private ChangesetDataSetEntry getEntry(PrimitiveId id, int n) { 210 if (id == null) 211 return null; 212 Object val = entryMap.get(id); 213 if (val == null) 214 return null; 215 if (val instanceof ChangesetDataSetEntry[]) { 216 ChangesetDataSetEntry[] entries = (ChangesetDataSetEntry[]) val; 217 return entries[n]; 218 } else { 219 return (ChangesetDataSetEntry) val; 220 } 221 } 222 223 /** 224 * Returns an iterator over dataset entries. The elements are returned in no particular order. 225 * @return an iterator over dataset entries. If a primitive was changed multiple times, only the last entry is returned. 226 */ 227 public Iterator<ChangesetDataSetEntry> iterator() { 228 return new DefaultIterator(entryMap); 229 } 230 231 /** 232 * Class to keep one entry of a changeset: the combination of modification type and primitive. 233 */ 234 public static class DefaultChangesetDataSetEntry implements ChangesetDataSetEntry { 235 private final ChangesetModificationType modificationType; 236 private final HistoryOsmPrimitive primitive; 237 238 /** 239 * Construct new entry. 240 * @param modificationType the modification type 241 * @param primitive the primitive 242 */ 243 public DefaultChangesetDataSetEntry(ChangesetModificationType modificationType, HistoryOsmPrimitive primitive) { 244 this.modificationType = modificationType; 245 this.primitive = primitive; 246 } 247 248 @Override 249 public ChangesetModificationType getModificationType() { 250 return modificationType; 251 } 252 253 @Override 254 public HistoryOsmPrimitive getPrimitive() { 255 return primitive; 256 } 257 258 @Override 259 public String toString() { 260 return modificationType.toString() + " " + primitive.toString(); 261 } 262 } 263 264 private static class DefaultIterator implements Iterator<ChangesetDataSetEntry> { 265 private final Iterator<Entry<PrimitiveId, Object>> typeIterator; 266 267 DefaultIterator(Map<PrimitiveId, Object> entryMap) { 268 typeIterator = entryMap.entrySet().iterator(); 269 } 270 271 @Override 272 public boolean hasNext() { 273 return typeIterator.hasNext(); 274 } 275 276 @Override 277 public ChangesetDataSetEntry next() { 278 Entry<PrimitiveId, Object> next = typeIterator.next(); 279 // get last entry 280 Object val = next.getValue(); 281 ChangesetDataSetEntry last; 282 if (val instanceof ChangesetDataSetEntry[]) { 283 ChangesetDataSetEntry[] entries = (ChangesetDataSetEntry[]) val; 284 last = entries[1]; 285 } else { 286 last = (ChangesetDataSetEntry) val; 287 } 288 return new DefaultChangesetDataSetEntry(last.getModificationType(), last.getPrimitive()); 289 } 290 291 @Override 292 public void remove() { 293 throw new UnsupportedOperationException(); 294 } 295 } 296}