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.logging.TraceException;
9 import net.sf.openrocket.startup.Application;
10 import net.sf.openrocket.startup.ExceptionHandler;
13 public class SwingExceptionHandler implements Thread.UncaughtExceptionHandler, ExceptionHandler {
15 private static final LogHelper log = Application.getLogger();
17 private static final int MEMORY_RESERVE = 512 * 1024;
20 * A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
22 * This field is package-private so that the JRE cannot optimize its use away.
24 volatile byte[] memoryReserve = null;
26 private volatile boolean handling = false;
32 public void uncaughtException(final Thread thread, final Throwable throwable) {
34 // Free memory reserve if out of memory
35 if (isOutOfMemoryError(throwable)) {
38 log.error("Out of memory error detected", throwable);
41 if (isNonFatalJREBug(throwable)) {
42 log.warn("Ignoring non-fatal JRE bug", throwable);
46 log.error("Handling uncaught exception on thread=" + thread, throwable);
47 throwable.printStackTrace();
50 log.warn("Exception is currently being handled, ignoring");
58 if (SwingUtilities.isEventDispatchThread()) {
59 log.info("Exception handler running on EDT, showing dialog");
60 showDialog(thread, throwable);
62 log.info("Exception handler not on EDT, invoking dialog on EDT");
63 SwingUtilities.invokeAndWait(new Runnable() {
66 showDialog(thread, throwable);
71 } catch (Throwable ex) {
73 // Make sure the handler does not throw any exceptions
75 log.error("Caught exception while handling exception", ex);
76 System.err.println("Exception in exception handler, dumping exception:");
78 } catch (Exception ignore) {
82 // Mark handling as completed
90 * Handle an error condition programmatically without throwing an exception.
91 * This can be used in cases where recovery of the error is desirable.
93 * This method is guaranteed never to throw an exception, and can thus be safely
94 * used in finally blocks.
96 * @param message the error message.
99 public void handleErrorCondition(String message) {
100 log.error(1, message, new TraceException());
101 handleErrorCondition(new InternalException(message));
106 * Handle an error condition programmatically without throwing an exception.
107 * This can be used in cases where recovery of the error is desirable.
109 * This method is guaranteed never to throw an exception, and can thus be safely
110 * used in finally blocks.
112 * @param message the error message.
113 * @param exception the exception that occurred.
116 public void handleErrorCondition(String message, Throwable exception) {
117 log.error(1, message, exception);
118 handleErrorCondition(new InternalException(message, exception));
123 * Handle an error condition programmatically without throwing an exception.
124 * This can be used in cases where recovery of the error is desirable.
126 * This method is guaranteed never to throw an exception, and can thus be safely
127 * used in finally blocks.
129 * @param exception the exception that occurred.
132 public void handleErrorCondition(final Throwable exception) {
134 if (!(exception instanceof InternalException)) {
135 log.error(1, "Error occurred", exception);
137 final Thread thread = Thread.currentThread();
139 if (SwingUtilities.isEventDispatchThread()) {
140 log.info("Running in EDT, showing dialog");
141 this.showDialog(thread, exception);
143 log.info("Not in EDT, invoking dialog later");
144 final SwingExceptionHandler instance = this;
145 SwingUtilities.invokeLater(new Runnable() {
148 instance.showDialog(thread, exception);
152 } catch (Exception e) {
153 log.error("Exception occurred in error handler", e);
159 * The actual handling routine.
161 * @param t the thread that caused the exception, or <code>null</code>.
162 * @param e the exception.
164 private void showDialog(Thread t, Throwable e) {
167 if (isOutOfMemoryError(e)) {
168 log.info("Showing out-of-memory dialog");
169 JOptionPane.showMessageDialog(null,
171 "OpenRocket is out of available memory!",
172 "You should immediately close unnecessary design windows,",
173 "save any unsaved designs and restart OpenRocket!"
174 }, "Out of memory", JOptionPane.ERROR_MESSAGE);
178 // Create the message
179 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
180 if (msg.length() > 90) {
181 msg = msg.substring(0, 80) + "...";
185 if (!(e instanceof Exception) && !(e instanceof LinkageError)) {
186 log.info("Showing Error dialog");
187 JOptionPane.showMessageDialog(null,
189 "An unknown Java error occurred:",
191 "<html>You should immediately close unnecessary design windows,<br>" +
192 "save any unsaved designs and restart OpenRocket!"
193 }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
198 // Normal exception, show question dialog
199 log.info("Showing Exception dialog");
200 int selection = JOptionPane.showOptionDialog(null, new Object[] {
201 "OpenRocket encountered an uncaught exception. This typically signifies " +
202 "a bug in the software.",
203 "<html><em> " + msg + "</em>",
205 "Please take a moment to report this bug to the developers.",
206 "This can be done automatically if you have an Internet connection."
207 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
208 JOptionPane.ERROR_MESSAGE, null,
209 new Object[] { "View bug report", "Close" }, "View bug report");
211 if (selection != 0) {
213 log.user("User chose not to fill bug report");
217 // Show bug report dialog
218 log.user("User requested sending bug report");
219 BugReportDialog.showExceptionDialog(null, t, e);
225 * Registers the uncaught exception handler. This should be used to ensure that
226 * all necessary registrations are performed.
228 public void registerExceptionHandler() {
230 Thread.setDefaultUncaughtExceptionHandler(this);
232 // Handler for modal dialogs of Sun's Java implementation
233 // See bug ID 4499199.
234 System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
242 * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
244 private void reserveMemory() {
245 memoryReserve = new byte[MEMORY_RESERVE];
246 for (int i = 0; i < MEMORY_RESERVE; i++) {
247 memoryReserve[i] = (byte) i;
254 * Return whether this throwable was caused by an OutOfMemoryError
255 * condition. An exception is deemed to be caused by OutOfMemoryError
256 * if the throwable or any of its causes is of the type OutOfMemoryError.
258 * This method is required because Apple's JRE implementation sometimes
259 * masks OutOfMemoryErrors within RuntimeExceptions. Idiots.
261 * @param t the throwable to examine.
262 * @return whether this is an out-of-memory condition.
264 private boolean isOutOfMemoryError(Throwable t) {
266 if (t instanceof OutOfMemoryError)
276 * Handler used in modal dialogs by Sun Java implementation.
278 public static class AwtHandler {
279 public void handle(Throwable t) {
280 Application.getExceptionHandler().uncaughtException(Thread.currentThread(), t);
286 * Detect various non-fatal Sun JRE bugs.
288 * @param t the throwable
289 * @return whether this exception should be ignored
291 private boolean isNonFatalJREBug(Throwable t) {
293 // NOTE: Calling method logs the entire throwable, so log only message here
297 * Detect and ignore bug 6826104 in Sun JRE.
299 if (t instanceof NullPointerException) {
300 StackTraceElement[] trace = t.getStackTrace();
302 if (trace.length > 3 &&
303 trace[0].getClassName().equals("sun.awt.X11.XWindowPeer") &&
304 trace[0].getMethodName().equals("restoreTransientFor") &&
306 trace[1].getClassName().equals("sun.awt.X11.XWindowPeer") &&
307 trace[1].getMethodName().equals("removeFromTransientFors") &&
309 trace[2].getClassName().equals("sun.awt.X11.XWindowPeer") &&
310 trace[2].getMethodName().equals("setModalBlocked")) {
311 log.warn("Ignoring Sun JRE bug (6826104): http://bugs.sun.com/view_bug.do?bug_id=6826104" + t);
319 * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
321 if (t instanceof ArrayIndexOutOfBoundsException) {
322 final String buggyClass = "sun.font.FontDesignMetrics";
323 StackTraceElement[] elements = t.getStackTrace();
324 if (elements.length >= 3 &&
325 (buggyClass.equals(elements[0].getClassName()) ||
326 buggyClass.equals(elements[1].getClassName()) ||
327 buggyClass.equals(elements[2].getClassName()))) {
328 log.warn("Ignoring Sun JRE bug 6828938: " +
329 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
335 * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
337 if (t instanceof NullPointerException) {
338 StackTraceElement[] trace = t.getStackTrace();
340 if (trace.length > 3 &&
341 trace[0].getClassName().equals("javax.swing.JComponent") &&
342 trace[0].getMethodName().equals("repaint") &&
344 trace[1].getClassName().equals("sun.swing.FilePane$2") &&
345 trace[1].getMethodName().equals("repaintListSelection") &&
347 trace[2].getClassName().equals("sun.swing.FilePane$2") &&
348 trace[2].getMethodName().equals("repaintSelection")) {
349 log.warn("Ignoring Sun JRE bug 6561072 " +
350 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
357 * Detect and ignore bug 6933331 in Sun JRE 1.6.0_18 and others
359 if (t instanceof IllegalStateException) {
360 StackTraceElement[] trace = t.getStackTrace();
362 if (trace.length > 1 &&
363 trace[0].getClassName().equals("sun.awt.windows.WComponentPeer") &&
364 trace[0].getMethodName().equals("getBackBuffer")) {
365 log.warn("Ignoring Sun JRE bug 6933331 " +
366 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933331): " + t);
372 * Detect and ignore bug in Sun JRE 1.6.0_19
374 if (t instanceof NullPointerException) {
375 StackTraceElement[] trace = t.getStackTrace();
377 if (trace.length > 3 &&
378 trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
379 trace[0].getMethodName().equals("pidlsEqual") &&
381 trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
382 trace[1].getMethodName().equals("equals") &&
384 trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
385 trace[2].getMethodName().equals("isFileSystemRoot")) {
386 log.warn("Ignoring Sun JRE bug " +
387 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
393 * Detect Sun JRE bug in D3D
395 if (t instanceof ClassCastException) {
396 if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
397 log.warn("Ignoring Sun JRE bug " +
398 "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
407 @SuppressWarnings("unused")
408 private static class InternalException extends Exception {
409 public InternalException() {
413 public InternalException(String message, Throwable cause) {
414 super(message, cause);
417 public InternalException(String message) {
421 public InternalException(Throwable cause) {