384aee185471c7969735eb2a8ff5c8e38fac8362
[debian/openrocket] / src / net / sf / openrocket / gui / main / ExceptionHandler.java
1 package net.sf.openrocket.gui.main;
2
3 import javax.swing.JOptionPane;
4 import javax.swing.SwingUtilities;
5
6 import net.sf.openrocket.gui.dialogs.BugReportDialog;
7 import net.sf.openrocket.logging.LogHelper;
8 import net.sf.openrocket.startup.Application;
9 import net.sf.openrocket.util.BugException;
10
11
12 public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
13         
14         private static final LogHelper log = Application.getLogger();
15         
16         private static final int MEMORY_RESERVE = 512 * 1024;
17         
18         /**
19          * A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
20          * <p>
21          * This field is package-private so that the JRE cannot optimize its use away.
22          */
23         static volatile byte[] memoryReserve = null;
24         
25         private static ExceptionHandler instance = null;
26         
27
28         private volatile boolean handling = false;
29         
30         
31
32
33         @Override
34         public void uncaughtException(final Thread thread, final Throwable throwable) {
35                 
36                 // Free memory reserve if out of memory
37                 if (isOutOfMemoryError(throwable)) {
38                         memoryReserve = null;
39                         handling = false;
40                         log.error("Out of memory error detected", throwable);
41                 }
42                 
43                 if (isNonFatalJREBug(throwable)) {
44                         log.warn("Ignoring non-fatal JRE bug", throwable);
45                         return;
46                 }
47                 
48                 log.error("Handling uncaught exception on thread=" + thread, throwable);
49                 throwable.printStackTrace();
50                 
51                 if (handling) {
52                         log.warn("Exception is currently being handled, ignoring");
53                         return;
54                 }
55                 
56                 try {
57                         handling = true;
58                         
59                         // Show on the EDT
60                         if (SwingUtilities.isEventDispatchThread()) {
61                                 log.info("Exception handler running on EDT, showing dialog");
62                                 showDialog(thread, throwable);
63                         } else {
64                                 log.info("Exception handler not on EDT, invoking dialog on EDT");
65                                 SwingUtilities.invokeAndWait(new Runnable() {
66                                         public void run() {
67                                                 showDialog(thread, throwable);
68                                         }
69                                 });
70                         }
71                         
72                 } catch (Throwable ex) {
73                         
74                         // Make sure the handler does not throw any exceptions
75                         try {
76                                 log.error("Caught exception while handling exception", ex);
77                                 System.err.println("Exception in exception handler, dumping exception:");
78                                 ex.printStackTrace();
79                         } catch (Throwable ignore) {
80                         }
81                         
82                 } finally {
83                         // Mark handling as completed
84                         handling = false;
85                 }
86                 
87         }
88         
89         
90         /**
91          * Handle an error condition programmatically without throwing an exception.
92          * This can be used in cases where recovery of the error is desirable.
93          * 
94          * @param message       the error message.
95          */
96         public static void handleErrorCondition(String message) {
97                 log.error(1, message);
98                 handleErrorCondition(new InternalException(message));
99         }
100         
101         
102         /**
103          * Handle an error condition programmatically without throwing an exception.
104          * This can be used in cases where recovery of the error is desirable.
105          * 
106          * @param message       the error message.
107          * @param exception     the exception that occurred.
108          */
109         public static void handleErrorCondition(String message, Exception exception) {
110                 log.error(1, message, exception);
111                 handleErrorCondition(new InternalException(message, exception));
112         }
113         
114         
115         /**
116          * Handle an error condition programmatically without throwing an exception.
117          * This can be used in cases where recovery of the error is desirable.
118          * 
119          * @param exception             the exception that occurred.
120          */
121         public static void handleErrorCondition(final Exception exception) {
122                 if (!(exception instanceof InternalException)) {
123                         log.error(1, "Error occurred", exception);
124                 }
125                 final Thread thread = Thread.currentThread();
126                 final ExceptionHandler handler = instance;
127                 
128                 if (handler == null) {
129                         // Not initialized, throw the exception
130                         throw new BugException("Error condition before exception handling has been initialized", exception);
131                 }
132                 
133                 try {
134                         if (SwingUtilities.isEventDispatchThread()) {
135                                 log.info("Running in EDT, showing dialog");
136                                 handler.showDialog(thread, exception);
137                         } else {
138                                 log.info("Not in EDT, invoking and waiting for dialog");
139                                 SwingUtilities.invokeAndWait(new Runnable() {
140                                         public void run() {
141                                                 handler.showDialog(thread, exception);
142                                         }
143                                 });
144                         }
145                 } catch (Exception e) {
146                         log.error("Exception occurred while showing error dialog", e);
147                         e.printStackTrace();
148                 }
149         }
150         
151         
152         /**
153          * The actual handling routine.
154          * 
155          * @param t             the thread that caused the exception, or <code>null</code>.
156          * @param e             the exception.
157          */
158         private void showDialog(Thread t, Throwable e) {
159                 
160                 // Out of memory
161                 if (isOutOfMemoryError(e)) {
162                         log.info("Showing out-of-memory dialog");
163                         JOptionPane.showMessageDialog(null,
164                                         new Object[] {
165                                                         "OpenRocket is out of available memory!",
166                                                         "You should immediately close unnecessary design windows,",
167                                                         "save any unsaved designs and restart OpenRocket!"
168                                         }, "Out of memory", JOptionPane.ERROR_MESSAGE);
169                         return;
170                 }
171                 
172                 // Create the message
173                 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
174                 if (msg.length() > 90) {
175                         msg = msg.substring(0, 80) + "...";
176                 }
177                 
178                 // Unknown Error
179                 if (!(e instanceof Exception)) {
180                         log.info("Showing Error dialog");
181                         JOptionPane.showMessageDialog(null,
182                                         new Object[] {
183                                                         "An unknown Java error occurred:",
184                                                         msg,
185                                                         "<html>You should immediately close unnecessary design windows,<br>" +
186                                                                         "save any unsaved designs and restart OpenRocket!"
187                                         }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
188                         return;
189                 }
190                 
191
192                 // Normal exception, show question dialog               
193                 log.info("Showing Exception dialog");
194                 int selection = JOptionPane.showOptionDialog(null, new Object[] {
195                                 "OpenRocket encountered an uncaught exception.  This typically signifies " +
196                                                 "a bug in the software.",
197                                 "<html><em>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + msg + "</em>",
198                                 " ",
199                                 "Please take a moment to report this bug to the developers.",
200                                 "This can be done automatically if you have an Internet connection."
201                                 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
202                                 JOptionPane.ERROR_MESSAGE, null,
203                                 new Object[] { "View bug report", "Close" }, "View bug report");
204                 
205                 if (selection != 0) {
206                         // User cancelled
207                         log.user("User chose not to fill bug report");
208                         return;
209                 }
210                 
211                 // Show bug report dialog
212                 log.user("User requested sending bug report");
213                 BugReportDialog.showExceptionDialog(null, t, e);
214         }
215         
216         
217
218         /**
219          * Registers the uncaught exception handler.  This should be used to ensure that
220          * all necessary registrations are performed.
221          */
222         public static void registerExceptionHandler() {
223                 
224                 if (instance == null) {
225                         instance = new ExceptionHandler();
226                         Thread.setDefaultUncaughtExceptionHandler(instance);
227                         
228                         // Handler for modal dialogs of Sun's Java implementation
229                         // See bug ID 4499199.
230                         System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
231                         
232                         reserveMemory();
233                 }
234                 
235         }
236         
237         
238         /**
239          * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
240          */
241         private static void reserveMemory() {
242                 memoryReserve = new byte[MEMORY_RESERVE];
243                 for (int i = 0; i < MEMORY_RESERVE; i++) {
244                         memoryReserve[i] = (byte) i;
245                 }
246         }
247         
248         
249
250         /**
251          * Return whether this throwable was caused by an OutOfMemoryError
252          * condition.  An exception is deemed to be caused by OutOfMemoryError
253          * if the throwable or any of its causes is of the type OutOfMemoryError.
254          * <p>
255          * This method is required because Apple's JRE implementation sometimes
256          * masks OutOfMemoryErrors within RuntimeExceptions.  Idiots.
257          * 
258          * @param t             the throwable to examine.
259          * @return              whether this is an out-of-memory condition.
260          */
261         private boolean isOutOfMemoryError(Throwable t) {
262                 while (t != null) {
263                         if (t instanceof OutOfMemoryError)
264                                 return true;
265                         t = t.getCause();
266                 }
267                 return false;
268         }
269         
270         
271
272         /**
273          * Handler used in modal dialogs by Sun Java implementation.
274          */
275         public static class AwtHandler {
276                 public void handle(Throwable t) {
277                         if (instance != null) {
278                                 instance.uncaughtException(Thread.currentThread(), t);
279                         }
280                 }
281         }
282         
283         
284         /**
285          * Detect various non-fatal Sun JRE bugs.
286          * 
287          * @param t             the throwable
288          * @return              whether this exception should be ignored
289          */
290         private static boolean isNonFatalJREBug(Throwable t) {
291                 
292                 /*
293                  * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
294                  */
295                 if (t instanceof ArrayIndexOutOfBoundsException) {
296                         final String buggyClass = "sun.font.FontDesignMetrics";
297                         StackTraceElement[] elements = t.getStackTrace();
298                         if (elements.length >= 3 &&
299                                         (buggyClass.equals(elements[0].getClassName()) ||
300                                                         buggyClass.equals(elements[1].getClassName()) ||
301                                                 buggyClass.equals(elements[2].getClassName()))) {
302                                 log.warn("Ignoring Sun JRE bug 6828938:  " +
303                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
304                                 return true;
305                         }
306                 }
307                 
308                 /*
309                  * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
310                  */
311                 if (t instanceof NullPointerException) {
312                         StackTraceElement[] trace = t.getStackTrace();
313                         
314                         if (trace.length > 3 &&
315                                         trace[0].getClassName().equals("javax.swing.JComponent") &&
316                                         trace[0].getMethodName().equals("repaint") &&
317
318                                         trace[1].getClassName().equals("sun.swing.FilePane$2") &&
319                                         trace[1].getMethodName().equals("repaintListSelection") &&
320
321                                         trace[2].getClassName().equals("sun.swing.FilePane$2") &&
322                                         trace[2].getMethodName().equals("repaintSelection")) {
323                                 log.warn("Ignoring Sun JRE bug 6561072 " +
324                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
325                                 return true;
326                         }
327                 }
328                 
329                 /*
330                  * Detect and ignore bug in Sun JRE 1.6.0_19
331                  */
332                 if (t instanceof NullPointerException) {
333                         StackTraceElement[] trace = t.getStackTrace();
334                         
335                         if (trace.length > 3 &&
336                                         trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
337                                         trace[0].getMethodName().equals("pidlsEqual") &&
338
339                                         trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
340                                         trace[1].getMethodName().equals("equals") &&
341
342                                         trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
343                                         trace[2].getMethodName().equals("isFileSystemRoot")) {
344                                 log.warn("Ignoring Sun JRE bug " +
345                                                 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
346                                 return true;
347                         }
348                 }
349                 return false;
350         }
351         
352         
353         @SuppressWarnings("unused")
354         private static class InternalException extends Exception {
355                 public InternalException() {
356                         super();
357                 }
358                 
359                 public InternalException(String message, Throwable cause) {
360                         super(message, cause);
361                 }
362                 
363                 public InternalException(String message) {
364                         super(message);
365                 }
366                 
367                 public InternalException(Throwable cause) {
368                         super(cause);
369                 }
370         }
371 }