1 package net.sf.openrocket.gui.main;
3 import javax.swing.JOptionPane;
4 import javax.swing.SwingUtilities;
6 import net.sf.openrocket.gui.dialogs.BugReportDialog;
7 import net.sf.openrocket.logging.LogHelper;
8 import net.sf.openrocket.startup.Application;
9 import net.sf.openrocket.util.BugException;
12 public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
14 private static final LogHelper log = Application.getLogger();
16 private static final int MEMORY_RESERVE = 512 * 1024;
19 * A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
21 * This field is package-private so that the JRE cannot optimize its use away.
23 static volatile byte[] memoryReserve = null;
25 private static ExceptionHandler instance = null;
28 private volatile boolean handling = false;
34 public void uncaughtException(final Thread thread, final Throwable throwable) {
36 // Free memory reserve if out of memory
37 if (isOutOfMemoryError(throwable)) {
40 log.error("Out of memory error detected", throwable);
43 if (isNonFatalJREBug(throwable)) {
44 log.warn("Ignoring non-fatal JRE bug", throwable);
48 log.error("Handling uncaught exception on thread=" + thread, throwable);
49 throwable.printStackTrace();
52 log.warn("Exception is currently being handled, ignoring");
60 if (SwingUtilities.isEventDispatchThread()) {
61 log.info("Exception handler running on EDT, showing dialog");
62 showDialog(thread, throwable);
64 log.info("Exception handler not on EDT, invoking dialog on EDT");
65 SwingUtilities.invokeAndWait(new Runnable() {
67 showDialog(thread, throwable);
72 } catch (Throwable ex) {
74 // Make sure the handler does not throw any exceptions
76 log.error("Caught exception while handling exception", ex);
77 System.err.println("Exception in exception handler, dumping exception:");
79 } catch (Throwable ignore) {
83 // Mark handling as completed
91 * Handle an error condition programmatically without throwing an exception.
92 * This can be used in cases where recovery of the error is desirable.
94 * @param message the error message.
96 public static void handleErrorCondition(String message) {
97 log.error(1, message);
98 handleErrorCondition(new InternalException(message));
103 * Handle an error condition programmatically without throwing an exception.
104 * This can be used in cases where recovery of the error is desirable.
106 * @param message the error message.
107 * @param exception the exception that occurred.
109 public static void handleErrorCondition(String message, Exception exception) {
110 log.error(1, message, exception);
111 handleErrorCondition(new InternalException(message, exception));
116 * Handle an error condition programmatically without throwing an exception.
117 * This can be used in cases where recovery of the error is desirable.
119 * @param exception the exception that occurred.
121 public static void handleErrorCondition(final Exception exception) {
122 if (!(exception instanceof InternalException)) {
123 log.error(1, "Error occurred", exception);
125 final Thread thread = Thread.currentThread();
126 final ExceptionHandler handler = instance;
128 if (handler == null) {
129 // Not initialized, throw the exception
130 throw new BugException("Error condition before exception handling has been initialized", exception);
134 if (SwingUtilities.isEventDispatchThread()) {
135 log.info("Running in EDT, showing dialog");
136 handler.showDialog(thread, exception);
138 log.info("Not in EDT, invoking and waiting for dialog");
139 SwingUtilities.invokeAndWait(new Runnable() {
141 handler.showDialog(thread, exception);
145 } catch (Exception e) {
146 log.error("Exception occurred while showing error dialog", e);
153 * The actual handling routine.
155 * @param t the thread that caused the exception, or <code>null</code>.
156 * @param e the exception.
158 private void showDialog(Thread t, Throwable e) {
161 if (isOutOfMemoryError(e)) {
162 log.info("Showing out-of-memory dialog");
163 JOptionPane.showMessageDialog(null,
165 "OpenRocket is out of available memory!",
166 "You should immediately close unnecessary design windows,",
167 "save any unsaved designs and restart OpenRocket!"
168 }, "Out of memory", JOptionPane.ERROR_MESSAGE);
172 // Create the message
173 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
174 if (msg.length() > 90) {
175 msg = msg.substring(0, 80) + "...";
179 if (!(e instanceof Exception)) {
180 log.info("Showing Error dialog");
181 JOptionPane.showMessageDialog(null,
183 "An unknown Java error occurred:",
185 "<html>You should immediately close unnecessary design windows,<br>" +
186 "save any unsaved designs and restart OpenRocket!"
187 }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
192 // Normal exception, show question dialog
193 log.info("Showing Exception dialog");
194 int selection = JOptionPane.showOptionDialog(null, new Object[] {
195 "OpenRocket encountered an uncaught exception. This typically signifies " +
196 "a bug in the software.",
197 "<html><em> " + msg + "</em>",
199 "Please take a moment to report this bug to the developers.",
200 "This can be done automatically if you have an Internet connection."
201 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
202 JOptionPane.ERROR_MESSAGE, null,
203 new Object[] { "View bug report", "Close" }, "View bug report");
205 if (selection != 0) {
207 log.user("User chose not to fill bug report");
211 // Show bug report dialog
212 log.user("User requested sending bug report");
213 BugReportDialog.showExceptionDialog(null, t, e);
219 * Registers the uncaught exception handler. This should be used to ensure that
220 * all necessary registrations are performed.
222 public static void registerExceptionHandler() {
224 if (instance == null) {
225 instance = new ExceptionHandler();
226 Thread.setDefaultUncaughtExceptionHandler(instance);
228 // Handler for modal dialogs of Sun's Java implementation
229 // See bug ID 4499199.
230 System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
239 * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
241 private static void reserveMemory() {
242 memoryReserve = new byte[MEMORY_RESERVE];
243 for (int i = 0; i < MEMORY_RESERVE; i++) {
244 memoryReserve[i] = (byte) i;
251 * Return whether this throwable was caused by an OutOfMemoryError
252 * condition. An exception is deemed to be caused by OutOfMemoryError
253 * if the throwable or any of its causes is of the type OutOfMemoryError.
255 * This method is required because Apple's JRE implementation sometimes
256 * masks OutOfMemoryErrors within RuntimeExceptions. Idiots.
258 * @param t the throwable to examine.
259 * @return whether this is an out-of-memory condition.
261 private boolean isOutOfMemoryError(Throwable t) {
263 if (t instanceof OutOfMemoryError)
273 * Handler used in modal dialogs by Sun Java implementation.
275 public static class AwtHandler {
276 public void handle(Throwable t) {
277 if (instance != null) {
278 instance.uncaughtException(Thread.currentThread(), t);
285 * Detect various non-fatal Sun JRE bugs.
287 * @param t the throwable
288 * @return whether this exception should be ignored
290 private static boolean isNonFatalJREBug(Throwable t) {
293 * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
295 if (t instanceof ArrayIndexOutOfBoundsException) {
296 final String buggyClass = "sun.font.FontDesignMetrics";
297 StackTraceElement[] elements = t.getStackTrace();
298 if (elements.length >= 3 &&
299 (buggyClass.equals(elements[0].getClassName()) ||
300 buggyClass.equals(elements[1].getClassName()) ||
301 buggyClass.equals(elements[2].getClassName()))) {
302 log.warn("Ignoring Sun JRE bug 6828938: " +
303 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
309 * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
311 if (t instanceof NullPointerException) {
312 StackTraceElement[] trace = t.getStackTrace();
314 if (trace.length > 3 &&
315 trace[0].getClassName().equals("javax.swing.JComponent") &&
316 trace[0].getMethodName().equals("repaint") &&
318 trace[1].getClassName().equals("sun.swing.FilePane$2") &&
319 trace[1].getMethodName().equals("repaintListSelection") &&
321 trace[2].getClassName().equals("sun.swing.FilePane$2") &&
322 trace[2].getMethodName().equals("repaintSelection")) {
323 log.warn("Ignoring Sun JRE bug 6561072 " +
324 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
330 * Detect and ignore bug in Sun JRE 1.6.0_19
332 if (t instanceof NullPointerException) {
333 StackTraceElement[] trace = t.getStackTrace();
335 if (trace.length > 3 &&
336 trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
337 trace[0].getMethodName().equals("pidlsEqual") &&
339 trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
340 trace[1].getMethodName().equals("equals") &&
342 trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
343 trace[2].getMethodName().equals("isFileSystemRoot")) {
344 log.warn("Ignoring Sun JRE bug " +
345 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
353 @SuppressWarnings("unused")
354 private static class InternalException extends Exception {
355 public InternalException() {
359 public InternalException(String message, Throwable cause) {
360 super(message, cause);
363 public InternalException(String message) {
367 public InternalException(Throwable cause) {