001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Component;
008import java.io.IOException;
009import java.text.MessageFormat;
010import java.util.Collection;
011import java.util.HashSet;
012import java.util.Set;
013
014import org.openstreetmap.josm.data.osm.Changeset;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
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.data.osm.history.History;
020import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
021import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
022import org.openstreetmap.josm.gui.ExceptionDialogUtil;
023import org.openstreetmap.josm.gui.PleaseWaitRunnable;
024import org.openstreetmap.josm.io.ChangesetQuery;
025import org.openstreetmap.josm.io.OsmServerChangesetReader;
026import org.openstreetmap.josm.io.OsmServerHistoryReader;
027import org.openstreetmap.josm.io.OsmTransferException;
028import org.openstreetmap.josm.tools.CheckParameterUtil;
029import org.xml.sax.SAXException;
030
031/**
032 * Loads the object history of an collection of objects from the
033 * server.
034 *
035 * It provides a fluent API for configuration.
036 *
037 * Sample usage:
038 *
039 * <pre>
040 *   HistoryLoadTask task  = new HistoryLoadTask()
041 *      .add(1, OsmPrimitiveType.NODE)
042 *      .add(1233, OsmPrimitiveType.WAY)
043 *      .add(37234, OsmPrimitveType.RELATION)
044 *      .add(aHistoryItem);
045 *
046 *   Main.worker.execute(task);
047 *
048 * </pre>
049 */
050public class HistoryLoadTask extends PleaseWaitRunnable {
051
052    private boolean canceled;
053    private Exception lastException;
054    private Set<PrimitiveId> toLoad;
055    private HistoryDataSet loadedData;
056    private OsmServerHistoryReader reader;
057
058    /**
059     * Constructs a new {@code HistoryLoadTask}.
060     */
061    public HistoryLoadTask() {
062        super(tr("Load history"), true);
063        toLoad = new HashSet<>();
064    }
065
066    /**
067     * Constructs a new {@code HistoryLoadTask}.
068     *
069     * @param parent the component to be used as reference to find the
070     * parent for {@link org.openstreetmap.josm.gui.PleaseWaitDialog}.
071     * Must not be <code>null</code>.
072     * @throws IllegalArgumentException if parent is <code>null</code>
073     */
074    public HistoryLoadTask(Component parent) {
075        super(parent, tr("Load history"), true);
076        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
077        toLoad = new HashSet<>();
078    }
079
080    /**
081     * Adds an object whose history is to be loaded.
082     *
083     * @param id the object id
084     * @param type the object type
085     * @return this task
086     */
087    public HistoryLoadTask add(long id, OsmPrimitiveType type) {
088        if (id <= 0)
089            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got {1}.", "id", id));
090        CheckParameterUtil.ensureParameterNotNull(type, "type");
091        SimplePrimitiveId pid = new SimplePrimitiveId(id, type);
092        toLoad.add(pid);
093        return this;
094    }
095
096    /**
097     * Adds an object whose history is to be loaded.
098     *
099     * @param pid  the primitive id. Must not be null. Id &gt; 0 required.
100     * @return this task
101     */
102    public HistoryLoadTask add(PrimitiveId pid) {
103        CheckParameterUtil.ensureValidPrimitiveId(pid, "pid");
104        toLoad.add(pid);
105        return this;
106    }
107
108    /**
109     * Adds an object to be loaded, the object is specified by a history item.
110     *
111     * @param primitive the history item
112     * @return this task
113     * @throws IllegalArgumentException if primitive is null
114     */
115    public HistoryLoadTask add(HistoryOsmPrimitive primitive) {
116        CheckParameterUtil.ensureParameterNotNull(primitive, "primitive");
117        toLoad.add(primitive.getPrimitiveId());
118        return this;
119    }
120
121    /**
122     * Adds an object to be loaded, the object is specified by an already loaded object history.
123     *
124     * @param history the history. Must not be null.
125     * @return this task
126     * @throws IllegalArgumentException if history is null
127     */
128    public HistoryLoadTask add(History history) {
129        CheckParameterUtil.ensureParameterNotNull(history, "history");
130        toLoad.add(history.getPrimitiveId());
131        return this;
132    }
133
134    /**
135     * Adds an object to be loaded, the object is specified by an OSM primitive.
136     *
137     * @param primitive the OSM primitive. Must not be null. primitive.getId() &gt; 0 required.
138     * @return this task
139     * @throws IllegalArgumentException if the primitive is null
140     * @throws IllegalArgumentException if primitive.getId() &lt;= 0
141     */
142    public HistoryLoadTask add(OsmPrimitive primitive) {
143        CheckParameterUtil.ensureValidPrimitiveId(primitive, "primitive");
144        toLoad.add(primitive.getPrimitiveId());
145        return this;
146    }
147
148    /**
149     * Adds a collection of objects to loaded, specified by a collection of OSM primitives.
150     *
151     * @param primitives the OSM primitives. Must not be <code>null</code>.
152     * <code>primitive.getId() &gt; 0</code> required.
153     * @return this task
154     * @throws IllegalArgumentException if primitives is <code>null</code>
155     * @throws IllegalArgumentException if one of the ids in the collection &lt;= 0
156     */
157    public HistoryLoadTask add(Collection<? extends OsmPrimitive> primitives) {
158        CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
159        for (OsmPrimitive primitive: primitives) {
160            if (primitive == null) {
161                continue;
162            }
163            add(primitive);
164        }
165        return this;
166    }
167
168    @Override
169    protected void cancel() {
170        if (reader != null) {
171            reader.cancel();
172        }
173        canceled = true;
174    }
175
176    @Override
177    protected void finish() {
178        if (isCanceled())
179            return;
180        if (lastException != null) {
181            ExceptionDialogUtil.explainException(lastException);
182            return;
183        }
184        HistoryDataSet.getInstance().mergeInto(loadedData);
185    }
186
187    @Override
188    protected void realRun() throws SAXException, IOException, OsmTransferException {
189        loadedData = new HistoryDataSet();
190        try {
191            progressMonitor.setTicksCount(toLoad.size());
192            for (PrimitiveId pid: toLoad) {
193                if (canceled) {
194                    break;
195                }
196                String msg = "";
197                switch(pid.getType()) {
198                case NODE: msg = marktr("Loading history for node {0}"); break;
199                case WAY: msg = marktr("Loading history for way {0}"); break;
200                case RELATION: msg = marktr("Loading history for relation {0}"); break;
201                }
202                progressMonitor.indeterminateSubTask(tr(msg,
203                        Long.toString(pid.getUniqueId())));
204                reader = null;
205                HistoryDataSet ds = null;
206                try {
207                    reader = new OsmServerHistoryReader(pid.getType(), pid.getUniqueId());
208                    ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false));
209                    // load corresponding changesets (mostly for changeset comment)
210                    for (final Changeset i : new OsmServerChangesetReader().queryChangesets(
211                            new ChangesetQuery().forChangesetIds(ds.getChangesetIds()), progressMonitor.createSubTaskMonitor(1, false))) {
212                        ds.putChangeset(i);
213                    }
214                } catch (OsmTransferException e) {
215                    if (canceled)
216                        return;
217                    throw e;
218                }
219                loadedData.mergeInto(ds);
220            }
221        } catch (OsmTransferException e) {
222            lastException = e;
223            return;
224        }
225    }
226
227    public boolean isCanceled() {
228        return canceled;
229    }
230
231    public Exception getLastException() {
232        return lastException;
233    }
234}