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 * @return an unmodifiable set of all primitives in this dataset. 180 * @since 14946 181 */ 182 public Set<PrimitiveId> getIds() { 183 return Collections.unmodifiableSet(entryMap.keySet()); 184 } 185 186 /** 187 * Replies the first {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset. 188 * null, if there is no such primitive in the data set. 189 * @param id the id 190 * @return the first {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset or null. 191 * @since 14946 192 */ 193 public ChangesetDataSetEntry getFirstEntry(PrimitiveId id) { 194 return getEntry(id, 0); 195 } 196 197 /** 198 * Replies the last {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset. 199 * null, if there is no such primitive in the data set. 200 * @param id the id 201 * @return the last {@link ChangesetDataSetEntry} with id <code>id</code> from this dataset or null. 202 * @since 14946 203 */ 204 public ChangesetDataSetEntry getLastEntry(PrimitiveId id) { 205 return getEntry(id, 1); 206 } 207 208 private ChangesetDataSetEntry getEntry(PrimitiveId id, int n) { 209 if (id == null) 210 return null; 211 Object val = entryMap.get(id); 212 if (val == null) 213 return null; 214 if (val instanceof ChangesetDataSetEntry[]) { 215 ChangesetDataSetEntry[] entries = (ChangesetDataSetEntry[]) val; 216 return entries[n]; 217 } else { 218 return (ChangesetDataSetEntry) val; 219 } 220 } 221 222 /** 223 * Returns an iterator over dataset entries. The elements are returned in no particular order. 224 * @return an iterator over dataset entries. If a primitive was changed multiple times, only the last entry is returned. 225 */ 226 public Iterator<ChangesetDataSetEntry> iterator() { 227 return new DefaultIterator(entryMap); 228 } 229 230 /** 231 * Class to keep one entry of a changeset: the combination of modification type and primitive. 232 */ 233 public static class DefaultChangesetDataSetEntry implements ChangesetDataSetEntry { 234 private final ChangesetModificationType modificationType; 235 private final HistoryOsmPrimitive primitive; 236 237 /** 238 * Construct new entry. 239 * @param modificationType the modification type 240 * @param primitive the primitive 241 */ 242 public DefaultChangesetDataSetEntry(ChangesetModificationType modificationType, HistoryOsmPrimitive primitive) { 243 this.modificationType = modificationType; 244 this.primitive = primitive; 245 } 246 247 @Override 248 public ChangesetModificationType getModificationType() { 249 return modificationType; 250 } 251 252 @Override 253 public HistoryOsmPrimitive getPrimitive() { 254 return primitive; 255 } 256 257 @Override 258 public String toString() { 259 return modificationType.toString() + " " + primitive.toString(); 260 } 261 } 262 263 private static class DefaultIterator implements Iterator<ChangesetDataSetEntry> { 264 private final Iterator<Entry<PrimitiveId, Object>> typeIterator; 265 266 DefaultIterator(Map<PrimitiveId, Object> entryMap) { 267 typeIterator = entryMap.entrySet().iterator(); 268 } 269 270 @Override 271 public boolean hasNext() { 272 return typeIterator.hasNext(); 273 } 274 275 @Override 276 public ChangesetDataSetEntry next() { 277 Entry<PrimitiveId, Object> next = typeIterator.next(); 278 // get last entry 279 Object val = next.getValue(); 280 ChangesetDataSetEntry last; 281 if (val instanceof ChangesetDataSetEntry[]) { 282 ChangesetDataSetEntry[] entries = (ChangesetDataSetEntry[]) val; 283 last = entries[1]; 284 } else { 285 last = (ChangesetDataSetEntry) val; 286 } 287 return new DefaultChangesetDataSetEntry(last.getModificationType(), last.getPrimitive()); 288 } 289 290 @Override 291 public void remove() { 292 throw new UnsupportedOperationException(); 293 } 294 } 295}