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