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;
007
008import javax.swing.SwingUtilities;
009
010import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
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.io.OsmTransferException;
015import org.openstreetmap.josm.tools.BugReportExceptionHandler;
016import org.openstreetmap.josm.tools.CheckParameterUtil;
017import org.xml.sax.SAXException;
018
019/**
020 * Instanced of this thread will display a "Please Wait" message in middle of JOSM
021 * to indicate a progress being executed.
022 *
023 * @author Imi
024 */
025public abstract class PleaseWaitRunnable implements Runnable, CancelListener {
026    private boolean canceled = false;
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 thrown if parent is null
061     */
062    public PleaseWaitRunnable(Component parent, String title, boolean ignoreException) throws IllegalArgumentException{
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 Exception e) {
125            if (!ignoreException) {
126                // Exception has to thrown in EDT to be shown to user
127                SwingUtilities.invokeLater(new Runnable() {
128                    @Override
129                    public void run() {
130                        if (e instanceof RuntimeException) {
131                            BugReportExceptionHandler.handleException(e);
132                        } else {
133                            ExceptionDialogUtil.explainException(e);
134                        }
135                    }
136                });
137            }
138        }
139    }
140
141    /**
142     * Can be overriden if something needs to run after progress monitor is closed.
143     */
144    protected void afterFinish() {
145
146    }
147
148    @Override
149    public final void run() {
150        if (canceled)
151            return; // since realRun isn't executed, do not call to finish
152
153        if (EventQueue.isDispatchThread()) {
154            new Thread(new Runnable() {
155                @Override
156                public void run() {
157                    doRealRun();
158                }
159            }).start();
160        } else {
161            doRealRun();
162        }
163    }
164
165    @Override
166    public void operationCanceled() {
167        cancel();
168    }
169
170    /**
171     * User pressed cancel button.
172     */
173    protected abstract void cancel();
174
175    /**
176     * Called in the worker thread to do the actual work. When any of the
177     * exception is thrown, a message box will be displayed and closeDialog
178     * is called. finish() is called in any case.
179     */
180    protected abstract void realRun() throws SAXException, IOException, OsmTransferException;
181
182    /**
183     * Finish up the data work. Is guaranteed to be called if realRun is called.
184     * Finish is called in the gui thread just after the dialog disappeared.
185     */
186    protected abstract void finish();
187
188    /**
189     * Relies the progress monitor.
190     * @return the progress monitor
191     */
192    public ProgressMonitor getProgressMonitor() {
193        return progressMonitor;
194    }
195
196    /**
197     * Task can run in background if returned value != null. Note that it's tasks responsibility
198     * to ensure proper synchronization, PleaseWaitRunnable doesn't with it.
199     * @return If returned value is != null then task can run in background. TaskId could be used in future for "Always run in background" checkbox
200     */
201    public ProgressTaskId canRunInBackground() {
202        return null;
203    }
204}