Component scaling support
[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, Exception 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 Exception 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                  * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
306                  */
307                 if (t instanceof ArrayIndexOutOfBoundsException) {
308                         final String buggyClass = "sun.font.FontDesignMetrics";
309                         StackTraceElement[] elements = t.getStackTrace();
310                         if (elements.length >= 3 &&
311                                         (buggyClass.equals(elements[0].getClassName()) ||
312                                                         buggyClass.equals(elements[1].getClassName()) ||
313                                                 buggyClass.equals(elements[2].getClassName()))) {
314                                 log.warn("Ignoring Sun JRE bug 6828938:  " +
315                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
316                                 return true;
317                         }
318                 }
319                 
320                 /*
321                  * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
322                  */
323                 if (t instanceof NullPointerException) {
324                         StackTraceElement[] trace = t.getStackTrace();
325                         
326                         if (trace.length > 3 &&
327                                         trace[0].getClassName().equals("javax.swing.JComponent") &&
328                                         trace[0].getMethodName().equals("repaint") &&
329
330                                         trace[1].getClassName().equals("sun.swing.FilePane$2") &&
331                                         trace[1].getMethodName().equals("repaintListSelection") &&
332
333                                         trace[2].getClassName().equals("sun.swing.FilePane$2") &&
334                                         trace[2].getMethodName().equals("repaintSelection")) {
335                                 log.warn("Ignoring Sun JRE bug 6561072 " +
336                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
337                                 return true;
338                         }
339                 }
340                 
341
342                 /*
343                  * Detect and ignore bug 6933331 in Sun JRE 1.6.0_18 and others
344                  */
345                 if (t instanceof IllegalStateException) {
346                         StackTraceElement[] trace = t.getStackTrace();
347                         
348                         if (trace.length > 1 &&
349                                         trace[0].getClassName().equals("sun.awt.windows.WComponentPeer") &&
350                                         trace[0].getMethodName().equals("getBackBuffer")) {
351                                 log.warn("Ignoring Sun JRE bug 6933331 " +
352                                                 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933331): " + t);
353                                 return true;
354                         }
355                 }
356                 
357                 /*
358                  * Detect and ignore bug in Sun JRE 1.6.0_19
359                  */
360                 if (t instanceof NullPointerException) {
361                         StackTraceElement[] trace = t.getStackTrace();
362                         
363                         if (trace.length > 3 &&
364                                         trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
365                                         trace[0].getMethodName().equals("pidlsEqual") &&
366
367                                         trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
368                                         trace[1].getMethodName().equals("equals") &&
369
370                                         trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
371                                         trace[2].getMethodName().equals("isFileSystemRoot")) {
372                                 log.warn("Ignoring Sun JRE bug " +
373                                                 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
374                                 return true;
375                         }
376                 }
377                 
378                 /*
379                  * Detect Sun JRE bug in D3D
380                  */
381                 if (t instanceof ClassCastException) {
382                         if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
383                                 log.warn("Ignoring Sun JRE bug " +
384                                                 "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
385                                 return true;
386                         }
387                 }
388                 
389                 return false;
390         }
391         
392         
393         @SuppressWarnings("unused")
394         private static class InternalException extends Exception {
395                 public InternalException() {
396                         super();
397                 }
398                 
399                 public InternalException(String message, Throwable cause) {
400                         super(message, cause);
401                 }
402                 
403                 public InternalException(String message) {
404                         super(message);
405                 }
406                 
407                 public InternalException(Throwable cause) {
408                         super(cause);
409                 }
410         }
411 }