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.Main; 015import org.openstreetmap.josm.data.osm.Changeset; 016import org.openstreetmap.josm.data.osm.IPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.PrimitiveId; 019import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 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 Main.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 public void addHistoryDataSetListener(HistoryDataSetListener listener) { 063 if (listener != null) { 064 listeners.addIfAbsent(listener); 065 } 066 } 067 068 public void removeHistoryDataSetListener(HistoryDataSetListener listener) { 069 listeners.remove(listener); 070 } 071 072 protected void fireHistoryUpdated(PrimitiveId id) { 073 for (HistoryDataSetListener l : listeners) { 074 l.historyUpdated(this, id); 075 } 076 } 077 078 protected void fireCacheCleared() { 079 for (HistoryDataSetListener l : listeners) { 080 l.historyDataSetCleared(this); 081 } 082 } 083 084 /** 085 * Replies the history primitive for the primitive with id <code>id</code> 086 * and version <code>version</code>. null, if no such primitive exists. 087 * 088 * @param id the id of the primitive. > 0 required. 089 * @param type the primitive type. Must not be null. 090 * @param version the version of the primitive. > 0 required 091 * @return the history primitive for the primitive with id <code>id</code>, 092 * type <code>type</code>, and version <code>version</code> 093 */ 094 public HistoryOsmPrimitive get(long id, OsmPrimitiveType type, long version) { 095 if (id <= 0) 096 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id)); 097 CheckParameterUtil.ensureParameterNotNull(type, "type"); 098 if (version <= 0) 099 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "version", version)); 100 101 SimplePrimitiveId pid = new SimplePrimitiveId(id, type); 102 List<HistoryOsmPrimitive> versions = data.get(pid); 103 if (versions == null) 104 return null; 105 for (HistoryOsmPrimitive primitive: versions) { 106 if (primitive.matches(id, version)) 107 return primitive; 108 } 109 return null; 110 } 111 112 /** 113 * Adds a history primitive to the data set 114 * 115 * @param primitive the history primitive to add 116 */ 117 public void put(HistoryOsmPrimitive primitive) { 118 PrimitiveId id = new SimplePrimitiveId(primitive.getId(), primitive.getType()); 119 if (data.get(id) == null) { 120 data.put(id, new ArrayList<HistoryOsmPrimitive>()); 121 } 122 data.get(id).add(primitive); 123 fireHistoryUpdated(id); 124 } 125 126 /** 127 * Adds a changeset to the data set 128 * 129 * @param changeset the changeset to add 130 */ 131 public void putChangeset(Changeset changeset) { 132 changesets.put((long) changeset.getId(), changeset); 133 fireHistoryUpdated(null); 134 } 135 136 /** 137 * Replies the history for a given primitive with id <code>id</code> 138 * and type <code>type</code>. 139 * 140 * @param id the id the if of the primitive. > 0 required 141 * @param type the type of the primitive. Must not be null. 142 * @return the history. null, if there isn't a history for <code>id</code> and 143 * <code>type</code>. 144 * @throws IllegalArgumentException if id <= 0 145 * @throws IllegalArgumentException if type is null 146 */ 147 public History getHistory(long id, OsmPrimitiveType type) { 148 if (id <= 0) 149 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected, got {1}", "id", id)); 150 CheckParameterUtil.ensureParameterNotNull(type, "type"); 151 SimplePrimitiveId pid = new SimplePrimitiveId(id, type); 152 return getHistory(pid); 153 } 154 155 /** 156 * Replies the history for a primitive with id <code>id</code>. null, if no 157 * such history exists. 158 * 159 * @param pid the primitive id. Must not be null. 160 * @return the history for a primitive with id <code>id</code>. null, if no 161 * such history exists 162 * @throws IllegalArgumentException if pid is null 163 */ 164 public History getHistory(PrimitiveId pid) { 165 CheckParameterUtil.ensureParameterNotNull(pid, "pid"); 166 List<HistoryOsmPrimitive> versions = data.get(pid); 167 if (versions == null && pid instanceof IPrimitive) { 168 versions = data.get(((IPrimitive) pid).getPrimitiveId()); 169 } 170 if (versions == null) 171 return null; 172 for (HistoryOsmPrimitive i : versions) { 173 i.setChangeset(changesets.get(i.getChangesetId())); 174 } 175 return new History(pid.getUniqueId(), pid.getType(), versions); 176 } 177 178 /** 179 * merges the histories from the {@link HistoryDataSet} other in this history data set 180 * 181 * @param other the other history data set. Ignored if null. 182 */ 183 public void mergeInto(HistoryDataSet other) { 184 if (other == null) 185 return; 186 this.data.putAll(other.data); 187 this.changesets.putAll(other.changesets); 188 fireHistoryUpdated(null); 189 } 190 191 public Collection<Long> getChangesetIds() { 192 final Set<Long> ids = new HashSet<>(); 193 for (Collection<HistoryOsmPrimitive> i : data.values()) { 194 for (HistoryOsmPrimitive j : i) { 195 ids.add(j.getChangesetId()); 196 } 197 } 198 return ids; 199 } 200 201 /* ------------------------------------------------------------------------------ */ 202 /* interface LayerChangeListener */ 203 /* ------------------------------------------------------------------------------ */ 204 @Override 205 public void layerOrderChanged(LayerOrderChangeEvent e) { 206 /* irrelevant in this context */ 207 } 208 209 @Override 210 public void layerAdded(LayerAddEvent e) { 211 /* irrelevant in this context */ 212 } 213 214 @Override 215 public void layerRemoving(LayerRemoveEvent e) { 216 if (!Main.isDisplayingMapView()) return; 217 if (Main.getLayerManager().getLayers().isEmpty()) { 218 data.clear(); 219 fireCacheCleared(); 220 } 221 } 222}