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