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