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     */
035    public PleaseWaitRunnable(String title) {
036        this(title, false);
037    }
038    /**
039     * Create the runnable object with a given message for the user.
040     *
041     * @param title message for the user
042     * @param ignoreException If true, exception will be propagated to calling code. If false then
043     * exception will be thrown directly in EDT. When this runnable is executed using executor framework
044     * then use false unless you read result of task (because exception will get lost if you don't)
045     */
046    public PleaseWaitRunnable(String title, boolean ignoreException) {
047        this(title, new PleaseWaitProgressMonitor(title), ignoreException);
048    }
049
050    /**
051     * Create the runnable object with a given message for the user
052     *
053     * @param parent the parent component for the please wait dialog. Must not be null.
054     * @param title message for the user
055     * @param ignoreException If true, exception will be propagated to calling code. If false then
056     * exception will be thrown directly in EDT. When this runnable is executed using executor framework
057     * then use false unless you read result of task (because exception will get lost if you don't)
058     * @throws IllegalArgumentException thrown if parent is null
059     */
060    public PleaseWaitRunnable(Component parent, String title, boolean ignoreException) throws IllegalArgumentException{
061        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
062        this.title = title;
063        this.progressMonitor = new PleaseWaitProgressMonitor(parent, title);
064        this.ignoreException = ignoreException;
065    }
066
067    public PleaseWaitRunnable(String title, ProgressMonitor progressMonitor, boolean ignoreException) {
068        this.title = title;
069        this.progressMonitor = progressMonitor == null?new PleaseWaitProgressMonitor(title):progressMonitor;
070        this.ignoreException = ignoreException;
071    }
072
073    private void doRealRun() {
074        try {
075            ProgressTaskId oldTaskId = null;
076            try {
077                progressMonitor.addCancelListener(this);
078                progressMonitor.beginTask(title);
079                oldTaskId = progressMonitor.getProgressTaskId();
080                progressMonitor.setProgressTaskId(canRunInBackground());
081                try {
082                    realRun();
083                } finally {
084                    if (EventQueue.isDispatchThread()) {
085                        finish();
086                    } else {
087                        EventQueue.invokeAndWait(new Runnable() {
088                            @Override
089                            public void run() {
090                                finish();
091                            }
092                        });
093                    }
094                }
095            } finally {
096                progressMonitor.finishTask();
097                progressMonitor.removeCancelListener(this);
098                progressMonitor.setProgressTaskId(oldTaskId);
099                if (progressMonitor instanceof PleaseWaitProgressMonitor) {
100                    ((PleaseWaitProgressMonitor)progressMonitor).close();
101                }
102                if (EventQueue.isDispatchThread()) {
103                    afterFinish();
104                } else {
105                    EventQueue.invokeAndWait(new Runnable() {
106                        @Override
107                        public void run() {
108                            afterFinish();
109                        }
110                    });
111                }
112            }
113        } catch (final Exception e) {
114            if (!ignoreException) {
115                // Exception has to thrown in EDT to be shown to user
116                SwingUtilities.invokeLater(new Runnable() {
117                    @Override
118                    public void run() {
119                        if (e instanceof RuntimeException) {
120                            BugReportExceptionHandler.handleException(e);
121                        } else {
122                            ExceptionDialogUtil.explainException(e);
123                        }
124                    }
125                });
126            }
127        }
128    }
129
130    /**
131     * Can be overriden 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 (canceled)
140            return; // since realRun isn't executed, do not call to finish
141
142        if (EventQueue.isDispatchThread()) {
143            new Thread(new Runnable() {
144                @Override
145                public void run() {
146                    doRealRun();
147                }
148            }).start();
149        } else {
150            doRealRun();
151        }
152    }
153
154    @Override
155    public void operationCanceled() {
156        cancel();
157    }
158
159    /**
160     * User pressed cancel button.
161     */
162    protected abstract void cancel();
163
164    /**
165     * Called in the worker thread to do the actual work. When any of the
166     * exception is thrown, a message box will be displayed and closeDialog
167     * is called. finish() is called in any case.
168     */
169    protected abstract void realRun() throws SAXException, IOException, OsmTransferException;
170
171    /**
172     * Finish up the data work. Is guaranteed to be called if realRun is called.
173     * Finish is called in the gui thread just after the dialog disappeared.
174     */
175    protected abstract void finish();
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. TaskId could be used in future for "Always run in background" checkbox
185     */
186    public ProgressTaskId canRunInBackground() {
187        return null;
188    }
189}