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<PrimitiveId, ArrayList<HistoryOsmPrimitive>>();
055        listeners = new CopyOnWriteArrayList<HistoryDataSetListener>();
056        changesets = new HashMap<Long, Changeset>();
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<Long>();
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}