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