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}