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}