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