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