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;
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() {
68 showDialog(thread, throwable);
73 } catch (Throwable ex) {
75 // Make sure the handler does not throw any exceptions
77 log.error("Caught exception while handling exception", ex);
78 System.err.println("Exception in exception handler, dumping exception:");
80 } catch (Exception ignore) {
84 // Mark handling as completed
92 * Handle an error condition programmatically without throwing an exception.
93 * This can be used in cases where recovery of the error is desirable.
95 * This method is guaranteed never to throw an exception, and can thus be safely
96 * used in finally blocks.
98 * @param message the error message.
100 public static void handleErrorCondition(String message) {
101 log.error(1, message, new TraceException());
102 handleErrorCondition(new InternalException(message));
107 * Handle an error condition programmatically without throwing an exception.
108 * This can be used in cases where recovery of the error is desirable.
110 * This method is guaranteed never to throw an exception, and can thus be safely
111 * used in finally blocks.
113 * @param message the error message.
114 * @param exception the exception that occurred.
116 public static void handleErrorCondition(String message, Exception 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.
131 public static void handleErrorCondition(final Exception exception) {
133 if (!(exception instanceof InternalException)) {
134 log.error(1, "Error occurred", exception);
136 final Thread thread = Thread.currentThread();
137 final ExceptionHandler handler = instance;
139 if (handler == null) {
140 log.error("Error condition occurred before exception handling has been initialized", exception);
144 if (SwingUtilities.isEventDispatchThread()) {
145 log.info("Running in EDT, showing dialog");
146 handler.showDialog(thread, exception);
148 log.info("Not in EDT, invoking and waiting for dialog");
149 SwingUtilities.invokeAndWait(new Runnable() {
152 handler.showDialog(thread, exception);
156 } catch (Exception e) {
157 log.error("Exception occurred in error handler", e);
163 * The actual handling routine.
165 * @param t the thread that caused the exception, or <code>null</code>.
166 * @param e the exception.
168 private void showDialog(Thread t, Throwable e) {
171 if (isOutOfMemoryError(e)) {
172 log.info("Showing out-of-memory dialog");
173 JOptionPane.showMessageDialog(null,
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);
182 // Create the message
183 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
184 if (msg.length() > 90) {
185 msg = msg.substring(0, 80) + "...";
189 if (!(e instanceof Exception)) {
190 log.info("Showing Error dialog");
191 JOptionPane.showMessageDialog(null,
193 "An unknown Java error occurred:",
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);
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> " + msg + "</em>",
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");
215 if (selection != 0) {
217 log.user("User chose not to fill bug report");
221 // Show bug report dialog
222 log.user("User requested sending bug report");
223 BugReportDialog.showExceptionDialog(null, t, e);
229 * Registers the uncaught exception handler. This should be used to ensure that
230 * all necessary registrations are performed.
232 public static void registerExceptionHandler() {
234 if (instance == null) {
235 instance = new ExceptionHandler();
236 Thread.setDefaultUncaughtExceptionHandler(instance);
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());
249 * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
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;
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.
265 * This method is required because Apple's JRE implementation sometimes
266 * masks OutOfMemoryErrors within RuntimeExceptions. Idiots.
268 * @param t the throwable to examine.
269 * @return whether this is an out-of-memory condition.
271 private boolean isOutOfMemoryError(Throwable t) {
273 if (t instanceof OutOfMemoryError)
283 * Handler used in modal dialogs by Sun Java implementation.
285 public static class AwtHandler {
286 public void handle(Throwable t) {
287 if (instance != null) {
288 instance.uncaughtException(Thread.currentThread(), t);
295 * Detect various non-fatal Sun JRE bugs.
297 * @param t the throwable
298 * @return whether this exception should be ignored
300 private static boolean isNonFatalJREBug(Throwable t) {
302 // NOTE: Calling method logs the entire throwable, so log only message here
305 * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
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);
321 * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
323 if (t instanceof NullPointerException) {
324 StackTraceElement[] trace = t.getStackTrace();
326 if (trace.length > 3 &&
327 trace[0].getClassName().equals("javax.swing.JComponent") &&
328 trace[0].getMethodName().equals("repaint") &&
330 trace[1].getClassName().equals("sun.swing.FilePane$2") &&
331 trace[1].getMethodName().equals("repaintListSelection") &&
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);
342 * Detect and ignore bug in Sun JRE 1.6.0_19
344 if (t instanceof NullPointerException) {
345 StackTraceElement[] trace = t.getStackTrace();
347 if (trace.length > 3 &&
348 trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
349 trace[0].getMethodName().equals("pidlsEqual") &&
351 trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
352 trace[1].getMethodName().equals("equals") &&
354 trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
355 trace[2].getMethodName().equals("isFileSystemRoot")) {
356 log.warn("Ignoring Sun JRE bug " +
357 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
363 * Detect Sun JRE bug in D3D
365 if (t instanceof ClassCastException) {
366 if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
367 log.warn("Ignoring Sun JRE bug " +
368 "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
377 @SuppressWarnings("unused")
378 private static class InternalException extends Exception {
379 public InternalException() {
383 public InternalException(String message, Throwable cause) {
384 super(message, cause);
387 public InternalException(String message) {
391 public InternalException(Throwable cause) {