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(new Runnable() {
099                            @Override
100                            public void run() {
101                                finish();
102                            }
103                        });
104                    }
105                }
106            } finally {
107                progressMonitor.finishTask();
108                progressMonitor.removeCancelListener(this);
109                progressMonitor.setProgressTaskId(oldTaskId);
110                if (progressMonitor instanceof PleaseWaitProgressMonitor) {
111                    ((PleaseWaitProgressMonitor) progressMonitor).close();
112                }
113                if (EventQueue.isDispatchThread()) {
114                    afterFinish();
115                } else {
116                    EventQueue.invokeAndWait(new Runnable() {
117                        @Override
118                        public void run() {
119                            afterFinish();
120                        }
121                    });
122                }
123            }
124        } catch (final RuntimeException |
125                OsmTransferException | IOException | SAXException | InvocationTargetException | InterruptedException e) {
126            if (!ignoreException) {
127                // Exception has to thrown in EDT to be shown to user
128                SwingUtilities.invokeLater(new Runnable() {
129                    @Override
130                    public void run() {
131                        if (e instanceof RuntimeException) {
132                            BugReportExceptionHandler.handleException(e);
133                        } else {
134                            ExceptionDialogUtil.explainException(e);
135                        }
136                    }
137                });
138            }
139        }
140    }
141
142    /**
143     * Can be overriden if something needs to run after progress monitor is closed.
144     */
145    protected void afterFinish() {
146
147    }
148
149    @Override
150    public final void run() {
151        if (EventQueue.isDispatchThread()) {
152            new Thread(new Runnable() {
153                @Override
154                public void run() {
155                    doRealRun();
156                }
157            }, getClass().getName()).start();
158        } else {
159            doRealRun();
160        }
161    }
162
163    @Override
164    public void operationCanceled() {
165        cancel();
166    }
167
168    /**
169     * User pressed cancel button.
170     */
171    protected abstract void cancel();
172
173    /**
174     * Called in the worker thread to do the actual work. When any of the
175     * exception is thrown, a message box will be displayed and closeDialog
176     * is called. finish() is called in any case.
177     * @throws SAXException if a SAX error occurs
178     * @throws IOException if an I/O error occurs
179     * @throws OsmTransferException if a communication error with the OSM server occurs
180     */
181    protected abstract void realRun() throws SAXException, IOException, OsmTransferException;
182
183    /**
184     * Finish up the data work. Is guaranteed to be called if realRun is called.
185     * Finish is called in the gui thread just after the dialog disappeared.
186     */
187    protected abstract void finish();
188
189    /**
190     * Relies the progress monitor.
191     * @return the progress monitor
192     */
193    public ProgressMonitor getProgressMonitor() {
194        return progressMonitor;
195    }
196
197    /**
198     * Task can run in background if returned value != null. Note that it's tasks responsibility
199     * to ensure proper synchronization, PleaseWaitRunnable doesn't with it.
200     * @return If returned value is != null then task can run in background.
201     * TaskId could be used in future for "Always run in background" checkbox
202     */
203    public ProgressTaskId canRunInBackground() {
204        return null;
205    }
206}