001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.downloadtasks;
003
004import java.awt.Component;
005import java.lang.reflect.InvocationTargetException;
006import java.net.URL;
007import java.util.HashSet;
008import java.util.Set;
009import java.util.concurrent.Future;
010
011import javax.swing.SwingUtilities;
012
013import org.openstreetmap.josm.data.Bounds;
014import org.openstreetmap.josm.data.osm.Changeset;
015import org.openstreetmap.josm.data.osm.ChangesetCache;
016import org.openstreetmap.josm.gui.MainApplication;
017import org.openstreetmap.josm.gui.PleaseWaitRunnable;
018import org.openstreetmap.josm.gui.progress.ProgressMonitor;
019import org.openstreetmap.josm.io.OsmServerChangesetReader;
020import org.openstreetmap.josm.tools.ExceptionUtil;
021import org.openstreetmap.josm.tools.Logging;
022import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
023
024/**
025 * Common abstract implementation of other changeset download tasks.
026 * @since 10124
027 */
028public abstract class AbstractChangesetDownloadTask extends AbstractDownloadTask<Set<Changeset>> {
029
030    abstract class RunnableDownloadTask extends PleaseWaitRunnable {
031        /** the reader object used to read changesets from the API */
032        protected final OsmServerChangesetReader reader = new OsmServerChangesetReader();
033        /** the set of downloaded changesets */
034        protected final Set<Changeset> downloadedChangesets = new HashSet<>();
035        /** keeps the last exception thrown in the task, if any */
036        protected Exception lastException;
037
038        RunnableDownloadTask(Component parent, String title) {
039            super(parent, title, false /* don't ignore exceptions */);
040        }
041
042        @Override
043        protected void cancel() {
044            setCanceled(true);
045            synchronized (this) {
046                if (reader != null) {
047                    reader.cancel();
048                }
049            }
050        }
051
052        protected final void rememberLastException(Exception e) {
053            lastException = e;
054            setFailed(true);
055        }
056
057        protected final void updateChangesets() {
058            // update the global changeset cache with the downloaded changesets.
059            // this will trigger change events which views are listening to. They
060            // will update their views accordingly.
061            //
062            // Run on the EDT because UI updates are triggered.
063            //
064            Runnable r = () -> ChangesetCache.getInstance().update(downloadedChangesets);
065            if (SwingUtilities.isEventDispatchThread()) {
066                r.run();
067            } else {
068                try {
069                    SwingUtilities.invokeAndWait(r);
070                } catch (InterruptedException e) {
071                    Logging.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
072                    Thread.currentThread().interrupt();
073                } catch (InvocationTargetException e) {
074                    Throwable t = e.getTargetException();
075                    if (t instanceof RuntimeException) {
076                        BugReportExceptionHandler.handleException(t);
077                    } else if (t instanceof Exception) {
078                        ExceptionUtil.explainException(e);
079                    } else {
080                        BugReportExceptionHandler.handleException(t);
081                    }
082                }
083            }
084        }
085    }
086
087    private RunnableDownloadTask downloadTaskRunnable;
088
089    protected final void setDownloadTask(RunnableDownloadTask downloadTask) {
090        this.downloadTaskRunnable = downloadTask;
091    }
092
093    @Override
094    public final Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
095        return download();
096    }
097
098    /**
099     * Asynchronously launches the changeset download task. This is equivalent to {@code download(false, null, null)}.
100     *
101     * You can wait for the asynchronous download task to finish by synchronizing on the returned
102     * {@link Future}, but make sure not to freeze up JOSM. Example:
103     * <pre>
104     *    Future&lt;?&gt; future = task.download();
105     *    // DON'T run this on the Swing EDT or JOSM will freeze
106     *    future.get(); // waits for the dowload task to complete
107     * </pre>
108     *
109     * The following example uses a pattern which is better suited if a task is launched from the Swing EDT:
110     * <pre>
111     *    final Future&lt;?&gt; future = task.download();
112     *    Runnable runAfterTask = new Runnable() {
113     *       public void run() {
114     *           // this is not strictly necessary because of the type of executor service
115     *           // Main.worker is initialized with, but it doesn't harm either
116     *           //
117     *           future.get(); // wait for the download task to complete
118     *           doSomethingAfterTheTaskCompleted();
119     *       }
120     *    }
121     *    MainApplication.worker.submit(runAfterTask);
122     * </pre>
123     *
124     * @return the future representing the asynchronous task
125     */
126    public final Future<?> download() {
127        return downloadTaskRunnable != null ? MainApplication.worker.submit(downloadTaskRunnable) : null;
128    }
129
130    @Override
131    public final Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
132        return downloadTaskRunnable != null ? MainApplication.worker.submit(downloadTaskRunnable) : null;
133    }
134
135    @Override
136    public final void cancel() {
137        if (downloadTaskRunnable != null) {
138            downloadTaskRunnable.cancel();
139        }
140    }
141
142    @Override
143    public String getConfirmationMessage(URL url) {
144        return null;
145    }
146}