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;
11 public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
13 private static final LogHelper log = Application.getLogger();
15 private static final int MEMORY_RESERVE = 512 * 1024;
18 * A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
20 * This field is package-private so that the JRE cannot optimize its use away.
22 static volatile byte[] memoryReserve = null;
24 private static ExceptionHandler instance = null;
27 private volatile boolean handling = false;
33 public void uncaughtException(final Thread thread, final Throwable throwable) {
35 // Free memory reserve if out of memory
36 if (isOutOfMemoryError(throwable)) {
39 log.warn("Out of memory error detected", throwable);
42 if (isNonFatalJREBug(throwable)) {
43 log.warn("Ignoring non-fatal JRE bug", throwable);
47 log.warn("Handling uncaught exception on thread=" + thread, throwable);
48 throwable.printStackTrace();
51 log.warn("Exception is currently being handled, ignoring");
59 if (SwingUtilities.isEventDispatchThread()) {
60 log.info("Exception handler running on EDT, showing dialog");
61 showDialog(thread, throwable);
63 log.info("Exception handler not on EDT, invoking dialog on EDT");
64 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 (Throwable 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 * @param message the error message.
95 public static void handleErrorCondition(String message) {
96 log.error(1, message);
97 handleErrorCondition(new InternalException(message));
102 * Handle an error condition programmatically without throwing an exception.
103 * This can be used in cases where recovery of the error is desirable.
105 * @param message the error message.
106 * @param exception the exception that occurred.
108 public static void handleErrorCondition(String message, Exception exception) {
109 log.error(1, message, exception);
110 handleErrorCondition(new InternalException(message, exception));
115 * Handle an error condition programmatically without throwing an exception.
116 * This can be used in cases where recovery of the error is desirable.
118 * @param exception the exception that occurred.
120 public static void handleErrorCondition(final Exception exception) {
121 if (!(exception instanceof InternalException)) {
122 log.error(1, "Error occurred", exception);
124 final ExceptionHandler handler = instance;
125 final Thread thread = Thread.currentThread();
129 if (handler == null) {
130 // Not initialized, simply print the exception
131 log.error("Exception handler not initialized");
132 exception.printStackTrace();
136 if (SwingUtilities.isEventDispatchThread()) {
137 log.info("Running in EDT, showing dialog");
138 handler.showDialog(thread, exception);
140 log.info("Not in EDT, invoking and waiting for dialog");
141 SwingUtilities.invokeAndWait(new Runnable() {
143 handler.showDialog(thread, exception);
147 } catch (Exception e) {
148 log.error("Exception occurred while showing error dialog", e);
155 * The actual handling routine.
157 * @param t the thread that caused the exception, or <code>null</code>.
158 * @param e the exception.
160 private void showDialog(Thread t, Throwable e) {
163 if (isOutOfMemoryError(e)) {
164 log.info("Showing out-of-memory dialog");
165 JOptionPane.showMessageDialog(null,
167 "OpenRocket is out of available memory!",
168 "You should immediately close unnecessary design windows,",
169 "save any unsaved designs and restart OpenRocket!"
170 }, "Out of memory", JOptionPane.ERROR_MESSAGE);
174 // Create the message
175 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
176 if (msg.length() > 90) {
177 msg = msg.substring(0, 80) + "...";
181 if (!(e instanceof Exception)) {
182 log.info("Showing Error dialog");
183 JOptionPane.showMessageDialog(null,
185 "An unknown Java error occurred:",
187 "<html>You should immediately close unnecessary design windows,<br>" +
188 "save any unsaved designs and restart OpenRocket!"
189 }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
194 // Normal exception, show question dialog
195 log.info("Showing Exception dialog");
196 int selection = JOptionPane.showOptionDialog(null, new Object[] {
197 "OpenRocket encountered an uncaught exception. This typically signifies " +
198 "a bug in the software.",
199 "<html><em> " + msg + "</em>",
201 "Please take a moment to report this bug to the developers.",
202 "This can be done automatically if you have an Internet connection."
203 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
204 JOptionPane.ERROR_MESSAGE, null,
205 new Object[] { "View bug report", "Close" }, "View bug report");
207 if (selection != 0) {
209 log.user("User chose not to fill bug report");
213 // Show bug report dialog
214 log.user("User requested sending bug report");
215 BugReportDialog.showExceptionDialog(null, t, e);
221 * Registers the uncaught exception handler. This should be used to ensure that
222 * all necessary registrations are performed.
224 public static void registerExceptionHandler() {
226 if (instance == null) {
227 instance = new ExceptionHandler();
228 Thread.setDefaultUncaughtExceptionHandler(instance);
230 // Handler for modal dialogs of Sun's Java implementation
231 // See bug ID 4499199.
232 System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
241 * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
243 private static void reserveMemory() {
244 memoryReserve = new byte[MEMORY_RESERVE];
245 for (int i = 0; i < MEMORY_RESERVE; i++) {
246 memoryReserve[i] = (byte) i;
253 * Return whether this throwable was caused by an OutOfMemoryError
254 * condition. An exception is deemed to be caused by OutOfMemoryError
255 * if the throwable or any of its causes is of the type OutOfMemoryError.
257 * This method is required because Apple's JRE implementation sometimes
258 * masks OutOfMemoryErrors within RuntimeExceptions. Idiots.
260 * @param t the throwable to examine.
261 * @return whether this is an out-of-memory condition.
263 private boolean isOutOfMemoryError(Throwable t) {
265 if (t instanceof OutOfMemoryError)
275 * Handler used in modal dialogs by Sun Java implementation.
277 public static class AwtHandler {
278 public void handle(Throwable t) {
279 if (instance != null) {
280 instance.uncaughtException(Thread.currentThread(), t);
287 * Detect various non-fatal Sun JRE bugs.
289 * @param t the throwable
290 * @return whether this exception should be ignored
292 private static boolean isNonFatalJREBug(Throwable t) {
295 * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
297 if (t instanceof ArrayIndexOutOfBoundsException) {
298 final String buggyClass = "sun.font.FontDesignMetrics";
299 StackTraceElement[] elements = t.getStackTrace();
300 if (elements.length >= 3 &&
301 (buggyClass.equals(elements[0].getClassName()) ||
302 buggyClass.equals(elements[1].getClassName()) ||
303 buggyClass.equals(elements[2].getClassName()))) {
304 log.warn("Ignoring Sun JRE bug 6828938: " +
305 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6828938): " + t);
311 * Detect and ignore bug 6561072 in Sun JRE 1.6.0_?
313 if (t instanceof NullPointerException) {
314 StackTraceElement[] trace = t.getStackTrace();
316 if (trace.length > 3 &&
317 trace[0].getClassName().equals("javax.swing.JComponent") &&
318 trace[0].getMethodName().equals("repaint") &&
320 trace[1].getClassName().equals("sun.swing.FilePane$2") &&
321 trace[1].getMethodName().equals("repaintListSelection") &&
323 trace[2].getClassName().equals("sun.swing.FilePane$2") &&
324 trace[2].getMethodName().equals("repaintSelection")) {
325 log.warn("Ignoring Sun JRE bug 6561072 " +
326 "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6561072): " + t);
332 * Detect and ignore bug in Sun JRE 1.6.0_19
334 if (t instanceof NullPointerException) {
335 StackTraceElement[] trace = t.getStackTrace();
337 if (trace.length > 3 &&
338 trace[0].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
339 trace[0].getMethodName().equals("pidlsEqual") &&
341 trace[1].getClassName().equals("sun.awt.shell.Win32ShellFolder2") &&
342 trace[1].getMethodName().equals("equals") &&
344 trace[2].getClassName().equals("sun.awt.shell.Win32ShellFolderManager2") &&
345 trace[2].getMethodName().equals("isFileSystemRoot")) {
346 log.warn("Ignoring Sun JRE bug " +
347 "(see http://forums.sun.com/thread.jspa?threadID=5435324): " + t);
355 @SuppressWarnings("unused")
356 private static class InternalException extends Exception {
357 public InternalException() {
361 public InternalException(String message, Throwable cause) {
362 super(message, cause);
365 public InternalException(String message) {
369 public InternalException(Throwable cause) {