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. &gt; 0 required.
087     * @param type the primitive type. Must not be null.
088     * @param version the version of the primitive. &gt; 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. &gt; 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 &lt;= 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}