From: kruland2607 Date: Mon, 19 Dec 2011 22:01:05 +0000 (+0000) Subject: Extract and interface for ExceptionHelper in package net.sf.openrocket.startup. ... X-Git-Tag: upstream/12.03~1^2~233 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=549da0aedef6771b1519032c1b455d75e1d4c23f;p=debian%2Fopenrocket Extract and interface for ExceptionHelper in package net.sf.openrocket.startup. Register an instance implementing this interface in the Application object in Startup2. Use the static locator functions in Application to retrieve reference to the ExceptionHandler in all the dependent classes. Note: net.sf.openrocket.gui.main.ExceptionHandler was renamed to net.sf.openrocket.gui.main.SwingExceptionHandler and all its static methods were changed to instance methods. git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@237 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/src/net/sf/openrocket/document/OpenRocketDocument.java b/src/net/sf/openrocket/document/OpenRocketDocument.java index 3853f4ab..9cd3d841 100644 --- a/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -11,7 +11,6 @@ import javax.swing.Action; import net.sf.openrocket.document.events.DocumentChangeEvent; import net.sf.openrocket.document.events.DocumentChangeListener; import net.sf.openrocket.document.events.SimulationChangeEvent; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; @@ -500,7 +499,7 @@ public class OpenRocketDocument implements ComponentChangeListener { if (!undoErrorReported) { undoErrorReported = true; - ExceptionHandler.handleErrorCondition("Undo/Redo error: " + error); + Application.getExceptionHandler().handleErrorCondition("Undo/Redo error: " + error); } } diff --git a/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java index 2e010f6e..e50a8ea5 100644 --- a/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java +++ b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java @@ -4,7 +4,7 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; -import net.sf.openrocket.gui.main.ExceptionHandler; +import net.sf.openrocket.startup.Application; public abstract class ColumnTableModel extends AbstractTableModel { private final Column[] columns; @@ -47,7 +47,7 @@ public abstract class ColumnTableModel extends AbstractTableModel { public Object getValueAt(int row, int col) { if ((row < 0) || (row >= getRowCount()) || (col < 0) || (col >= columns.length)) { - ExceptionHandler.handleErrorCondition("Error: Requested illegal column/row, col=" + col + " row=" + row); + Application.getExceptionHandler().handleErrorCondition("Error: Requested illegal column/row, col=" + col + " row=" + row); return null; } return columns[col].getValueAt(row); diff --git a/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java index fb8b32fd..34f3d646 100644 --- a/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java @@ -25,7 +25,6 @@ import net.sf.openrocket.gui.SpinnerEditor; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.UnitSelector; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; @@ -410,7 +409,7 @@ public class ScaleDialog extends JDialog { private void doScale() { double mul = multiplier.getValue(); if (!(SCALE_MIN <= mul && mul <= SCALE_MAX)) { - ExceptionHandler.handleErrorCondition("Illegal multiplier value, mul=" + mul); + Application.getExceptionHandler().handleErrorCondition("Illegal multiplier value, mul=" + mul); return; } diff --git a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java index 611bbffa..fa92a671 100644 --- a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java +++ b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java @@ -407,13 +407,13 @@ public class ComponentAddButtons extends JPanel implements Scrollable { if (c == null) { // Should not occur - ExceptionHandler.handleErrorCondition("ERROR: Could not place new component."); + Application.getExceptionHandler().handleErrorCondition("ERROR: Could not place new component."); updateEnabled(); return; } if (constructor == null) { - ExceptionHandler.handleErrorCondition("ERROR: Construction of type not supported yet."); + Application.getExceptionHandler().handleErrorCondition("ERROR: Construction of type not supported yet."); return; } @@ -525,7 +525,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable { // Insert at the end of the parent return new Pair(parent, null); default: - ExceptionHandler.handleErrorCondition("ERROR: Bad position type: " + pos); + Application.getExceptionHandler().handleErrorCondition("ERROR: Bad position type: " + pos); return null; } } @@ -572,7 +572,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable { sel = 2; break; default: - ExceptionHandler.handleErrorCondition("ERROR: JOptionPane returned " + sel); + Application.getExceptionHandler().handleErrorCondition("ERROR: JOptionPane returned " + sel); return 0; } diff --git a/src/net/sf/openrocket/gui/main/ComponentIcons.java b/src/net/sf/openrocket/gui/main/ComponentIcons.java index cd71c2a6..b0071caa 100644 --- a/src/net/sf/openrocket/gui/main/ComponentIcons.java +++ b/src/net/sf/openrocket/gui/main/ComponentIcons.java @@ -124,7 +124,7 @@ public class ComponentIcons { private static ImageIcon loadSmall(String file, String desc) { URL url = ClassLoader.getSystemResource(file); if (url == null) { - ExceptionHandler.handleErrorCondition("ERROR: Couldn't find file: " + file); + Application.getExceptionHandler().handleErrorCondition("ERROR: Couldn't find file: " + file); return null; } return new ImageIcon(url, desc); @@ -141,7 +141,7 @@ public class ComponentIcons { bi = ImageIO.read(url); bi2 = ImageIO.read(url); // How the fsck can one duplicate a BufferedImage??? } catch (IOException e) { - ExceptionHandler.handleErrorCondition("ERROR: Couldn't read file: " + file, e); + Application.getExceptionHandler().handleErrorCondition("ERROR: Couldn't read file: " + file, e); return new ImageIcon[] { null, null }; } @@ -176,7 +176,7 @@ public class ComponentIcons { return icons; } else { - ExceptionHandler.handleErrorCondition("ERROR: Couldn't find file: " + file); + Application.getExceptionHandler().handleErrorCondition("ERROR: Couldn't find file: " + file); return new ImageIcon[] { null, null }; } } diff --git a/src/net/sf/openrocket/gui/main/ExceptionHandler.java b/src/net/sf/openrocket/gui/main/ExceptionHandler.java deleted file mode 100644 index 83301da7..00000000 --- a/src/net/sf/openrocket/gui/main/ExceptionHandler.java +++ /dev/null @@ -1,434 +0,0 @@ -package net.sf.openrocket.gui.main; - -import javax.swing.JOptionPane; -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 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. - *

- * This field is package-private so that the JRE cannot optimize its use away. - */ - static volatile byte[] memoryReserve = null; - - private static ExceptionHandler instance = null; - - - private volatile boolean handling = false; - - - - - @Override - public void uncaughtException(final Thread thread, final Throwable throwable) { - - // Free memory reserve if out of memory - 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; - } - - try { - handling = true; - - // Show on the EDT - if (SwingUtilities.isEventDispatchThread()) { - log.info("Exception handler running on EDT, showing dialog"); - showDialog(thread, throwable); - } else { - 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 (Exception ignore) { - } - - } finally { - // Mark handling as completed - handling = false; - } - - } - - - /** - * Handle an error condition programmatically without throwing an exception. - * This can be used in cases where recovery of the error is desirable. - *

- * 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. - *

- * 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, 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. - *

- * 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 Throwable exception) { - try { - 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 { - log.info("Not in EDT, invoking dialog later"); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - handler.showDialog(thread, exception); - } - }); - } - } catch (Exception e) { - log.error("Exception occurred in error handler", e); - } - } - - - /** - * The actual handling routine. - * - * @param t the thread that caused the exception, or null. - * @param e the exception. - */ - private void showDialog(Thread t, Throwable e) { - - // Out of memory - if (isOutOfMemoryError(e)) { - 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; - } - - // Create the message - String msg = e.getClass().getSimpleName() + ": " + e.getMessage(); - if (msg.length() > 90) { - msg = msg.substring(0, 80) + "..."; - } - - // Unknown Error - if (!(e instanceof Exception) && !(e instanceof LinkageError)) { - log.info("Showing Error dialog"); - JOptionPane.showMessageDialog(null, - new Object[] { - "An unknown Java error occurred:", - msg, - "You should immediately close unnecessary design windows,
" + - "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.", - "        " + msg + "", - " ", - "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, - 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. - */ - public static void registerExceptionHandler() { - - if (instance == null) { - instance = new ExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(instance); - - // Handler for modal dialogs of Sun's Java implementation - // See bug ID 4499199. - System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName()); - - reserveMemory(); - } - - } - - - /** - * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs. - */ - private static void reserveMemory() { - memoryReserve = new byte[MEMORY_RESERVE]; - 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 - * if the throwable or any of its causes is of the type OutOfMemoryError. - *

- * This method is required because Apple's JRE implementation sometimes - * masks OutOfMemoryErrors within RuntimeExceptions. Idiots. - * - * @param t the throwable to examine. - * @return whether this is an out-of-memory condition. - */ - private boolean isOutOfMemoryError(Throwable t) { - while (t != null) { - if (t instanceof OutOfMemoryError) - return true; - t = t.getCause(); - } - return false; - } - - - - /** - * 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 6826104 in Sun JRE. - */ - if (t instanceof NullPointerException) { - StackTraceElement[] trace = t.getStackTrace(); - - if (trace.length > 3 && - trace[0].getClassName().equals("sun.awt.X11.XWindowPeer") && - trace[0].getMethodName().equals("restoreTransientFor") && - - trace[1].getClassName().equals("sun.awt.X11.XWindowPeer") && - trace[1].getMethodName().equals("removeFromTransientFors") && - - trace[2].getClassName().equals("sun.awt.X11.XWindowPeer") && - trace[2].getMethodName().equals("setModalBlocked")) { - log.warn("Ignoring Sun JRE bug (6826104): http://bugs.sun.com/view_bug.do?bug_id=6826104" + t); - return true; - } - - } - - - /* - * 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(); - - 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 (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); - } - } -} diff --git a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java index 63248f53..c1080305 100644 --- a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java @@ -403,7 +403,7 @@ public class SimulationRunDialog extends JDialog { } else { - ExceptionHandler.handleErrorCondition("An exception occurred during the simulation", t); + Application.getExceptionHandler().handleErrorCondition("An exception occurred during the simulation", t); } simulationDone(); diff --git a/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java b/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java new file mode 100644 index 00000000..b930e79e --- /dev/null +++ b/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java @@ -0,0 +1,424 @@ +package net.sf.openrocket.gui.main; + +import javax.swing.JOptionPane; +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 SwingExceptionHandler implements Thread.UncaughtExceptionHandler, net.sf.openrocket.startup.ExceptionHandler { + + 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. + *

+ * This field is package-private so that the JRE cannot optimize its use away. + */ + volatile byte[] memoryReserve = null; + + private volatile boolean handling = false; + + + + + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + + // Free memory reserve if out of memory + 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; + } + + try { + handling = true; + + // Show on the EDT + if (SwingUtilities.isEventDispatchThread()) { + log.info("Exception handler running on EDT, showing dialog"); + showDialog(thread, throwable); + } else { + 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 (Exception ignore) { + } + + } finally { + // Mark handling as completed + handling = false; + } + + } + + + /** + * Handle an error condition programmatically without throwing an exception. + * This can be used in cases where recovery of the error is desirable. + *

+ * This method is guaranteed never to throw an exception, and can thus be safely + * used in finally blocks. + * + * @param message the error message. + */ + @Override + public 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. + *

+ * 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. + */ + @Override + public 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. + *

+ * This method is guaranteed never to throw an exception, and can thus be safely + * used in finally blocks. + * + * @param exception the exception that occurred. + */ + @Override + public void handleErrorCondition(final Throwable exception) { + try { + if (!(exception instanceof InternalException)) { + log.error(1, "Error occurred", exception); + } + final Thread thread = Thread.currentThread(); + + if (SwingUtilities.isEventDispatchThread()) { + log.info("Running in EDT, showing dialog"); + this.showDialog(thread, exception); + } else { + log.info("Not in EDT, invoking dialog later"); + final SwingExceptionHandler instance = this; + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + instance.showDialog(thread, exception); + } + }); + } + } catch (Exception e) { + log.error("Exception occurred in error handler", e); + } + } + + + /** + * The actual handling routine. + * + * @param t the thread that caused the exception, or null. + * @param e the exception. + */ + private void showDialog(Thread t, Throwable e) { + + // Out of memory + if (isOutOfMemoryError(e)) { + 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; + } + + // Create the message + String msg = e.getClass().getSimpleName() + ": " + e.getMessage(); + if (msg.length() > 90) { + msg = msg.substring(0, 80) + "..."; + } + + // Unknown Error + if (!(e instanceof Exception) && !(e instanceof LinkageError)) { + log.info("Showing Error dialog"); + JOptionPane.showMessageDialog(null, + new Object[] { + "An unknown Java error occurred:", + msg, + "You should immediately close unnecessary design windows,
" + + "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.", + "        " + msg + "", + " ", + "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, + 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. + */ + public void registerExceptionHandler() { + + Thread.setDefaultUncaughtExceptionHandler(this); + + // Handler for modal dialogs of Sun's Java implementation + // See bug ID 4499199. + System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName()); + + reserveMemory(); + + } + + + /** + * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs. + */ + private void reserveMemory() { + memoryReserve = new byte[MEMORY_RESERVE]; + 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 + * if the throwable or any of its causes is of the type OutOfMemoryError. + *

+ * This method is required because Apple's JRE implementation sometimes + * masks OutOfMemoryErrors within RuntimeExceptions. Idiots. + * + * @param t the throwable to examine. + * @return whether this is an out-of-memory condition. + */ + private boolean isOutOfMemoryError(Throwable t) { + while (t != null) { + if (t instanceof OutOfMemoryError) + return true; + t = t.getCause(); + } + return false; + } + + + + /** + * Handler used in modal dialogs by Sun Java implementation. + */ + public static class AwtHandler { + public void handle(Throwable t) { + Application.getExceptionHandler().uncaughtException(Thread.currentThread(), t); + } + } + + + /** + * Detect various non-fatal Sun JRE bugs. + * + * @param t the throwable + * @return whether this exception should be ignored + */ + private boolean isNonFatalJREBug(Throwable t) { + + // NOTE: Calling method logs the entire throwable, so log only message here + + + /* + * Detect and ignore bug 6826104 in Sun JRE. + */ + if (t instanceof NullPointerException) { + StackTraceElement[] trace = t.getStackTrace(); + + if (trace.length > 3 && + trace[0].getClassName().equals("sun.awt.X11.XWindowPeer") && + trace[0].getMethodName().equals("restoreTransientFor") && + + trace[1].getClassName().equals("sun.awt.X11.XWindowPeer") && + trace[1].getMethodName().equals("removeFromTransientFors") && + + trace[2].getClassName().equals("sun.awt.X11.XWindowPeer") && + trace[2].getMethodName().equals("setModalBlocked")) { + log.warn("Ignoring Sun JRE bug (6826104): http://bugs.sun.com/view_bug.do?bug_id=6826104" + t); + return true; + } + + } + + + /* + * 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(); + + 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 (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); + } + } +} diff --git a/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java index b998b83f..4855112b 100644 --- a/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java +++ b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java @@ -14,7 +14,6 @@ import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -177,7 +176,7 @@ public class ComponentTreeTransferHandler extends TransferHandler { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - ExceptionHandler.handleErrorCondition(e); + Application.getExceptionHandler().handleErrorCondition(e); } }); return false; diff --git a/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java b/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java index 3ed7741d..43096273 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java @@ -3,8 +3,8 @@ package net.sf.openrocket.gui.rocketfigure; import java.awt.Shape; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.gui.scalefigure.RocketFigure; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Transformation; @@ -18,7 +18,7 @@ public class RocketComponentShapes { public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation t) { // no-op - ExceptionHandler.handleErrorCondition("ERROR: RocketComponent.getShapesSide called with " + Application.getExceptionHandler().handleErrorCondition("ERROR: RocketComponent.getShapesSide called with " + component); return new Shape[0]; } @@ -26,7 +26,7 @@ public class RocketComponentShapes { public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation t) { // no-op - ExceptionHandler.handleErrorCondition("ERROR: RocketComponent.getShapesBack called with " + Application.getExceptionHandler().handleErrorCondition("ERROR: RocketComponent.getShapesBack called with " +component); return new Shape[0]; } diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index 8045f454..854eb17a 100644 --- a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -20,7 +20,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import net.sf.openrocket.gui.figureelements.FigureElement; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.gui.util.ColorConversion; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.motor.Motor; @@ -459,7 +458,7 @@ public class RocketFigure extends AbstractScaleFigure { } if (m == null) { - ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for " + Application.getExceptionHandler().handleErrorCondition("ERROR: Rocket figure paint method not found for " + component); return new Shape[0]; } diff --git a/src/net/sf/openrocket/gui/util/Icons.java b/src/net/sf/openrocket/gui/util/Icons.java index 8adf191d..5c76bf24 100644 --- a/src/net/sf/openrocket/gui/util/Icons.java +++ b/src/net/sf/openrocket/gui/util/Icons.java @@ -9,7 +9,6 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.startup.Application; @@ -94,7 +93,7 @@ public class Icons { URL url = ClassLoader.getSystemResource(file); if (url == null) { - ExceptionHandler.handleErrorCondition("Image file " + file + " not found, ignoring."); + Application.getExceptionHandler().handleErrorCondition("Image file " + file + " not found, ignoring."); return null; } return new ImageIcon(url, name); diff --git a/src/net/sf/openrocket/gui/util/OpenFileWorker.java b/src/net/sf/openrocket/gui/util/OpenFileWorker.java index a52692e5..1b186408 100644 --- a/src/net/sf/openrocket/gui/util/OpenFileWorker.java +++ b/src/net/sf/openrocket/gui/util/OpenFileWorker.java @@ -12,7 +12,6 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.RocketLoader; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; @@ -72,7 +71,7 @@ public class OpenFileWorker extends SwingWorker { try { is.close(); } catch (Exception e) { - ExceptionHandler.handleErrorCondition("Error closing file", e); + Application.getExceptionHandler().handleErrorCondition("Error closing file", e); } } } diff --git a/src/net/sf/openrocket/gui/util/SaveCSVWorker.java b/src/net/sf/openrocket/gui/util/SaveCSVWorker.java index 585a63ac..55ef4da7 100644 --- a/src/net/sf/openrocket/gui/util/SaveCSVWorker.java +++ b/src/net/sf/openrocket/gui/util/SaveCSVWorker.java @@ -13,9 +13,9 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.CSVExport; import net.sf.openrocket.gui.dialogs.SwingWorkerDialog; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.ProgressOutputStream; @@ -78,7 +78,7 @@ public class SaveCSVWorker extends SwingWorker { try { os.close(); } catch (Exception e) { - ExceptionHandler.handleErrorCondition("Error closing file", e); + Application.getExceptionHandler().handleErrorCondition("Error closing file", e); } } return null; diff --git a/src/net/sf/openrocket/gui/util/SaveFileWorker.java b/src/net/sf/openrocket/gui/util/SaveFileWorker.java index b9191c4a..9e5f4117 100644 --- a/src/net/sf/openrocket/gui/util/SaveFileWorker.java +++ b/src/net/sf/openrocket/gui/util/SaveFileWorker.java @@ -8,7 +8,7 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.RocketSaver; -import net.sf.openrocket.gui.main.ExceptionHandler; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.ProgressOutputStream; public class SaveFileWorker extends SwingWorker { @@ -48,7 +48,7 @@ public class SaveFileWorker extends SwingWorker { try { os.close(); } catch (Exception e) { - ExceptionHandler.handleErrorCondition("Error closing file", e); + Application.getExceptionHandler().handleErrorCondition("Error closing file", e); } } return null; diff --git a/src/net/sf/openrocket/gui/util/SwingPreferences.java b/src/net/sf/openrocket/gui/util/SwingPreferences.java index c1847a18..69e841e4 100644 --- a/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -6,7 +6,6 @@ import java.awt.Point; import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -16,17 +15,9 @@ import java.util.prefs.Preferences; import net.sf.openrocket.arch.SystemInfo; import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.material.Material; -import net.sf.openrocket.rocketcomponent.BodyComponent; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.InternalComponent; -import net.sf.openrocket.rocketcomponent.LaunchLug; -import net.sf.openrocket.rocketcomponent.MassObject; -import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.RK4SimulationStepper; import net.sf.openrocket.simulation.SimulationOptions; @@ -449,7 +440,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } } catch (BackingStoreException e) { - ExceptionHandler.handleErrorCondition(e); + Application.getExceptionHandler().handleErrorCondition(e); } } diff --git a/src/net/sf/openrocket/l10n/ExceptionSuppressingTranslator.java b/src/net/sf/openrocket/l10n/ExceptionSuppressingTranslator.java index 4cca766b..dd916b6c 100644 --- a/src/net/sf/openrocket/l10n/ExceptionSuppressingTranslator.java +++ b/src/net/sf/openrocket/l10n/ExceptionSuppressingTranslator.java @@ -3,7 +3,7 @@ package net.sf.openrocket.l10n; import java.util.Locale; import java.util.MissingResourceException; -import net.sf.openrocket.gui.main.ExceptionHandler; +import net.sf.openrocket.startup.Application; /** * A translator that suppresses MissingResourceExceptions and handles them gracefully. @@ -46,7 +46,7 @@ public class ExceptionSuppressingTranslator implements Translator { private static synchronized void handleError(String key, MissingResourceException e) { if (!errorReported) { errorReported = true; - ExceptionHandler.handleErrorCondition("Can not find translation for '" + key + "' locale=" + Locale.getDefault(), e); + Application.getExceptionHandler().handleErrorCondition("Can not find translation for '" + key + "' locale=" + Locale.getDefault(), e); } } diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index 62d2c080..226f9527 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -9,7 +9,6 @@ import java.util.LinkedList; import java.util.List; import java.util.UUID; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.motor.Motor; @@ -459,7 +458,7 @@ public class Rocket extends RocketComponent { freezeList = new LinkedList(); log.debug("Freezing Rocket"); } else { - ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " + + Application.getExceptionHandler().handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " + "freezeList=" + freezeList); } } @@ -474,7 +473,7 @@ public class Rocket extends RocketComponent { public void thaw() { checkState(); if (freezeList == null) { - ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen"); + Application.getExceptionHandler().handleErrorCondition("Attempting to thaw Rocket when it is not frozen"); return; } if (freezeList.size() == 0) { diff --git a/src/net/sf/openrocket/startup/Application.java b/src/net/sf/openrocket/startup/Application.java index 863776f7..da80895a 100644 --- a/src/net/sf/openrocket/startup/Application.java +++ b/src/net/sf/openrocket/startup/Application.java @@ -1,6 +1,7 @@ package net.sf.openrocket.startup; import net.sf.openrocket.database.ThrustCurveMotorSetDatabase; +import net.sf.openrocket.gui.main.SwingExceptionHandler; import net.sf.openrocket.l10n.ClassBasedTranslator; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.ExceptionSuppressingTranslator; @@ -25,6 +26,8 @@ public final class Application { private static ThrustCurveMotorSetDatabase motorSetDatabase; private static Preferences preferences; + + private static SwingExceptionHandler exceptionHandler; // Initialize the logger to something sane for testing without executing Startup static { @@ -130,6 +133,20 @@ public final class Application { Application.preferences = preferences; } + /** + * @return the exceptionHandler + */ + public static SwingExceptionHandler getExceptionHandler() { + return exceptionHandler; + } + + /** + * @param exceptionHandler the exceptionHandler to set + */ + public static void setExceptionHandler(SwingExceptionHandler exceptionHandler) { + Application.exceptionHandler = exceptionHandler; + } + /** * Return the database of all thrust curves loaded into the system. */ diff --git a/src/net/sf/openrocket/startup/ExceptionHandler.java b/src/net/sf/openrocket/startup/ExceptionHandler.java new file mode 100644 index 00000000..fa115322 --- /dev/null +++ b/src/net/sf/openrocket/startup/ExceptionHandler.java @@ -0,0 +1,12 @@ +package net.sf.openrocket.startup; + +public interface ExceptionHandler { + + public void handleErrorCondition(String message); + public void handleErrorCondition(String message, Throwable exception); + public void handleErrorCondition(final Throwable exception); + + + public void uncaughtException(final Thread thread, final Throwable throwable); + +} diff --git a/src/net/sf/openrocket/startup/Startup2.java b/src/net/sf/openrocket/startup/Startup2.java index 3c4f1dab..11ceb190 100644 --- a/src/net/sf/openrocket/startup/Startup2.java +++ b/src/net/sf/openrocket/startup/Startup2.java @@ -21,11 +21,11 @@ import net.sf.openrocket.file.iterator.FileIterator; import net.sf.openrocket.file.motor.MotorLoaderHelper; import net.sf.openrocket.gui.dialogs.UpdateInfoDialog; import net.sf.openrocket.gui.main.BasicFrame; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.gui.main.Splash; +import net.sf.openrocket.gui.main.SwingExceptionHandler; import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.gui.util.SimpleFileFilter; +import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; @@ -92,7 +92,9 @@ public class Startup2 { // Setup the uncaught exception handler log.info("Registering exception handler"); - ExceptionHandler.registerExceptionHandler(); + SwingExceptionHandler exceptionHandler = new SwingExceptionHandler(); + Application.setExceptionHandler(exceptionHandler); + exceptionHandler.registerExceptionHandler(); // Start update info fetching final UpdateInfoRetriever updateInfo; diff --git a/src/net/sf/openrocket/util/SafetyMutex.java b/src/net/sf/openrocket/util/SafetyMutex.java index 1e8267eb..646c54e4 100644 --- a/src/net/sf/openrocket/util/SafetyMutex.java +++ b/src/net/sf/openrocket/util/SafetyMutex.java @@ -2,7 +2,6 @@ package net.sf.openrocket.util; import java.util.LinkedList; -import net.sf.openrocket.gui.main.ExceptionHandler; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.logging.TraceException; import net.sf.openrocket.startup.Application; @@ -148,7 +147,7 @@ public abstract class SafetyMutex { try { if (location == null) { - ExceptionHandler.handleErrorCondition("location is null"); + Application.getExceptionHandler().handleErrorCondition("location is null"); location = ""; } checkState(false); @@ -181,7 +180,7 @@ public abstract class SafetyMutex { } return true; } catch (Exception e) { - ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " + + Application.getExceptionHandler().handleErrorCondition("An exception occurred while unlocking a mutex, " + "locking thread=" + lockingThread + " locations=" + locations, e); return false; } @@ -225,7 +224,7 @@ public abstract class SafetyMutex { if (!errorReported) { errorReported = true; - ExceptionHandler.handleErrorCondition(ex); + Application.getExceptionHandler().handleErrorCondition(ex); } else { log.error(message, ex); }