// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.history; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openstreetmap.josm.data.osm.Changeset; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.history.History; import org.openstreetmap.josm.data.osm.history.HistoryDataSet; import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; import org.openstreetmap.josm.gui.ExceptionDialogUtil; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.io.ChangesetQuery; import org.openstreetmap.josm.io.OsmServerChangesetReader; import org.openstreetmap.josm.io.OsmServerHistoryReader; import org.openstreetmap.josm.io.OsmTransferException; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.xml.sax.SAXException; /** * Loads the object history of a collection of objects from the server. * * It provides a fluent API for configuration. * * Sample usage: * * <pre> * HistoryLoadTask task = new HistoryLoadTask() * .add(node) * .add(way) * .add(relation) * .add(aHistoryItem); * * Main.worker.execute(task); * </pre> */ public class HistoryLoadTask extends PleaseWaitRunnable { private boolean canceled; private Exception lastException; private final Set<PrimitiveId> toLoad = new HashSet<>(); private HistoryDataSet loadedData; private OsmServerHistoryReader reader; /** * Constructs a new {@code HistoryLoadTask}. */ public HistoryLoadTask() { super(tr("Load history"), true); } /** * Constructs a new {@code HistoryLoadTask}. * * @param parent the component to be used as reference to find the * parent for {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. * Must not be <code>null</code>. * @throws IllegalArgumentException if parent is <code>null</code> */ public HistoryLoadTask(Component parent) { super(parent, tr("Load history"), true); CheckParameterUtil.ensureParameterNotNull(parent, "parent"); } /** * Adds an object whose history is to be loaded. * * @param pid the primitive id. Must not be null. Id > 0 required. * @return this task */ public HistoryLoadTask add(PrimitiveId pid) { CheckParameterUtil.ensureValidPrimitiveId(pid, "pid"); toLoad.add(pid); return this; } /** * Adds an object to be loaded, the object is specified by a history item. * * @param primitive the history item * @return this task * @throws IllegalArgumentException if primitive is null */ public HistoryLoadTask add(HistoryOsmPrimitive primitive) { CheckParameterUtil.ensureParameterNotNull(primitive, "primitive"); return add(primitive.getPrimitiveId()); } /** * Adds an object to be loaded, the object is specified by an already loaded object history. * * @param history the history. Must not be null. * @return this task * @throws IllegalArgumentException if history is null */ public HistoryLoadTask add(History history) { CheckParameterUtil.ensureParameterNotNull(history, "history"); return add(history.getPrimitiveId()); } /** * Adds an object to be loaded, the object is specified by an OSM primitive. * * @param primitive the OSM primitive. Must not be null. primitive.getId() > 0 required. * @return this task * @throws IllegalArgumentException if the primitive is null * @throws IllegalArgumentException if primitive.getId() <= 0 */ public HistoryLoadTask add(OsmPrimitive primitive) { CheckParameterUtil.ensureValidPrimitiveId(primitive, "primitive"); return add(primitive.getPrimitiveId()); } /** * Adds a collection of objects to loaded, specified by a collection of OSM primitives. * * @param primitives the OSM primitives. Must not be <code>null</code>. * <code>primitive.getId() > 0</code> required. * @return this task * @throws IllegalArgumentException if primitives is <code>null</code> * @throws IllegalArgumentException if one of the ids in the collection <= 0 */ public HistoryLoadTask add(Collection<? extends OsmPrimitive> primitives) { CheckParameterUtil.ensureParameterNotNull(primitives, "primitives"); for (OsmPrimitive primitive: primitives) { if (primitive != null) { add(primitive); } } return this; } @Override protected void cancel() { if (reader != null) { reader.cancel(); } canceled = true; } @Override protected void finish() { if (isCanceled()) return; if (lastException != null) { ExceptionDialogUtil.explainException(lastException); return; } HistoryDataSet.getInstance().mergeInto(loadedData); } @Override protected void realRun() throws SAXException, IOException, OsmTransferException { loadedData = new HistoryDataSet(); try { progressMonitor.setTicksCount(toLoad.size()); for (PrimitiveId pid: toLoad) { if (canceled) { break; } loadHistory(pid); } } catch (OsmTransferException e) { lastException = e; return; } } private void loadHistory(PrimitiveId pid) throws OsmTransferException { String msg = getLoadingMessage(pid); progressMonitor.indeterminateSubTask(tr(msg, Long.toString(pid.getUniqueId()))); reader = null; HistoryDataSet ds; try { reader = new OsmServerHistoryReader(pid.getType(), pid.getUniqueId()); ds = loadHistory(reader, progressMonitor); } catch (OsmTransferException e) { if (canceled) return; throw e; } loadedData.mergeInto(ds); } protected static HistoryDataSet loadHistory(OsmServerHistoryReader reader, ProgressMonitor progressMonitor) throws OsmTransferException { HistoryDataSet ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false)); if (ds != null) { // load corresponding changesets (mostly for changeset comment) OsmServerChangesetReader changesetReader = new OsmServerChangesetReader(); List<Long> changesetIds = new ArrayList<>(ds.getChangesetIds()); // query changesets 100 by 100 (OSM API limit) int n = ChangesetQuery.MAX_CHANGESETS_NUMBER; for (int i = 0; i < changesetIds.size(); i += n) { for (Changeset c : changesetReader.queryChangesets( new ChangesetQuery().forChangesetIds(changesetIds.subList(i, Math.min(i + n, changesetIds.size()))), progressMonitor.createSubTaskMonitor(1, false))) { ds.putChangeset(c); } } } return ds; } protected static String getLoadingMessage(PrimitiveId pid) { switch (pid.getType()) { case NODE: return marktr("Loading history for node {0}"); case WAY: return marktr("Loading history for way {0}"); case RELATION: return marktr("Loading history for relation {0}"); default: return ""; } } /** * Determines if this task has ben canceled. * @return {@code true} if this task has ben canceled */ public boolean isCanceled() { return canceled; } /** * Returns the last exception that occured during loading, if any. * @return the last exception that occured during loading, or {@code null} */ public Exception getLastException() { return lastException; } }