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.Collection; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012import java.util.concurrent.CopyOnWriteArrayList; 013 014import org.openstreetmap.josm.data.osm.Changeset; 015import org.openstreetmap.josm.data.osm.IPrimitive; 016import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 017import org.openstreetmap.josm.data.osm.PrimitiveId; 018import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 019import org.openstreetmap.josm.gui.MainApplication; 020import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 021import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 022import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 023import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 024import org.openstreetmap.josm.tools.CheckParameterUtil; 025 026/** 027 * A data set holding histories of OSM primitives. 028 * @since 1670 029 * @since 10386 (new LayerChangeListener interface) 030 */ 031public class HistoryDataSet implements LayerChangeListener { 032 /** the unique instance */ 033 private static HistoryDataSet historyDataSet; 034 035 /** 036 * Replies the unique instance of the history data set 037 * 038 * @return the unique instance of the history data set 039 */ 040 public static synchronized HistoryDataSet getInstance() { 041 if (historyDataSet == null) { 042 historyDataSet = new HistoryDataSet(); 043 MainApplication.getLayerManager().addLayerChangeListener(historyDataSet); 044 } 045 return historyDataSet; 046 } 047 048 /** the history data */ 049 private final Map<PrimitiveId, ArrayList<HistoryOsmPrimitive>> data; 050 private final CopyOnWriteArrayList<HistoryDataSetListener> listeners; 051 private final Map<Long, Changeset> changesets; 052 053 /** 054 * Constructs a new {@code HistoryDataSet}. 055 */ 056 public HistoryDataSet() { 057 data = new HashMap<>(); 058 listeners = new CopyOnWriteArrayList<>(); 059 changesets = new HashMap<>(); 060 } 061 062 /** 063 * Adds a listener that listens to history data set events. 064 * @param listener The listener 065 */ 066 public void addHistoryDataSetListener(HistoryDataSetListener listener) { 067 if (listener != null) { 068 listeners.addIfAbsent(listener); 069 } 070 } 071 072 /** 073 * Removes a listener that listens to history data set events. 074 * @param listener The listener 075 */ 076 public void removeHistoryDataSetListener(HistoryDataSetListener listener) { 077 listeners.remove(listener); 078 } 079 080 protected void fireHistoryUpdated(PrimitiveId id) { 081 for (HistoryDataSetListener l : listeners) { 082 l.historyUpdated(this, id); 083 } 084 } 085 086 protected void fireCacheCleared() { 087 for (HistoryDataSetListener l : listeners) { 088 l.historyDataSetCleared(this); 089 } 090 } 091 092 /** 093 * Replies the history primitive for the primitive with id <code>id</code> 094 * and version <code>version</code>. null, if no such primitive exists. 095 * 096 * @param id the id of the primitive. > 0 required. 097 * @param type the primitive type. Must not be null. 098 * @param version the version of the primitive. > 0 required 099 * @return the history primitive for the primitive with id <code>id</code>, 100 * type <code>type</code>, and version <code>version</code> 101 */ 102 public HistoryOsmPrimitive get(long id, OsmPrimitiveType type, long version) { 103 if (id <= 0) 104 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id)); 105 CheckParameterUtil.ensureParameterNotNull(type, "type"); 106 if (version <= 0) 107 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "version", version)); 108 109 SimplePrimitiveId pid = new SimplePrimitiveId(id, type); 110 List<HistoryOsmPrimitive> versions = data.get(pid); 111 if (versions == null) 112 return null; 113 for (HistoryOsmPrimitive primitive: versions) { 114 if (primitive.matches(id, version)) 115 return primitive; 116 } 117 return null; 118 } 119 120 /** 121 * Adds a history primitive to the data set 122 * 123 * @param primitive the history primitive to add 124 */ 125 public void put(HistoryOsmPrimitive primitive) { 126 PrimitiveId id = new SimplePrimitiveId(primitive.getId(), primitive.getType()); 127 data.computeIfAbsent(id, k-> new ArrayList<>()).add(primitive); 128 fireHistoryUpdated(id); 129 } 130 131 /** 132 * Adds a changeset to the data set 133 * 134 * @param changeset the changeset to add 135 */ 136 public void putChangeset(Changeset changeset) { 137 changesets.put((long) changeset.getId(), changeset); 138 fireHistoryUpdated(null); 139 } 140 141 /** 142 * Replies the history for a given primitive with id <code>id</code> 143 * and type <code>type</code>. 144 * 145 * @param id the id the if of the primitive. > 0 required 146 * @param type the type of the primitive. Must not be null. 147 * @return the history. null, if there isn't a history for <code>id</code> and 148 * <code>type</code>. 149 * @throws IllegalArgumentException if id <= 0 150 * @throws IllegalArgumentException if type is null 151 */ 152 public History getHistory(long id, OsmPrimitiveType type) { 153 if (id <= 0) 154 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id)); 155 CheckParameterUtil.ensureParameterNotNull(type, "type"); 156 SimplePrimitiveId pid = new SimplePrimitiveId(id, type); 157 return getHistory(pid); 158 } 159 160 /** 161 * Replies the history for a primitive with id <code>id</code>. null, if no 162 * such history exists. 163 * 164 * @param pid the primitive id. Must not be null. 165 * @return the history for a primitive with id <code>id</code>. null, if no 166 * such history exists 167 * @throws IllegalArgumentException if pid is null 168 */ 169 public History getHistory(PrimitiveId pid) { 170 CheckParameterUtil.ensureParameterNotNull(pid, "pid"); 171 List<HistoryOsmPrimitive> versions = data.get(pid); 172 if (versions == null && pid instanceof IPrimitive) { 173 versions = data.get(((IPrimitive) pid).getPrimitiveId()); 174 } 175 if (versions == null) 176 return null; 177 for (HistoryOsmPrimitive i : versions) { 178 i.setChangeset(changesets.get(i.getChangesetId())); 179 } 180 return new History(pid.getUniqueId(), pid.getType(), versions); 181 } 182 183 /** 184 * merges the histories from the {@link HistoryDataSet} other in this history data set 185 * 186 * @param other the other history data set. Ignored if null. 187 */ 188 public void mergeInto(HistoryDataSet other) { 189 if (other == null) 190 return; 191 this.data.putAll(other.data); 192 this.changesets.putAll(other.changesets); 193 fireHistoryUpdated(null); 194 } 195 196 /** 197 * Gets a unsorted set of all changeset ids that were used by the primitives in this data set 198 * @return The ids 199 */ 200 public Collection<Long> getChangesetIds() { 201 final Set<Long> ids = new HashSet<>(); 202 for (Collection<HistoryOsmPrimitive> i : data.values()) { 203 for (HistoryOsmPrimitive j : i) { 204 ids.add(j.getChangesetId()); 205 } 206 } 207 return ids; 208 } 209 210 /* ------------------------------------------------------------------------------ */ 211 /* interface LayerChangeListener */ 212 /* ------------------------------------------------------------------------------ */ 213 @Override 214 public void layerOrderChanged(LayerOrderChangeEvent e) { 215 /* irrelevant in this context */ 216 } 217 218 @Override 219 public void layerAdded(LayerAddEvent e) { 220 /* irrelevant in this context */ 221 } 222 223 @Override 224 public void layerRemoving(LayerRemoveEvent e) { 225 if (!MainApplication.isDisplayingMapView()) return; 226 if (MainApplication.getLayerManager().getLayers().isEmpty()) { 227 data.clear(); 228 fireCacheCleared(); 229 } 230 } 231}