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 = new Runnable() {
064                @Override public void run() {
065                    ChangesetCache.getInstance().update(downloadedChangesets);
066                }
067            };
068            if (SwingUtilities.isEventDispatchThread()) {
069                r.run();
070            } else {
071                try {
072                    SwingUtilities.invokeAndWait(r);
073                } catch (InterruptedException e) {
074                    Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
075                } catch (InvocationTargetException e) {
076                    Throwable t = e.getTargetException();
077                    if (t instanceof RuntimeException) {
078                        BugReportExceptionHandler.handleException(t);
079                    } else if (t instanceof Exception) {
080                        ExceptionUtil.explainException(e);
081                    } else {
082                        BugReportExceptionHandler.handleException(t);
083                    }
084                }
085            }
086        }
087    }
088
089    private RunnableDownloadTask downloadTaskRunnable;
090
091    protected final void setDownloadTask(RunnableDownloadTask downloadTask) {
092        this.downloadTaskRunnable = downloadTask;
093    }
094
095    @Override
096    public final Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
097        return download();
098    }
099
100    /**
101     * Asynchronously launches the changeset download task. This is equivalent to {@code download(false, null, null)}.
102     *
103     * You can wait for the asynchronous download task to finish by synchronizing on the returned
104     * {@link Future}, but make sure not to freeze up JOSM. Example:
105     * <pre>
106     *    Future&lt;?&gt; future = task.download();
107     *    // DON'T run this on the Swing EDT or JOSM will freeze
108     *    future.get(); // waits for the dowload task to complete
109     * </pre>
110     *
111     * The following example uses a pattern which is better suited if a task is launched from the Swing EDT:
112     * <pre>
113     *    final Future&lt;?&gt; future = task.download();
114     *    Runnable runAfterTask = new Runnable() {
115     *       public void run() {
116     *           // this is not strictly necessary because of the type of executor service
117     *           // Main.worker is initialized with, but it doesn't harm either
118     *           //
119     *           future.get(); // wait for the download task to complete
120     *           doSomethingAfterTheTaskCompleted();
121     *       }
122     *    }
123     *    Main.worker.submit(runAfterTask);
124     * </pre>
125     *
126     * @return the future representing the asynchronous task
127     */
128    public final Future<?> download() {
129        return downloadTaskRunnable != null ? Main.worker.submit(downloadTaskRunnable) : null;
130    }
131
132    @Override
133    public final Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
134        return downloadTaskRunnable != null ? Main.worker.submit(downloadTaskRunnable) : null;
135    }
136
137    @Override
138    public final void cancel() {
139        if (downloadTaskRunnable != null) {
140            downloadTaskRunnable.cancel();
141        }
142    }
143
144    @Override
145    public String getConfirmationMessage(URL url) {
146        return null;
147    }
148}