001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.gui;
003    
004    import java.awt.Component;
005    import java.awt.EventQueue;
006    import java.io.IOException;
007    
008    import javax.swing.SwingUtilities;
009    
010    import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
011    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
012    import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
013    import org.openstreetmap.josm.gui.progress.ProgressTaskId;
014    import org.openstreetmap.josm.io.OsmTransferException;
015    import org.openstreetmap.josm.tools.BugReportExceptionHandler;
016    import org.openstreetmap.josm.tools.CheckParameterUtil;
017    import 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     */
025    public 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                                public void run() {
089                                    finish();
090                                }
091                            });
092                        }
093                    }
094                } finally {
095                    progressMonitor.finishTask();
096                    progressMonitor.removeCancelListener(this);
097                    progressMonitor.setProgressTaskId(oldTaskId);
098                    if (progressMonitor instanceof PleaseWaitProgressMonitor) {
099                        ((PleaseWaitProgressMonitor)progressMonitor).close();
100                    }
101                    if (EventQueue.isDispatchThread()) {
102                        afterFinish();
103                    } else {
104                        EventQueue.invokeAndWait(new Runnable() {
105                            public void run() {
106                                afterFinish();
107                            }
108                        });
109                    }
110                }
111            } catch (final Exception e) {
112                if (!ignoreException) {
113                    // Exception has to thrown in EDT to be shown to user
114                    SwingUtilities.invokeLater(new Runnable() {
115                        public void run() {
116                            if (e instanceof RuntimeException) {
117                                BugReportExceptionHandler.handleException(e);
118                            } else {
119                                ExceptionDialogUtil.explainException(e);
120                            }
121                        }
122                    });
123                }
124            }
125        }
126    
127        /**
128         * Can be overriden if something needs to run after progress monitor is closed.
129         */
130        protected void afterFinish() {
131    
132        }
133    
134        public final void run() {
135            if (canceled)
136                return; // since realRun isn't executed, do not call to finish
137    
138            if (EventQueue.isDispatchThread()) {
139                new Thread(new Runnable() {
140                    public void run() {
141                        doRealRun();
142                    }
143                }).start();
144            } else {
145                doRealRun();
146            }
147        }
148    
149        public void operationCanceled() {
150            cancel();
151        }
152    
153        /**
154         * User pressed cancel button.
155         */
156        protected abstract void cancel();
157    
158        /**
159         * Called in the worker thread to do the actual work. When any of the
160         * exception is thrown, a message box will be displayed and closeDialog
161         * is called. finish() is called in any case.
162         */
163        protected abstract void realRun() throws SAXException, IOException, OsmTransferException;
164    
165        /**
166         * Finish up the data work. Is guaranteed to be called if realRun is called.
167         * Finish is called in the gui thread just after the dialog disappeared.
168         */
169        protected abstract void finish();
170    
171        public ProgressMonitor getProgressMonitor() {
172            return progressMonitor;
173        }
174    
175        /**
176         * Task can run in background if returned value <> null. Note that it's tasks responsibility
177         * to ensure proper synchronization, PleaseWaitRunnable doesn't with it.
178         * @return If returned value is <> null then task can run in background. TaskId could be used in future for "Always run in background" checkbox
179         */
180        public ProgressTaskId canRunInBackground() {
181            return null;
182        }
183    }