Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractChangesetDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractChangesetDownloadTask.java	(revision 10124)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractChangesetDownloadTask.java	(revision 10124)
@@ -0,0 +1,108 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.downloadtasks;
+
+import java.awt.Component;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+
+/**
+ * Common abstract implementation of other changeset download tasks.
+ * @since 10124
+ */
+public abstract class AbstractChangesetDownloadTask extends AbstractDownloadTask<Set<Changeset>> {
+
+    abstract class RunnableDownloadTask extends PleaseWaitRunnable {
+        /** the reader object used to read changesets from the API */
+        protected final OsmServerChangesetReader reader = new OsmServerChangesetReader();
+        /** the set of downloaded changesets */
+        protected final Set<Changeset> downloadedChangesets = new HashSet<>();
+        /** keeps the last exception thrown in the task, if any */
+        protected Exception lastException;
+
+        RunnableDownloadTask(Component parent, String title) {
+            super(parent, title, false /* don't ignore exceptions */);
+        }
+
+        @Override
+        protected void cancel() {
+            setCanceled(true);
+            synchronized (this) {
+                if (reader != null) {
+                    reader.cancel();
+                }
+            }
+        }
+
+        protected final void rememberLastException(Exception e) {
+            lastException = e;
+            setFailed(true);
+        }
+    }
+
+    private RunnableDownloadTask downloadTaskRunnable;
+
+    protected final void setDownloadTask(RunnableDownloadTask downloadTask) {
+        this.downloadTaskRunnable = downloadTask;
+    }
+
+    @Override
+    public final Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
+        return download();
+    }
+
+    /**
+     * Asynchronously launches the changeset download task. This is equivalent to {@code download(false, null, null)}.
+     *
+     * You can wait for the asynchronous download task to finish by synchronizing on the returned
+     * {@link Future}, but make sure not to freeze up JOSM. Example:
+     * <pre>
+     *    Future&lt;?&gt; future = task.download();
+     *    // DON'T run this on the Swing EDT or JOSM will freeze
+     *    future.get(); // waits for the dowload task to complete
+     * </pre>
+     *
+     * The following example uses a pattern which is better suited if a task is launched from the Swing EDT:
+     * <pre>
+     *    final Future&lt;?&gt; future = task.download();
+     *    Runnable runAfterTask = new Runnable() {
+     *       public void run() {
+     *           // this is not strictly necessary because of the type of executor service
+     *           // Main.worker is initialized with, but it doesn't harm either
+     *           //
+     *           future.get(); // wait for the download task to complete
+     *           doSomethingAfterTheTaskCompleted();
+     *       }
+     *    }
+     *    Main.worker.submit(runAfterTask);
+     * </pre>
+     *
+     * @return the future representing the asynchronous task
+     */
+    public final Future<?> download() {
+        return Main.worker.submit(downloadTaskRunnable);
+    }
+
+    @Override
+    public final Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
+        return Main.worker.submit(downloadTaskRunnable);
+    }
+
+    @Override
+    public final void cancel() {
+        downloadTaskRunnable.cancel();
+    }
+
+    @Override
+    public String getConfirmationMessage(URL url) {
+        return null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java	(revision 10124)
@@ -8,5 +8,5 @@
 
 /**
- * Common abstract implementation of other download tasks
+ * Common abstract implementation of other download tasks.
  * @param <T> The downloaded data type
  * @since 2322
@@ -18,20 +18,39 @@
     protected T downloadedData;
 
+    /**
+     * Constructs a new {@code AbstractDownloadTask}.
+     */
     public AbstractDownloadTask() {
         errorMessages = new ArrayList<>();
     }
 
+    /**
+     * Determines if the download task has been canceled.
+     * @return {@code true} if the download task has been canceled
+     */
     public boolean isCanceled() {
         return canceled;
     }
 
+    /**
+     * Marks this download task as canceled.
+     * @param canceled {@code true} to mark this download task as canceled
+     */
     public void setCanceled(boolean canceled) {
         this.canceled = canceled;
     }
 
+    /**
+     * Determines if the download task has failed.
+     * @return {@code true} if the download task has failed
+     */
     public boolean isFailed() {
         return failed;
     }
 
+    /**
+     * Marks this download task as failed.
+     * @param failed {@code true} to mark this download task as failed
+     */
     public void setFailed(boolean failed) {
         this.failed = failed;
@@ -82,7 +101,13 @@
     }
 
-    // Can be overridden for more complex checking logic
+    /**
+     * Determines if the given URL is accepted by {@link #getPatterns}.
+     * Can be overridden for more complex checking logic.
+     * @param url URL to donwload
+     * @return {@code true} if this URL is accepted
+     */
     public boolean acceptsUrl(String url) {
-        if (url == null) return false;
+        if (url == null)
+            return false;
         for (String p: getPatterns()) {
             if (url.matches(p)) {
@@ -113,5 +138,6 @@
     @Override
     public boolean acceptsUrl(String url, boolean isRemotecontrol) {
-        if (isRemotecontrol && !isSafeForRemotecontrolRequests()) return false;
+        if (isRemotecontrol && !isSafeForRemotecontrolRequests())
+            return false;
         return acceptsUrl(url);
     }
@@ -133,4 +159,3 @@
         return new String[]{};
     }
-
 }
Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetContentDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetContentDownloadTask.java	(revision 10124)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetContentDownloadTask.java	(revision 10124)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.downloadtasks;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.io.OsmTransferCanceledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading the changeset content of a collection of changesets.
+ * @since 2689
+ */
+public class ChangesetContentDownloadTask extends AbstractChangesetDownloadTask {
+
+    private final DownloadTask downloadTask;
+
+    class DownloadTask extends RunnableDownloadTask {
+        /** the list of changeset ids to download */
+        private final List<Integer> toDownload = new ArrayList<>();
+
+        DownloadTask(Component parent, Collection<Integer> ids) {
+            super(parent, tr("Downloading changeset content"));
+            for (Integer id: ids != null ? ids : Collections.<Integer>emptyList()) {
+                if (id == null || id <= 0) {
+                    continue;
+                }
+                toDownload.add(id);
+            }
+        }
+
+        /**
+         * Downloads the changeset with id <code>changesetId</code> (only "header" information, no content)
+         *
+         * @param changesetId the changeset id
+         * @throws OsmTransferException if something went wrong
+         */
+        protected void downloadChangeset(int changesetId) throws OsmTransferException {
+            Changeset cs = reader.readChangeset(changesetId, false, getProgressMonitor().createSubTaskMonitor(0, false));
+            ChangesetCache.getInstance().update(cs);
+        }
+
+        @Override
+        protected void realRun() throws SAXException, IOException, OsmTransferException {
+            try {
+                getProgressMonitor().setTicksCount(toDownload.size());
+                int i = 0;
+                for (int id: toDownload) {
+                    i++;
+                    if (!isAvailableLocally(id)) {
+                        getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
+                        downloadChangeset(id);
+                    }
+                    if (isCanceled())
+                        return;
+                    getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
+                    ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
+                    Changeset cs = ChangesetCache.getInstance().get(id);
+                    cs.setContent(ds);
+                    ChangesetCache.getInstance().update(cs);
+                    downloadedChangesets.add(cs);
+                    getProgressMonitor().worked(1);
+                }
+            } catch (OsmTransferCanceledException e) {
+                // the download was canceled by the user. This exception is caught if the user canceled the authentication dialog.
+                setCanceled(true);
+                return;
+            } catch (OsmTransferException e) {
+                if (isCanceled())
+                    return;
+                rememberLastException(e);
+            }
+        }
+
+        @Override
+        protected void finish() {
+            rememberDownloadedData(downloadedChangesets);
+            if (isCanceled())
+                return;
+            if (lastException != null) {
+                ExceptionDialogUtil.explainException(lastException);
+            }
+        }
+    }
+
+    /**
+     * Creates a download task for a single changeset
+     *
+     * @param changesetId the changeset id. &gt; 0 required.
+     * @throws IllegalArgumentException if changesetId &lt;= 0
+     */
+    public ChangesetContentDownloadTask(int changesetId) {
+        this(Main.parent, changesetId);
+    }
+
+    /**
+     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
+     * the collection are silently discarded.
+     *
+     * @param changesetIds the changeset ids. Empty collection assumed, if null.
+     */
+    public ChangesetContentDownloadTask(Collection<Integer> changesetIds) {
+        this(Main.parent, changesetIds);
+    }
+
+    /**
+     * Creates a download task for a single changeset
+     *
+     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
+     * @param changesetId the changeset id. {@code >0} required.
+     * @throws IllegalArgumentException if {@code changesetId <= 0}
+     * @throws IllegalArgumentException if parent is {@code null}
+     */
+    public ChangesetContentDownloadTask(Component parent, int changesetId) {
+        if (changesetId <= 0)
+            throw new IllegalArgumentException(
+                    MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
+        downloadTask = new DownloadTask(parent, Collections.singleton(changesetId));
+        setDownloadTask(downloadTask);
+    }
+
+    /**
+     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
+     * the collection are sillently discarded.
+     *
+     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
+     * @param changesetIds the changeset ids. Empty collection assumed, if {@code null}.
+     * @throws IllegalArgumentException if parent is {@code null}
+     */
+    public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) {
+        downloadTask = new DownloadTask(parent, changesetIds);
+        setDownloadTask(downloadTask);
+    }
+
+    /**
+     * Replies true if the local {@link ChangesetCache} already includes the changeset with
+     * id <code>changesetId</code>.
+     *
+     * @param changesetId the changeset id
+     * @return true if the local {@link ChangesetCache} already includes the changeset with
+     * id <code>changesetId</code>
+     */
+    protected static boolean isAvailableLocally(int changesetId) {
+        return ChangesetCache.getInstance().get(changesetId) != null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetHeaderDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetHeaderDownloadTask.java	(revision 10124)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetHeaderDownloadTask.java	(revision 10124)
@@ -0,0 +1,185 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.downloadtasks;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading a collection of changests from the OSM server.
+ *
+ * The  task only downloads the changeset properties without the changeset content. It
+ * updates the global {@link ChangesetCache}.
+ * @since 2613
+ */
+public class ChangesetHeaderDownloadTask extends AbstractChangesetDownloadTask {
+
+    private final DownloadTask downloadTask;
+
+    class DownloadTask extends RunnableDownloadTask {
+        /** the list of changeset ids to download */
+        private final Set<Integer> toDownload = new HashSet<>();
+        /** whether to include discussions or not */
+        private final boolean includeDiscussion;
+
+        DownloadTask(Component parent, Collection<Integer> ids, boolean includeDiscussion) {
+            super(parent, tr("Download changesets"));
+            this.includeDiscussion = includeDiscussion;
+            for (int id: ids != null ? ids : Collections.<Integer>emptyList()) {
+                if (id <= 0) {
+                    continue;
+                }
+                toDownload.add(id);
+            }
+        }
+
+        @Override
+        protected void realRun() throws SAXException, IOException, OsmTransferException {
+            try {
+                downloadedChangesets.addAll(reader.readChangesets(toDownload, includeDiscussion,
+                        getProgressMonitor().createSubTaskMonitor(0, false)));
+            } catch (OsmTransferException e) {
+                if (isCanceled())
+                    // ignore exception if canceled
+                    return;
+                // remember other exceptions
+                rememberLastException(e);
+            }
+        }
+
+        @Override
+        protected void finish() {
+            rememberDownloadedData(downloadedChangesets);
+            if (isCanceled())
+                return;
+            if (lastException != null) {
+                ExceptionDialogUtil.explainException(lastException);
+            }
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    ChangesetCache.getInstance().update(downloadedChangesets);
+                }
+            };
+
+            if (SwingUtilities.isEventDispatchThread()) {
+                r.run();
+            } else {
+                try {
+                    SwingUtilities.invokeAndWait(r);
+                } catch (InterruptedException e) {
+                    Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
+                } catch (InvocationTargetException e) {
+                    Throwable t = e.getTargetException();
+                    if (t instanceof RuntimeException) {
+                        BugReportExceptionHandler.handleException(t);
+                    } else if (t instanceof Exception) {
+                        ExceptionUtil.explainException(e);
+                    } else {
+                        BugReportExceptionHandler.handleException(t);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
+     * whose parent is {@link Main#parent}.
+     *
+     * Null ids or or ids &lt;= 0 in the id collection are ignored.
+     *
+     * @param ids the collection of ids. Empty collection assumed if null.
+     */
+    public ChangesetHeaderDownloadTask(Collection<Integer> ids) {
+        this(Main.parent, ids, false);
+    }
+
+    /**
+     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
+     * whose parent is the parent window of <code>dialogParent</code>.
+     *
+     * Null ids or or ids &lt;= 0 in the id collection are ignored.
+     *
+     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
+     * @param ids the collection of ids. Empty collection assumed if null.
+     * @throws IllegalArgumentException if dialogParent is null
+     */
+    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) {
+        this(dialogParent, ids, false);
+    }
+
+    /**
+     * Creates the download task for a collection of changeset ids, with possibility to download changeset discussion.
+     * Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog} whose parent is the parent window of <code>dialogParent</code>.
+     *
+     * Null ids or or ids &lt;= 0 in the id collection are ignored.
+     *
+     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
+     * @param ids the collection of ids. Empty collection assumed if null.
+     * @param includeDiscussion determines if discussion comments must be downloaded or not
+     * @throws IllegalArgumentException if dialogParent is null
+     * @since 7704
+     */
+    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids, boolean includeDiscussion) {
+        downloadTask = new DownloadTask(dialogParent, ids, includeDiscussion);
+        setDownloadTask(downloadTask);
+    }
+
+    /**
+     * Builds a download task from for a collection of changesets.
+     *
+     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
+     *
+     * @param changesets the collection of changesets. Assumes an empty collection if null.
+     * @return the download task
+     */
+    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Collection<Changeset> changesets) {
+        return buildTaskForChangesets(Main.parent, changesets);
+    }
+
+    /**
+     * Builds a download task from for a collection of changesets.
+     *
+     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
+     *
+     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
+     * Must not be null.
+     * @param changesets the collection of changesets. Assumes an empty collection if null.
+     * @return the download task
+     * @throws IllegalArgumentException if parent is null
+     */
+    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Component parent, Collection<Changeset> changesets) {
+        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+
+        Set<Integer> ids = new HashSet<>();
+        for (Changeset cs: changesets != null ? changesets : Collections.<Changeset>emptyList()) {
+            if (cs == null || cs.isNew()) {
+                continue;
+            }
+            ids.add(cs.getId());
+        }
+        if (parent == null)
+            return new ChangesetHeaderDownloadTask(ids);
+        else
+            return new ChangesetHeaderDownloadTask(parent, ids);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetQueryTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetQueryTask.java	(revision 10124)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/ChangesetQueryTask.java	(revision 10124)
@@ -0,0 +1,172 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.downloadtasks;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.io.OsmServerUserInfoReader;
+import org.openstreetmap.josm.io.OsmTransferCanceledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * Asynchronous task to send a changeset query to the OSM API.
+ * @since 2689
+ */
+public class ChangesetQueryTask extends AbstractChangesetDownloadTask {
+
+    private final DownloadTask downloadTask;
+
+    class DownloadTask extends RunnableDownloadTask {
+        /** the changeset query */
+        private ChangesetQuery query;
+        /** the reader object used to read information about the current user from the API */
+        private final OsmServerUserInfoReader userInfoReader = new OsmServerUserInfoReader();
+
+        DownloadTask(Component parent, ChangesetQuery query) {
+            super(parent, tr("Querying and downloading changesets"));
+            this.query = query;
+        }
+
+        /**
+         * Tries to fully identify the current JOSM user
+         *
+         * @throws OsmTransferException if something went wrong
+         */
+        protected void fullyIdentifyCurrentUser() throws OsmTransferException {
+            getProgressMonitor().indeterminateSubTask(tr("Determine user id for current user..."));
+
+            UserInfo info = userInfoReader.fetchUserInfo(getProgressMonitor().createSubTaskMonitor(1, false));
+            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+            im.setFullyIdentified(im.getUserName(), info);
+        }
+
+        @Override
+        protected void realRun() throws SAXException, IOException, OsmTransferException {
+            try {
+                JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+                if (query.isRestrictedToPartiallyIdentifiedUser() && im.isCurrentUser(query.getUserName())) {
+                    // if we query changesets for the current user, make sure we query against
+                    // its user id, not its user name. If necessary, determine the user id first.
+                    //
+                    if (im.isPartiallyIdentified()) {
+                        fullyIdentifyCurrentUser();
+                    }
+                    query = query.forUser(JosmUserIdentityManager.getInstance().getUserId());
+                }
+                if (isCanceled())
+                    return;
+                getProgressMonitor().indeterminateSubTask(tr("Query and download changesets ..."));
+                downloadedChangesets.addAll(reader.queryChangesets(query, getProgressMonitor().createSubTaskMonitor(0, false)));
+            } catch (OsmTransferCanceledException e) {
+                // thrown if user cancel the authentication dialog
+                setCanceled(true);
+            }  catch (OsmTransferException e) {
+                if (isCanceled())
+                    return;
+                rememberLastException(e);
+            }
+        }
+
+        @Override
+        protected void finish() {
+            rememberDownloadedData(downloadedChangesets);
+            if (isCanceled())
+                return;
+            if (lastException != null) {
+                GuiHelper.runInEDTAndWait(new Runnable() {
+                    private final Component parent = progressMonitor != null ? progressMonitor.getWindowParent() : null;
+                    @Override
+                    public void run() {
+                        JOptionPane.showMessageDialog(
+                                parent != null ? parent : Main.parent,
+                                ExceptionUtil.explainException(lastException),
+                                tr("Errors during download"),
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                });
+                return;
+            }
+
+            // update the global changeset cache with the downloaded changesets.
+            // this will trigger change events which views are listening to. They
+            // will update their views accordingly.
+            //
+            // Run on the EDT because UI updates are triggered.
+            //
+            Runnable r = new Runnable() {
+                @Override public void run() {
+                    ChangesetCache.getInstance().update(downloadedChangesets);
+                }
+            };
+            if (SwingUtilities.isEventDispatchThread()) {
+                r.run();
+            } else {
+                try {
+                    SwingUtilities.invokeAndWait(r);
+                } catch (InterruptedException e) {
+                    Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
+                } catch (InvocationTargetException e) {
+                    Throwable t = e.getTargetException();
+                    if (t instanceof RuntimeException) {
+                        BugReportExceptionHandler.handleException(t);
+                    } else if (t instanceof Exception) {
+                        ExceptionUtil.explainException(e);
+                    } else {
+                        BugReportExceptionHandler.handleException(t);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void cancel() {
+            super.cancel();
+            synchronized (this) {
+                if (userInfoReader != null) {
+                    userInfoReader.cancel();
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates the task.
+     *
+     * @param query the query to submit to the OSM server. Must not be null.
+     * @throws IllegalArgumentException if query is null.
+     */
+    public ChangesetQueryTask(ChangesetQuery query) {
+        this(Main.parent, query);
+    }
+
+    /**
+     * Creates the task.
+     *
+     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
+     * Must not be null.
+     * @param query the query to submit to the OSM server. Must not be null.
+     * @throws IllegalArgumentException if query is null.
+     * @throws IllegalArgumentException if parent is null
+     */
+    public ChangesetQueryTask(Component parent, ChangesetQuery query) {
+        CheckParameterUtil.ensureParameterNotNull(query, "query");
+        downloadTask = new DownloadTask(parent, query);
+        setDownloadTask(downloadTask);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java	(revision 10124)
@@ -35,4 +35,6 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AbstractInfoAction;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.ChangesetCache;
@@ -44,5 +46,4 @@
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
-import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetHeaderDownloadTask;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
 import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
@@ -330,5 +331,5 @@
 
         @Override
-        public void itemStateChanged(ItemEvent arg0) {
+        public void itemStateChanged(ItemEvent e) {
             updateEnabledState();
 
@@ -354,5 +355,5 @@
 
         @Override
-        public void actionPerformed(ActionEvent arg0) {
+        public void actionPerformed(ActionEvent e) {
             ChangesetListModel model = getCurrentChangesetListModel();
             Set<Integer> sel = model.getSelectedChangesetIds();
@@ -360,5 +361,5 @@
                 return;
             ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
-            Main.worker.submit(task);
+            Main.worker.submit(new PostDownloadHandler(task, task.download()));
         }
 
@@ -368,7 +369,6 @@
 
         @Override
-        public void itemStateChanged(ItemEvent arg0) {
-            updateEnabledState();
-
+        public void itemStateChanged(ItemEvent e) {
+            updateEnabledState();
         }
 
@@ -392,5 +392,5 @@
 
         @Override
-        public void actionPerformed(ActionEvent arg0) {
+        public void actionPerformed(ActionEvent e) {
             List<Changeset> sel = getCurrentChangesetListModel().getSelectedOpenChangesets();
             if (sel.isEmpty())
@@ -404,5 +404,5 @@
 
         @Override
-        public void itemStateChanged(ItemEvent arg0) {
+        public void itemStateChanged(ItemEvent e) {
             updateEnabledState();
         }
@@ -427,5 +427,5 @@
 
         @Override
-        public void actionPerformed(ActionEvent arg0) {
+        public void actionPerformed(ActionEvent e) {
             Set<Changeset> sel = getCurrentChangesetListModel().getSelectedChangesets();
             if (sel.isEmpty())
@@ -435,8 +435,5 @@
             String baseUrl = Main.getBaseBrowseUrl();
             for (Changeset cs: sel) {
-                String url = baseUrl + "/changeset/" + cs.getId();
-                OpenBrowser.displayUrl(
-                        url
-                );
+                OpenBrowser.displayUrl(baseUrl + "/changeset/" + cs.getId());
             }
         }
@@ -447,5 +444,5 @@
 
         @Override
-        public void itemStateChanged(ItemEvent arg0) {
+        public void itemStateChanged(ItemEvent e) {
             updateEnabledState();
         }
@@ -469,5 +466,5 @@
 
         @Override
-        public void actionPerformed(ActionEvent arg0) {
+        public void actionPerformed(ActionEvent e) {
             ChangesetListModel model = getCurrentChangesetListModel();
             Set<Integer> sel = model.getSelectedChangesetIds();
@@ -521,5 +518,5 @@
             } else {
                 task = new ChangesetHeaderDownloadTask(toDownload);
-                future = Main.worker.submit(task);
+                future = Main.worker.submit(new PostDownloadHandler(task, task.download()));
             }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java	(revision 10124)
@@ -40,4 +40,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.downloadtasks.AbstractChangesetDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetQueryTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.ChangesetCache;
@@ -46,5 +51,4 @@
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog;
-import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryTask;
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
@@ -652,7 +656,13 @@
     public void setSelectedChangesets(Collection<Changeset> changesets) {
         model.setSelectedChangesets(changesets);
-        int idx = model.getSelectionModel().getMinSelectionIndex();
-        if (idx < 0) return;
-        tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
+        final int idx = model.getSelectionModel().getMinSelectionIndex();
+        if (idx < 0)
+            return;
+        GuiHelper.runInEDTAndWait(new Runnable() {
+            @Override
+            public void run() {
+                tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
+            }
+        });
         repaint();
     }
@@ -696,11 +706,12 @@
      * @param task The changeset download task to run
      */
-    public void runDownloadTask(final ChangesetDownloadTask task) {
-        Main.worker.submit(task);
+    public void runDownloadTask(final AbstractChangesetDownloadTask task) {
+        Main.worker.submit(new PostDownloadHandler(task, task.download()));
         Main.worker.submit(new Runnable() {
             @Override
             public void run() {
-                if (task.isCanceled() || task.isFailed()) return;
-                setSelectedChangesets(task.getDownloadedChangesets());
+                if (task.isCanceled() || task.isFailed())
+                    return;
+                setSelectedChangesets(task.getDownloadedData());
             }
         });
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java	(revision 10123)
+++ 	(revision )
@@ -1,220 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-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.ChangesetCache;
-import org.openstreetmap.josm.data.osm.ChangesetDataSet;
-import org.openstreetmap.josm.gui.ExceptionDialogUtil;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.io.OsmServerChangesetReader;
-import org.openstreetmap.josm.io.OsmTransferCanceledException;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.xml.sax.SAXException;
-
-/**
- * This is an asynchronous task for downloading the changeset content of a collection of
- * changesets.
- *
- */
-public class ChangesetContentDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask {
-
-    /** the list of changeset ids to download */
-    private final List<Integer> toDownload = new ArrayList<>();
-    /** true if the task was canceled */
-    private boolean canceled;
-    /** keeps the last exception thrown in the task, if any */
-    private Exception lastException;
-    /** the reader object used to read changesets from the API */
-    private OsmServerChangesetReader reader;
-    /** the set of downloaded changesets */
-    private Set<Changeset> downloadedChangesets;
-
-    /**
-     * Initialize the task with a collection of changeset ids to download
-     *
-     * @param ids the collection of ids. May be null.
-     */
-    protected void init(Collection<Integer> ids) {
-        if (ids == null) {
-            ids = Collections.emptyList();
-        }
-        for (Integer id: ids) {
-            if (id == null || id <= 0) {
-                continue;
-            }
-            toDownload.add(id);
-        }
-        downloadedChangesets = new HashSet<>();
-    }
-
-    /**
-     * Creates a download task for a single changeset
-     *
-     * @param changesetId the changeset id. &gt; 0 required.
-     * @throws IllegalArgumentException if changesetId &lt;= 0
-     */
-    public ChangesetContentDownloadTask(int changesetId) {
-        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
-        if (changesetId <= 0)
-            throw new IllegalArgumentException(
-                    MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
-        init(Collections.singleton(changesetId));
-    }
-
-    /**
-     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
-     * the collection are sillently discarded.
-     *
-     * @param changesetIds the changeset ids. Empty collection assumed, if null.
-     */
-    public ChangesetContentDownloadTask(Collection<Integer> changesetIds) {
-        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
-        init(changesetIds);
-    }
-
-    /**
-     * Creates a download task for a single changeset
-     *
-     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
-     * @param changesetId the changeset id. {@code >0} required.
-     * @throws IllegalArgumentException if {@code changesetId <= 0}
-     * @throws IllegalArgumentException if parent is {@code null}
-     */
-    public ChangesetContentDownloadTask(Component parent, int changesetId) {
-        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
-        if (changesetId <= 0)
-            throw new IllegalArgumentException(
-                    MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
-        init(Collections.singleton(changesetId));
-    }
-
-    /**
-     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
-     * the collection are sillently discarded.
-     *
-     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
-     * @param changesetIds the changeset ids. Empty collection assumed, if {@code null}.
-     * @throws IllegalArgumentException if parent is {@code null}
-     */
-    public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) {
-        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
-        init(changesetIds);
-    }
-
-    /**
-     * Replies true if the local {@link ChangesetCache} already includes the changeset with
-     * id <code>changesetId</code>.
-     *
-     * @param changesetId the changeset id
-     * @return true if the local {@link ChangesetCache} already includes the changeset with
-     * id <code>changesetId</code>
-     */
-    protected boolean isAvailableLocally(int changesetId) {
-        return ChangesetCache.getInstance().get(changesetId) != null;
-    }
-
-    /**
-     * Downloads the changeset with id <code>changesetId</code> (only "header"
-     * information, no content)
-     *
-     * @param changesetId the changeset id
-     * @throws OsmTransferException if something went wrong
-     */
-    protected void downloadChangeset(int changesetId) throws OsmTransferException {
-        synchronized (this) {
-            reader = new OsmServerChangesetReader();
-        }
-        Changeset cs = reader.readChangeset(changesetId, false, getProgressMonitor().createSubTaskMonitor(0, false));
-        synchronized (this) {
-            reader = null;
-        }
-        ChangesetCache.getInstance().update(cs);
-    }
-
-    @Override
-    protected void cancel() {
-        canceled = true;
-        synchronized (this) {
-            if (reader != null) {
-                reader.cancel();
-            }
-        }
-    }
-
-    @Override
-    protected void finish() {
-        if (canceled) return;
-        if (lastException != null) {
-            ExceptionDialogUtil.explainException(lastException);
-        }
-    }
-
-    @Override
-    protected void realRun() throws SAXException, IOException, OsmTransferException {
-        try {
-            getProgressMonitor().setTicksCount(toDownload.size());
-            int i = 0;
-            for (int id: toDownload) {
-                i++;
-                if (!isAvailableLocally(id)) {
-                    getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
-                    downloadChangeset(id);
-                }
-                if (canceled) return;
-                synchronized (this) {
-                    reader = new OsmServerChangesetReader();
-                }
-                getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
-                ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
-                synchronized (this) {
-                    reader = null;
-                }
-                Changeset cs = ChangesetCache.getInstance().get(id);
-                cs.setContent(ds);
-                ChangesetCache.getInstance().update(cs);
-                downloadedChangesets.add(cs);
-                getProgressMonitor().worked(1);
-            }
-        } catch (OsmTransferCanceledException e) {
-            // the download was canceled by the user. This exception is caught if the
-            // user canceled the authentication dialog.
-            //
-            canceled = true;
-            return;
-        } catch (OsmTransferException e) {
-            if (canceled)
-                return;
-            lastException = e;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /* interface ChangesetDownloadTask                                                 */
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public Set<Changeset> getDownloadedChangesets() {
-        return downloadedChangesets;
-    }
-
-    @Override
-    public boolean isCanceled() {
-        return canceled;
-    }
-
-    @Override
-    public boolean isFailed() {
-        return lastException != null;
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java	(revision 10124)
@@ -34,4 +34,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java	(revision 10124)
@@ -29,4 +29,6 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.ChangesetCache;
@@ -298,11 +300,11 @@
         @Override
         public void actionPerformed(ActionEvent evt) {
-            if (currentChangeset == null) return;
-            Main.worker.submit(
-                    new ChangesetHeaderDownloadTask(
-                            ChangesetDetailPanel.this,
-                            Collections.singleton(currentChangeset.getId())
-                    )
+            if (currentChangeset == null)
+                return;
+            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(
+                    ChangesetDetailPanel.this,
+                    Collections.singleton(currentChangeset.getId())
             );
+            Main.worker.submit(new PostDownloadHandler(task, task.download()));
         }
 
@@ -313,6 +315,5 @@
 
     /**
-     * Selects the primitives in the content of this changeset in the current
-     * data layer.
+     * Selects the primitives in the content of this changeset in the current data layer.
      *
      */
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionPanel.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionPanel.java	(revision 10124)
@@ -21,4 +21,6 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.io.OnlineResource;
@@ -68,12 +70,12 @@
         @Override
         public void actionPerformed(ActionEvent evt) {
-            if (current == null) return;
-            Main.worker.submit(
-                    new ChangesetHeaderDownloadTask(
-                            ChangesetDiscussionPanel.this,
-                            Collections.singleton(current.getId()),
-                            true /* include discussion */
-                    )
+            if (current == null)
+                return;
+            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(
+                    ChangesetDiscussionPanel.this,
+                    Collections.singleton(current.getId()),
+                    true /* include discussion */
             );
+            Main.worker.submit(new PostDownloadHandler(task, task.download()));
         }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java	(revision 10123)
+++ 	(revision )
@@ -1,14 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset;
-
-import java.util.Set;
-
-import org.openstreetmap.josm.data.osm.Changeset;
-
-public interface ChangesetDownloadTask extends Runnable {
-    Set<Changeset> getDownloadedChangesets();
-
-    boolean isCanceled();
-
-    boolean isFailed();
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java	(revision 10123)
+++ 	(revision )
@@ -1,229 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.Changeset;
-import org.openstreetmap.josm.data.osm.ChangesetCache;
-import org.openstreetmap.josm.gui.ExceptionDialogUtil;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.io.OsmServerChangesetReader;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
-import org.openstreetmap.josm.tools.ExceptionUtil;
-import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
-import org.xml.sax.SAXException;
-
-/**
- * This is an asynchronous task for downloading a collection of changests from the OSM
- * server.
- *
- * The  task only downloads the changeset properties without the changeset content. It
- * updates the global {@link ChangesetCache}.
- *
- */
-public class ChangesetHeaderDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask {
-
-    /**
-     * Builds a download task from for a collection of changesets.
-     *
-     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
-     *
-     * @param changesets the collection of changesets. Assumes an empty collection if null.
-     * @return the download task
-     */
-    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Collection<Changeset> changesets) {
-        return buildTaskForChangesets(Main.parent, changesets);
-    }
-
-    /**
-     * Builds a download task from for a collection of changesets.
-     *
-     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
-     *
-     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
-     * Must not be null.
-     * @param changesets the collection of changesets. Assumes an empty collection if null.
-     * @return the download task
-     * @throws IllegalArgumentException if parent is null
-     */
-    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Component parent, Collection<Changeset> changesets) {
-        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
-        if (changesets == null) {
-            changesets = Collections.emptyList();
-        }
-
-        Set<Integer> ids = new HashSet<>();
-        for (Changeset cs: changesets) {
-            if (cs == null || cs.isNew()) {
-                continue;
-            }
-            ids.add(cs.getId());
-        }
-        if (parent == null)
-            return new ChangesetHeaderDownloadTask(ids);
-        else
-            return new ChangesetHeaderDownloadTask(parent, ids);
-
-    }
-
-    private Set<Integer> idsToDownload;
-    private OsmServerChangesetReader reader;
-    private boolean canceled;
-    private Exception lastException;
-    private Set<Changeset> downloadedChangesets;
-    private final boolean includeDiscussion;
-
-    protected void init(Collection<Integer> ids) {
-        if (ids == null) {
-            ids = Collections.emptyList();
-        }
-        idsToDownload = new HashSet<>();
-        if (ids == null ||  ids.isEmpty())
-            return;
-        for (int id: ids) {
-            if (id <= 0) {
-                continue;
-            }
-            idsToDownload.add(id);
-        }
-    }
-
-    /**
-     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
-     * whose parent is {@link Main#parent}.
-     *
-     * Null ids or or ids &lt;= 0 in the id collection are ignored.
-     *
-     * @param ids the collection of ids. Empty collection assumed if null.
-     */
-    public ChangesetHeaderDownloadTask(Collection<Integer> ids) {
-        // parent for dialog is Main.parent
-        super(tr("Download changesets"), false /* don't ignore exceptions */);
-        init(ids);
-        this.includeDiscussion = false;
-    }
-
-    /**
-     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
-     * whose parent is the parent window of <code>dialogParent</code>.
-     *
-     * Null ids or or ids &lt;= 0 in the id collection are ignored.
-     *
-     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
-     * @param ids the collection of ids. Empty collection assumed if null.
-     * @throws IllegalArgumentException if dialogParent is null
-     */
-    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) {
-        this(dialogParent, ids, false);
-    }
-
-    /**
-     * Creates the download task for a collection of changeset ids, with possibility to download changeset discussion.
-     * Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog} whose parent is the parent window of <code>dialogParent</code>.
-     *
-     * Null ids or or ids &lt;= 0 in the id collection are ignored.
-     *
-     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
-     * @param ids the collection of ids. Empty collection assumed if null.
-     * @param includeDiscussion determines if discussion comments must be downloaded or not
-     * @throws IllegalArgumentException if dialogParent is null
-     * @since 7704
-     */
-    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids, boolean includeDiscussion) {
-        super(dialogParent, tr("Download changesets"), false /* don't ignore exceptions */);
-        init(ids);
-        this.includeDiscussion = includeDiscussion;
-    }
-
-    @Override
-    protected void cancel() {
-        canceled = true;
-        synchronized (this) {
-            if (reader != null) {
-                reader.cancel();
-            }
-        }
-    }
-
-    @Override
-    protected void finish() {
-        if (canceled)
-            return;
-        if (lastException != null) {
-            ExceptionDialogUtil.explainException(lastException);
-        }
-        Runnable r = new Runnable() {
-            @Override
-            public void run() {
-                ChangesetCache.getInstance().update(downloadedChangesets);
-            }
-        };
-
-        if (SwingUtilities.isEventDispatchThread()) {
-            r.run();
-        } else {
-            try {
-                SwingUtilities.invokeAndWait(r);
-            } catch (InterruptedException e) {
-                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
-            } catch (InvocationTargetException e) {
-                Throwable t = e.getTargetException();
-                if (t instanceof RuntimeException) {
-                    BugReportExceptionHandler.handleException(t);
-                } else if (t instanceof Exception) {
-                    ExceptionUtil.explainException(e);
-                } else {
-                    BugReportExceptionHandler.handleException(t);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void realRun() throws SAXException, IOException, OsmTransferException {
-        try {
-            synchronized (this) {
-                reader = new OsmServerChangesetReader();
-            }
-            downloadedChangesets = new HashSet<>();
-            downloadedChangesets.addAll(reader.readChangesets(idsToDownload, includeDiscussion,
-                    getProgressMonitor().createSubTaskMonitor(0, false)));
-        } catch (OsmTransferException e) {
-            if (canceled)
-                // ignore exception if canceled
-                return;
-            // remember other exceptions
-            lastException = e;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /* interface ChangesetDownloadTask                                                 */
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public Set<Changeset> getDownloadedChangesets() {
-        return downloadedChangesets;
-    }
-
-    @Override
-    public boolean isCanceled() {
-        return canceled;
-    }
-
-    @Override
-    public boolean isFailed() {
-        return lastException != null;
-    }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetContentAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetContentAction.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetContentAction.java	(revision 10124)
@@ -9,4 +9,5 @@
 import javax.swing.AbstractAction;
 
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java	(revision 10123)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java	(revision 10124)
@@ -17,4 +17,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.downloadtasks.ChangesetContentDownloadTask;
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.widgets.ChangesetIdTextField;
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java	(revision 10123)
+++ 	(revision )
@@ -1,212 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset.query;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.Changeset;
-import org.openstreetmap.josm.data.osm.ChangesetCache;
-import org.openstreetmap.josm.data.osm.UserInfo;
-import org.openstreetmap.josm.gui.JosmUserIdentityManager;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetDownloadTask;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.ChangesetQuery;
-import org.openstreetmap.josm.io.OsmServerChangesetReader;
-import org.openstreetmap.josm.io.OsmServerUserInfoReader;
-import org.openstreetmap.josm.io.OsmTransferCanceledException;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
-import org.openstreetmap.josm.tools.ExceptionUtil;
-import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
-import org.xml.sax.SAXException;
-
-/**
- * Asynchronous task to send a changeset query to the OSM API.
- * @since 2689
- */
-public class ChangesetQueryTask extends PleaseWaitRunnable implements ChangesetDownloadTask {
-
-    /** the changeset query */
-    private ChangesetQuery query;
-    /** true if the task was canceled */
-    private boolean canceled;
-    /** the set of downloaded changesets */
-    private Set<Changeset> downloadedChangesets;
-    /** the last exception remembered, if any */
-    private Exception lastException;
-    /** the reader object used to read information about the current user from the API */
-    private OsmServerUserInfoReader userInfoReader;
-    /** the reader object used to submit the changeset query to the API */
-    private OsmServerChangesetReader changesetReader;
-
-    /**
-     * Creates the task.
-     *
-     * @param query the query to submit to the OSM server. Must not be null.
-     * @throws IllegalArgumentException if query is null.
-     */
-    public ChangesetQueryTask(ChangesetQuery query) {
-        super(tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
-        CheckParameterUtil.ensureParameterNotNull(query, "query");
-        this.query = query;
-    }
-
-    /**
-     * Creates the task.
-     *
-     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
-     * Must not be null.
-     * @param query the query to submit to the OSM server. Must not be null.
-     * @throws IllegalArgumentException if query is null.
-     * @throws IllegalArgumentException if parent is null
-     */
-    public ChangesetQueryTask(Component parent, ChangesetQuery query) {
-        super(parent, tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
-        CheckParameterUtil.ensureParameterNotNull(query, "query");
-        this.query = query;
-    }
-
-    @Override
-    protected void cancel() {
-        canceled = true;
-        synchronized (this) {
-            if (userInfoReader != null) {
-                userInfoReader.cancel();
-            }
-        }
-        synchronized (this) {
-            if (changesetReader != null) {
-                changesetReader.cancel();
-            }
-        }
-    }
-
-    @Override
-    protected void finish() {
-        if (canceled) return;
-        if (lastException != null) {
-            GuiHelper.runInEDTAndWait(new Runnable() {
-                private final Component parent = progressMonitor != null ? progressMonitor.getWindowParent() : null;
-                @Override
-                public void run() {
-                    JOptionPane.showMessageDialog(
-                            parent != null ? parent : Main.parent,
-                            ExceptionUtil.explainException(lastException),
-                            tr("Errors during download"),
-                            JOptionPane.ERROR_MESSAGE);
-                }
-            });
-            return;
-        }
-
-        // update the global changeset cache with the downloaded changesets.
-        // this will trigger change events which views are listening to. They
-        // will update their views accordingly.
-        //
-        // Run on the EDT because UI updates are triggered.
-        //
-        Runnable r = new Runnable() {
-            @Override public void run() {
-                ChangesetCache.getInstance().update(downloadedChangesets);
-            }
-        };
-        if (SwingUtilities.isEventDispatchThread()) {
-            r.run();
-        } else {
-            try {
-                SwingUtilities.invokeAndWait(r);
-            } catch (InterruptedException e) {
-                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
-            } catch (InvocationTargetException e) {
-                Throwable t = e.getTargetException();
-                if (t instanceof RuntimeException) {
-                    BugReportExceptionHandler.handleException(t);
-                } else if (t instanceof Exception) {
-                    ExceptionUtil.explainException(e);
-                } else {
-                    BugReportExceptionHandler.handleException(t);
-                }
-            }
-        }
-    }
-
-    /**
-     * Tries to fully identify the current JOSM user
-     *
-     * @throws OsmTransferException if something went wrong
-     */
-    protected void fullyIdentifyCurrentUser() throws OsmTransferException {
-        getProgressMonitor().indeterminateSubTask(tr("Determine user id for current user..."));
-
-        synchronized (this) {
-            userInfoReader = new OsmServerUserInfoReader();
-        }
-        UserInfo info = userInfoReader.fetchUserInfo(getProgressMonitor().createSubTaskMonitor(1, false));
-        synchronized (this) {
-            userInfoReader = null;
-        }
-        JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
-        im.setFullyIdentified(im.getUserName(), info);
-    }
-
-    @Override
-    protected void realRun() throws SAXException, IOException, OsmTransferException {
-        try {
-            JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
-            if (query.isRestrictedToPartiallyIdentifiedUser() && im.isCurrentUser(query.getUserName())) {
-                // if we query changesets for the current user, make sure we query against
-                // its user id, not its user name. If necessary, determine the user id first.
-                //
-                if (im.isPartiallyIdentified()) {
-                    fullyIdentifyCurrentUser();
-                }
-                query = query.forUser(JosmUserIdentityManager.getInstance().getUserId());
-            }
-            if (canceled) return;
-            getProgressMonitor().indeterminateSubTask(tr("Query and download changesets ..."));
-            synchronized (this) {
-                changesetReader = new OsmServerChangesetReader();
-            }
-            downloadedChangesets = new HashSet<>();
-            downloadedChangesets.addAll(changesetReader.queryChangesets(query, getProgressMonitor().createSubTaskMonitor(0, false)));
-            synchronized (this) {
-                changesetReader = null;
-            }
-        } catch (OsmTransferCanceledException e) {
-            // thrown if user cancel the authentication dialog
-            canceled = true;
-        }  catch (OsmTransferException e) {
-            if (canceled)
-                return;
-            this.lastException = e;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /* interface ChangesetDownloadTask                                                 */
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public Set<Changeset> getDownloadedChangesets() {
-        return downloadedChangesets;
-    }
-
-    @Override
-    public boolean isCanceled() {
-        return canceled;
-    }
-
-    @Override
-    public boolean isFailed() {
-        return lastException != null;
-    }
-}
