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<?> 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<?> 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}