import javax.swing.SwingUtilities;
import net.sf.openrocket.gui.dialogs.BugReportDialog;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
-
- private static final int MEMORY_RESERVE = 512*1024;
+
+ private static final LogHelper log = Application.getLogger();
+
+ private static final int MEMORY_RESERVE = 512 * 1024;
/**
* A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
private static ExceptionHandler instance = null;
-
+
private volatile boolean handling = false;
-
-
+
+
@Override
- public void uncaughtException(final Thread t, final Throwable e) {
+ public void uncaughtException(final Thread thread, final Throwable throwable) {
// Free memory reserve if out of memory
- if (isOutOfMemoryError(e)) {
+ if (isOutOfMemoryError(throwable)) {
memoryReserve = null;
handling = false;
+ log.error("Out of memory error detected", throwable);
+ }
+
+ if (isNonFatalJREBug(throwable)) {
+ log.warn("Ignoring non-fatal JRE bug", throwable);
+ return;
+ }
+
+ log.error("Handling uncaught exception on thread=" + thread, throwable);
+ throwable.printStackTrace();
+
+ if (handling) {
+ log.warn("Exception is currently being handled, ignoring");
+ return;
}
-
- e.printStackTrace();
try {
-
- if (handling) {
- System.err.println("Exception is currently being handled, ignoring:");
- e.printStackTrace();
- return;
- }
-
handling = true;
// Show on the EDT
if (SwingUtilities.isEventDispatchThread()) {
- showDialog(t, e);
+ log.info("Exception handler running on EDT, showing dialog");
+ showDialog(thread, throwable);
} else {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
- showDialog(t, e);
- }
- });
+ log.info("Exception handler not on EDT, invoking dialog on EDT");
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ showDialog(thread, throwable);
+ }
+ });
}
} catch (Throwable ex) {
// Make sure the handler does not throw any exceptions
try {
+ log.error("Caught exception while handling exception", ex);
System.err.println("Exception in exception handler, dumping exception:");
ex.printStackTrace();
- } catch (Throwable ignore) { }
+ } catch (Exception ignore) {
+ }
} finally {
// Mark handling as completed
}
}
-
+
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param message the error message.
*/
public static void handleErrorCondition(String message) {
+ log.error(1, message, new TraceException());
handleErrorCondition(new InternalException(message));
}
-
+
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param message the error message.
* @param exception the exception that occurred.
*/
- public static void handleErrorCondition(String message, Exception exception) {
+ public static void handleErrorCondition(String message, Throwable exception) {
+ log.error(1, message, exception);
handleErrorCondition(new InternalException(message, exception));
}
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param exception the exception that occurred.
*/
- public static void handleErrorCondition(final Exception exception) {
- final ExceptionHandler handler;
-
+ public static void handleErrorCondition(final Throwable exception) {
try {
-
- if (instance == null) {
- handler = new ExceptionHandler();
- } else {
- handler = instance;
+ if (!(exception instanceof InternalException)) {
+ log.error(1, "Error occurred", exception);
}
-
final Thread thread = Thread.currentThread();
-
+ final ExceptionHandler handler = instance;
+
+ if (handler == null) {
+ log.error("Error condition occurred before exception handling has been initialized", exception);
+ return;
+ }
+
if (SwingUtilities.isEventDispatchThread()) {
+ log.info("Running in EDT, showing dialog");
handler.showDialog(thread, exception);
} else {
- SwingUtilities.invokeAndWait(new Runnable() {
+ log.info("Not in EDT, invoking dialog later");
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
public void run() {
handler.showDialog(thread, exception);
}
});
}
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Exception occurred in error handler", e);
}
}
// Out of memory
if (isOutOfMemoryError(e)) {
- JOptionPane.showMessageDialog(null,
- new Object[] {
- "OpenRocket can out of available memory!",
- "You should immediately close unnecessary design windows,",
- "save any unsaved designs and restart OpenRocket!"
+ log.info("Showing out-of-memory dialog");
+ JOptionPane.showMessageDialog(null,
+ new Object[] {
+ "OpenRocket is out of available memory!",
+ "You should immediately close unnecessary design windows,",
+ "save any unsaved designs and restart OpenRocket!"
}, "Out of memory", JOptionPane.ERROR_MESSAGE);
return;
}
}
// Unknown Error
- if (!(e instanceof Exception)) {
- JOptionPane.showMessageDialog(null,
- new Object[] {
- "An unknown Java error occurred:",
- msg,
- "<html>You should immediately close unnecessary design windows,<br>" +
- "save any unsaved designs and restart OpenRocket!"
+ if (!(e instanceof Exception) && !(e instanceof LinkageError)) {
+ log.info("Showing Error dialog");
+ JOptionPane.showMessageDialog(null,
+ new Object[] {
+ "An unknown Java error occurred:",
+ msg,
+ "<html>You should immediately close unnecessary design windows,<br>" +
+ "save any unsaved designs and restart OpenRocket!"
}, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
return;
}
-
+
// Normal exception, show question dialog
+ log.info("Showing Exception dialog");
int selection = JOptionPane.showOptionDialog(null, new Object[] {
"OpenRocket encountered an uncaught exception. This typically signifies " +
- "a bug in the software.",
+ "a bug in the software.",
"<html><em> " + msg + "</em>",
" ",
"Please take a moment to report this bug to the developers.",
"This can be done automatically if you have an Internet connection."
- }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
- JOptionPane.ERROR_MESSAGE, null,
+ }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.ERROR_MESSAGE, null,
new Object[] { "View bug report", "Close" }, "View bug report");
if (selection != 0) {
// User cancelled
+ log.user("User chose not to fill bug report");
return;
}
// Show bug report dialog
+ log.user("User requested sending bug report");
BugReportDialog.showExceptionDialog(null, t, e);
-
}
-
+
/**
* Registers the uncaught exception handler. This should be used to ensure that
* all necessary registrations are performed.
*/
private static void reserveMemory() {
memoryReserve = new byte[MEMORY_RESERVE];
- for (int i=0; i<MEMORY_RESERVE; i++) {
- memoryReserve[i] = (byte)i;
+ for (int i = 0; i < MEMORY_RESERVE; i++) {
+ memoryReserve[i] = (byte) i;
}
}
-
+
/**
* Return whether this throwable was caused by an OutOfMemoryError
* condition. An exception is deemed to be caused by OutOfMemoryError
}
-
+
/**
* Handler used in modal dialogs by Sun Java implementation.
*/
public static class AwtHandler {
public void handle(Throwable t) {
+ if (instance != null) {
+ instance.uncaughtException(Thread.currentThread(), t);
+ }
+ }
+ }
+
+
+ /**
+ * Detect various non-fatal Sun JRE bugs.
+ *
+ * @param t the throwable
+ * @return whether this exception should be ignored
+ */
+ private static boolean isNonFatalJREBug(Throwable t) {
+
+ // NOTE: Calling method logs the entire throwable, so log only message here
+
+ /*
+ * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
+ */
+ if (t instanceof ArrayIndexOutOfBoundsException) {
+ final String buggyClass = "sun.font.FontDesignMetrics";
+ StackTraceElement[] elements = t.getStackTrace();
+ if (elements.length >= 3 &&
+ (buggyClass.equals(elements[0].getClassName()) ||
+ buggyClass.equals(elements[1].getClassName()) ||
+ buggyClass.equals(elements[2].getClassName()))) {
+ log.warn("Ignoring Sun JRE bug 6828938: " +
+ "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
+ return true;
+ }
+ }
+
+ /*
+ * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
+ */
+ if (t instanceof NullPointerException) {
+ StackTraceElement[] trace = t.getStackTrace();
- /*
- * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
- */
- if (t instanceof ArrayIndexOutOfBoundsException) {
- final String buggyClass = "sun.font.FontDesignMetrics";
- StackTraceElement[] elements = t.getStackTrace();
- if (elements.length >= 3 &&
- (buggyClass.equals(elements[0].getClassName()) ||
- buggyClass.equals(elements[1].getClassName()) ||
- buggyClass.equals(elements[2].getClassName()))) {
- System.err.println("Ignoring Sun JRE bug 6828938: " + t);
- return;
- }
+ if (trace.length > 3 &&
+ trace[0].getClassName().equals("javax.swing.JComponent") &&
+ trace[0].getMethodName().equals("repaint") &&
+
+ trace[1].getClassName().equals("sun.swing.FilePane$2") &&
+ trace[1].getMethodName().equals("repaintListSelection") &&
+
+ trace[2].getClassName().equals("sun.swing.FilePane$2") &&
+ trace[2].getMethodName().equals("repaintSelection")) {
+ log.warn("Ignoring Sun JRE bug 6561072 " +
+ "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
+ return true;
}
+ }
+
+
+ /*
+ * Detect and ignore bug 6933331 in Sun JRE 1.6.0_18 and others
+ */
+ if (t instanceof IllegalStateException) {
+ StackTraceElement[] trace = t.getStackTrace();
+ if (trace.length > 1 &&
+ trace[0].getClassName().equals("sun.awt.windows.WComponentPeer") &&
+ trace[0].getMethodName().equals("getBackBuffer")) {
+ log.warn("Ignoring Sun JRE bug 6933331 " +
+ "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933331): " + t);
+ return true;
+ }
+ }
+
+ /*
+ * Detect and ignore bug in Sun JRE 1.6.0_19
+ */
+ if (t instanceof NullPointerException) {
+ StackTraceElement[] trace = t.getStackTrace();
- if (instance != null) {
- instance.uncaughtException(Thread.currentThread(), t);
+ if (trace.length > 3 &&
+ trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
+ trace[0].getMethodName().equals("pidlsEqual") &&
+
+ trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
+ trace[1].getMethodName().equals("equals") &&
+
+ trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
+ trace[2].getMethodName().equals("isFileSystemRoot")) {
+ log.warn("Ignoring Sun JRE bug " +
+ "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
+ return true;
}
}
+
+ /*
+ * Detect Sun JRE bug in D3D
+ */
+ if (t instanceof ClassCastException) {
+ if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
+ log.warn("Ignoring Sun JRE bug " +
+ "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
+ return true;
+ }
+ }
+
+ return false;
}
+ @SuppressWarnings("unused")
private static class InternalException extends Exception {
public InternalException() {
super();
}
-
+
public InternalException(String message, Throwable cause) {
super(message, cause);
}
-
+
public InternalException(String message) {
super(message);
}
-
+
public InternalException(Throwable cause) {
super(cause);
}