001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import java.awt.Component; 005import java.awt.EventQueue; 006import java.io.IOException; 007import java.lang.reflect.InvocationTargetException; 008 009import javax.swing.SwingUtilities; 010 011import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; 012import org.openstreetmap.josm.gui.progress.ProgressMonitor; 013import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener; 014import org.openstreetmap.josm.gui.progress.ProgressTaskId; 015import org.openstreetmap.josm.io.OsmTransferException; 016import org.openstreetmap.josm.tools.CheckParameterUtil; 017import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler; 018import org.xml.sax.SAXException; 019 020/** 021 * Instanced of this thread will display a "Please Wait" message in middle of JOSM 022 * to indicate a progress being executed. 023 * 024 * @author Imi 025 */ 026public abstract class PleaseWaitRunnable implements Runnable, CancelListener { 027 private boolean ignoreException; 028 private final String title; 029 030 protected final ProgressMonitor progressMonitor; 031 032 /** 033 * Create the runnable object with a given message for the user. 034 * @param title message for the user 035 */ 036 public PleaseWaitRunnable(String title) { 037 this(title, false); 038 } 039 040 /** 041 * Create the runnable object with a given message for the user. 042 * 043 * @param title message for the user 044 * @param ignoreException If true, exception will be silently ignored. If false then 045 * exception will be handled by showing a dialog. When this runnable is executed using executor framework 046 * then use false unless you read result of task (because exception will get lost if you don't) 047 */ 048 public PleaseWaitRunnable(String title, boolean ignoreException) { 049 this(title, new PleaseWaitProgressMonitor(title), ignoreException); 050 } 051 052 /** 053 * Create the runnable object with a given message for the user 054 * 055 * @param parent the parent component for the please wait dialog. Must not be null. 056 * @param title message for the user 057 * @param ignoreException If true, exception will be silently ignored. If false then 058 * exception will be handled by showing a dialog. When this runnable is executed using executor framework 059 * then use false unless you read result of task (because exception will get lost if you don't) 060 * @throws IllegalArgumentException if parent is null 061 */ 062 public PleaseWaitRunnable(Component parent, String title, boolean ignoreException) { 063 CheckParameterUtil.ensureParameterNotNull(parent, "parent"); 064 this.title = title; 065 this.progressMonitor = new PleaseWaitProgressMonitor(parent, title); 066 this.ignoreException = ignoreException; 067 } 068 069 /** 070 * Create the runnable object with a given message for the user 071 * 072 * @param title message for the user 073 * @param progressMonitor progress monitor 074 * @param ignoreException If true, exception will be silently ignored. If false then 075 * exception will be handled by showing a dialog. When this runnable is executed using executor framework 076 * then use false unless you read result of task (because exception will get lost if you don't) 077 */ 078 public PleaseWaitRunnable(String title, ProgressMonitor progressMonitor, boolean ignoreException) { 079 this.title = title; 080 this.progressMonitor = progressMonitor == null ? new PleaseWaitProgressMonitor(title) : progressMonitor; 081 this.ignoreException = ignoreException; 082 } 083 084 private void doRealRun() { 085 try { 086 ProgressTaskId oldTaskId = null; 087 try { 088 progressMonitor.addCancelListener(this); 089 progressMonitor.beginTask(title); 090 oldTaskId = progressMonitor.getProgressTaskId(); 091 progressMonitor.setProgressTaskId(canRunInBackground()); 092 try { 093 realRun(); 094 } finally { 095 if (EventQueue.isDispatchThread()) { 096 finish(); 097 } else { 098 EventQueue.invokeAndWait(this::finish); 099 } 100 } 101 } finally { 102 progressMonitor.finishTask(); 103 progressMonitor.removeCancelListener(this); 104 progressMonitor.setProgressTaskId(oldTaskId); 105 if (progressMonitor instanceof PleaseWaitProgressMonitor) { 106 ((PleaseWaitProgressMonitor) progressMonitor).close(); 107 } 108 if (EventQueue.isDispatchThread()) { 109 afterFinish(); 110 } else { 111 EventQueue.invokeAndWait(this::afterFinish); 112 } 113 } 114 } catch (final RuntimeException | 115 OsmTransferException | IOException | SAXException | InvocationTargetException | InterruptedException e) { 116 if (!ignoreException) { 117 // Exception has to thrown in EDT to be shown to user 118 SwingUtilities.invokeLater(() -> { 119 if (e instanceof RuntimeException) { 120 BugReportExceptionHandler.handleException(e); 121 } else { 122 ExceptionDialogUtil.explainException(e); 123 } 124 }); 125 } 126 } 127 } 128 129 /** 130 * Can be overriden if something needs to run after progress monitor is closed. 131 */ 132 protected void afterFinish() { 133 134 } 135 136 @Override 137 public final void run() { 138 if (EventQueue.isDispatchThread()) { 139 new Thread((Runnable) this::doRealRun, getClass().getName()).start(); 140 } else { 141 doRealRun(); 142 } 143 } 144 145 @Override 146 public void operationCanceled() { 147 cancel(); 148 } 149 150 /** 151 * User pressed cancel button. 152 */ 153 protected abstract void cancel(); 154 155 /** 156 * Called in the worker thread to do the actual work. When any of the 157 * exception is thrown, a message box will be displayed and closeDialog 158 * is called. finish() is called in any case. 159 * @throws SAXException if a SAX error occurs 160 * @throws IOException if an I/O error occurs 161 * @throws OsmTransferException if a communication error with the OSM server occurs 162 */ 163 protected abstract void realRun() throws SAXException, IOException, OsmTransferException; 164 165 /** 166 * Finish up the data work. Is guaranteed to be called if realRun is called. 167 * Finish is called in the gui thread just after the dialog disappeared. 168 */ 169 protected abstract void finish(); 170 171 /** 172 * Relies the progress monitor. 173 * @return the progress monitor 174 */ 175 public ProgressMonitor getProgressMonitor() { 176 return progressMonitor; 177 } 178 179 /** 180 * Task can run in background if returned value != null. Note that it's tasks responsibility 181 * to ensure proper synchronization, PleaseWaitRunnable doesn't with it. 182 * @return If returned value is != null then task can run in background. 183 * TaskId could be used in future for "Always run in background" checkbox 184 */ 185 public ProgressTaskId canRunInBackground() { 186 return null; 187 } 188}