Merge branch 'upstream' into debian
[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.logging.TraceException;
9 import net.sf.openrocket.startup.Application;
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                                         @Override
67                                         public void run() {
68                                                 showDialog(thread, throwable);
69                                         }
70                                 });
71                         }
72                         
73                 } catch (Throwable ex) {
74                         
75                         // Make sure the handler does not throw any exceptions
76                         try {
77                                 log.error("Caught exception while handling exception", ex);
78                                 System.err.println("Exception in exception handler, dumping exception:");
79                                 ex.printStackTrace();
80                         } catch (Exception ignore) {
81                         }
82                         
83                 } finally {
84                         // Mark handling as completed
85                         handling = false;
86                 }
87                 
88         }
89         
90         
91         /**
92          * Handle an error condition programmatically without throwing an exception.
93          * This can be used in cases where recovery of the error is desirable.
94          * <p>
95          * This method is guaranteed never to throw an exception, and can thus be safely
96          * used in finally blocks.
97          * 
98          * @param message       the error message.
99          */
100         public static void handleErrorCondition(String message) {
101                 log.error(1, message, new TraceException());
102                 handleErrorCondition(new InternalException(message));
103         }
104         
105         
106         /**
107          * Handle an error condition programmatically without throwing an exception.
108          * This can be used in cases where recovery of the error is desirable.
109          * <p>
110          * This method is guaranteed never to throw an exception, and can thus be safely
111          * used in finally blocks.
112          * 
113          * @param message       the error message.
114          * @param exception     the exception that occurred.
115          */
116         public static void handleErrorCondition(String message, Throwable exception) {
117                 log.error(1, message, exception);
118                 handleErrorCondition(new InternalException(message, exception));
119         }
120         
121         
122         /**
123          * Handle an error condition programmatically without throwing an exception.
124          * This can be used in cases where recovery of the error is desirable.
125          * <p>
126          * This method is guaranteed never to throw an exception, and can thus be safely
127          * used in finally blocks.
128          * 
129          * @param exception             the exception that occurred.
130          */
131         public static void handleErrorCondition(final Throwable exception) {
132                 try {
133                         if (!(exception instanceof InternalException)) {
134                                 log.error(1, "Error occurred", exception);
135                         }
136                         final Thread thread = Thread.currentThread();
137                         final ExceptionHandler handler = instance;
138                         
139                         if (handler == null) {
140                                 log.error("Error condition occurred before exception handling has been initialized", exception);
141                                 return;
142                         }
143                         
144                         if (SwingUtilities.isEventDispatchThread()) {
145                                 log.info("Running in EDT, showing dialog");
146                                 handler.showDialog(thread, exception);
147                         } else {
148                                 log.info("Not in EDT, invoking dialog later");
149                                 SwingUtilities.invokeLater(new Runnable() {
150                                         @Override
151                                         public void run() {
152                                                 handler.showDialog(thread, exception);
153                                         }
154                                 });
155                         }
156                 } catch (Exception e) {
157                         log.error("Exception occurred in error handler", e);
158                 }
159         }
160         
161         
162         /**
163          * The actual handling routine.
164          * 
165          * @param t             the thread that caused the exception, or <code>null</code>.
166          * @param e             the exception.
167          */
168         private void showDialog(Thread t, Throwable e) {
169                 
170                 // Out of memory
171                 if (isOutOfMemoryError(e)) {
172                         log.info("Showing out-of-memory dialog");
173                         JOptionPane.showMessageDialog(null,
174                                         new Object[] {
175                                                         "OpenRocket is out of available memory!",
176                                                         "You should immediately close unnecessary design windows,",
177                                                         "save any unsaved designs and restart OpenRocket!"
178                                         }, "Out of memory", JOptionPane.ERROR_MESSAGE);
179                         return;
180                 }
181                 
182                 // Create the message
183                 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
184                 if (msg.length() > 90) {
185                         msg = msg.substring(0, 80) + "...";
186                 }
187                 
188                 // Unknown Error
189                 if (!(e instanceof Exception) && !(e instanceof LinkageError)) {
190                         log.info("Showing Error dialog");
191                         JOptionPane.showMessageDialog(null,
192                                         new Object[] {
193                                                         "An unknown Java error occurred:",
194                                                         msg,
195                                                         "<html>You should immediately close unnecessary design windows,<br>" +
196                                                                         "save any unsaved designs and restart OpenRocket!"
197                                         }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
198                         return;
199                 }
200                 
201
202                 // Normal exception, show question dialog               
203                 log.info("Showing Exception dialog");
204                 int selection = JOptionPane.showOptionDialog(null, new Object[] {
205                                 "OpenRocket encountered an uncaught exception.  This typically signifies " +
206                                                 "a bug in the software.",
207                                 "<html><em>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + msg + "</em>",
208                                 " ",
209                                 "Please take a moment to report this bug to the developers.",
210                                 "This can be done automatically if you have an Internet connection."
211                                 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
212                                 JOptionPane.ERROR_MESSAGE, null,
213                                 new Object[] { "View bug report", "Close" }, "View bug report");
214                 
215                 if (selection != 0) {
216                         // User cancelled
217                         log.user("User chose not to fill bug report");
218                         return;
219                 }
220                 
221                 // Show bug report dialog
222                 log.user("User requested sending bug report");
223                 BugReportDialog.showExceptionDialog(null, t, e);
224         }
225         
226         
227
228         /**
229          * Registers the uncaught exception handler.  This should be used to ensure that
230          * all necessary registrations are performed.
231          */
232         public static void registerExceptionHandler() {
233                 
234                 if (instance == null) {
235                         instance = new ExceptionHandler();
236                         Thread.setDefaultUncaughtExceptionHandler(instance);
237                         
238                         // Handler for modal dialogs of Sun's Java implementation
239                         // See bug ID 4499199.
240                         System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
241                         
242                         reserveMemory();
243                 }
244                 
245         }
246         
247         
248         /**
249          * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
250          */
251         private static void reserveMemory() {
252                 memoryReserve = new byte[MEMORY_RESERVE];
253                 for (int i = 0; i < MEMORY_RESERVE; i++) {
254                         memoryReserve[i] = (byte) i;
255                 }
256         }
257         
258         
259
260         /**
261          * Return whether this throwable was caused by an OutOfMemoryError
262          * condition.  An exception is deemed to be caused by OutOfMemoryError
263          * if the throwable or any of its causes is of the type OutOfMemoryError.
264          * <p>
265          * This method is required because Apple's JRE implementation sometimes
266          * masks OutOfMemoryErrors within RuntimeExceptions.  Idiots.
267          * 
268          * @param t             the throwable to examine.
269          * @return              whether this is an out-of-memory condition.
270          */
271         private boolean isOutOfMemoryError(Throwable t) {
272                 while (t != null) {
273                         if (t instanceof OutOfMemoryError)
274                                 return true;
275                         t = t.getCause();
276                 }
277                 return false;
278         }
279         
280         
281
282         /**
283          * Handler used in modal dialogs by Sun Java implementation.
284          */
285         public static class AwtHandler {
286                 public void handle(Throwable t) {
287                         if (instance != null) {
288                                 instance.uncaughtException(Thread.currentThread(), t);
289                         }
290                 }
291         }
292         
293         
294         /**
295          * Detect various non-fatal Sun JRE bugs.
296          * 
297          * @param t             the throwable
298          * @return              whether this exception should be ignored
299          */
300         private static boolean isNonFatalJREBug(Throwable t) {
301                 
302                 // NOTE:  Calling method logs the entire throwable, so log only message here
303                 
304
305                 /*
306                  * Detect and ignore bug 6826104 in Sun JRE.
307                  */
308                 if (t instanceof NullPointerException) {
309                         StackTraceElement[] trace = t.getStackTrace();
310                         
311                         if (trace.length > 3 &&
312                                         trace[0].getClassName().equals("sun.awt.X11.XWindowPeer") &&
313                                         trace[0].getMethodName().equals("restoreTransientFor") &&
314
315                                         trace[1].getClassName().equals("sun.awt.X11.XWindowPeer") &&
316                                         trace[1].getMethodName().equals("removeFromTransientFors") &&
317
318                                         trace[2].getClassName().equals("sun.awt.X11.XWindowPeer") &&
319                                         trace[2].getMethodName().equals("setModalBlocked")) {
320                                 log.warn("Ignoring Sun JRE bug (6826104): http://bugs.sun.com/view_bug.do?bug_id=6826104" + t);
321                                 return true;
322                         }
323                         
324                 }
325                 
326
327                 /*
328                  * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
329                  */
330                 if (t instanceof ArrayIndexOutOfBoundsException) {
331                         final String buggyClass = "sun.font.FontDesignMetrics";
332                         StackTraceElement[] elements = t.getStackTrace();
333                         if (elements.length >= 3 &&
334                                         (buggyClass.equals(elements[0].getClassName()) ||
335                                                         buggyClass.equals(elements[1].getClassName()) ||
336                                                 buggyClass.equals(elements[2].getClassName()))) {
337                                 log.warn("Ignoring Sun JRE bug 6828938:  " +
338                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
339                                 return true;
340                         }
341                 }
342                 
343                 /*
344                  * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
345                  */
346                 if (t instanceof NullPointerException) {
347                         StackTraceElement[] trace = t.getStackTrace();
348                         
349                         if (trace.length > 3 &&
350                                         trace[0].getClassName().equals("javax.swing.JComponent") &&
351                                         trace[0].getMethodName().equals("repaint") &&
352
353                                         trace[1].getClassName().equals("sun.swing.FilePane$2") &&
354                                         trace[1].getMethodName().equals("repaintListSelection") &&
355
356                                         trace[2].getClassName().equals("sun.swing.FilePane$2") &&
357                                         trace[2].getMethodName().equals("repaintSelection")) {
358                                 log.warn("Ignoring Sun JRE bug 6561072 " +
359                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
360                                 return true;
361                         }
362                 }
363                 
364
365                 /*
366                  * Detect and ignore bug 6933331 in Sun JRE 1.6.0_18 and others
367                  */
368                 if (t instanceof IllegalStateException) {
369                         StackTraceElement[] trace = t.getStackTrace();
370                         
371                         if (trace.length > 1 &&
372                                         trace[0].getClassName().equals("sun.awt.windows.WComponentPeer") &&
373                                         trace[0].getMethodName().equals("getBackBuffer")) {
374                                 log.warn("Ignoring Sun JRE bug 6933331 " +
375                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933331): " + t);
376                                 return true;
377                         }
378                 }
379                 
380                 /*
381                  * Detect and ignore bug in Sun JRE 1.6.0_19
382                  */
383                 if (t instanceof NullPointerException) {
384                         StackTraceElement[] trace = t.getStackTrace();
385                         
386                         if (trace.length > 3 &&
387                                         trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
388                                         trace[0].getMethodName().equals("pidlsEqual") &&
389
390                                         trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
391                                         trace[1].getMethodName().equals("equals") &&
392
393                                         trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
394                                         trace[2].getMethodName().equals("isFileSystemRoot")) {
395                                 log.warn("Ignoring Sun JRE bug " +
396                                                 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
397                                 return true;
398                         }
399                 }
400                 
401                 /*
402                  * Detect Sun JRE bug in D3D
403                  */
404                 if (t instanceof ClassCastException) {
405                         if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
406                                 log.warn("Ignoring Sun JRE bug " +
407                                                 "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
408                                 return true;
409                         }
410                 }
411                 
412                 return false;
413         }
414         
415         
416         @SuppressWarnings("unused")
417         private static class InternalException extends Exception {
418                 public InternalException() {
419                         super();
420                 }
421                 
422                 public InternalException(String message, Throwable cause) {
423                         super(message, cause);
424                 }
425                 
426                 public InternalException(String message) {
427                         super(message);
428                 }
429                 
430                 public InternalException(Throwable cause) {
431                         super(cause);
432                 }
433         }
434 }