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