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}