logging and unit test updates
[debian/openrocket] / src / net / sf / openrocket / gui / main / ExceptionHandler.java
1 package net.sf.openrocket.gui.main;
2
3 import javax.swing.JOptionPane;
4 import javax.swing.SwingUtilities;
5
6 import net.sf.openrocket.gui.dialogs.BugReportDialog;
7
8
9 public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
10
11         private static final int MEMORY_RESERVE = 512*1024;
12         
13         /**
14          * A memory reserve of 0.5 MB of memory, that can be freed when showing the dialog.
15          * <p>
16          * This field is package-private so that the JRE cannot optimize its use away.
17          */
18         static volatile byte[] memoryReserve = null;
19         
20         private static ExceptionHandler instance = null;
21         
22         
23         private volatile boolean handling = false;
24         
25         
26         
27         
28         @Override
29         public void uncaughtException(final Thread t, final Throwable e) {
30                 
31                 // Free memory reserve if out of memory
32                 if (isOutOfMemoryError(e)) {
33                         memoryReserve = null;
34                         handling = false;
35                 }
36
37                 e.printStackTrace();
38                 
39                 try {
40                         
41                         if (handling) {
42                                 System.err.println("Exception is currently being handled, ignoring:");
43                                 e.printStackTrace();
44                                 return;
45                         }
46                         
47                         handling = true;
48                         
49                         // Show on the EDT
50                         if (SwingUtilities.isEventDispatchThread()) {
51                                 showDialog(t, e);
52                         } else {
53                     SwingUtilities.invokeAndWait(new Runnable() {
54                         public void run() {
55                             showDialog(t, e);
56                         }
57                     });
58                         }
59                         
60                 } catch (Throwable ex) {
61                         
62                         // Make sure the handler does not throw any exceptions
63                         try {
64                                 System.err.println("Exception in exception handler, dumping exception:");
65                                 ex.printStackTrace();
66                         } catch (Throwable ignore) { }
67                         
68                 } finally {
69                         // Mark handling as completed
70                         handling = false;
71                 }
72                 
73         }
74
75         
76         /**
77          * Handle an error condition programmatically without throwing an exception.
78          * This can be used in cases where recovery of the error is desirable.
79          * 
80          * @param message       the error message.
81          */
82         public static void handleErrorCondition(String message) {
83                 handleErrorCondition(new InternalException(message));
84         }
85         
86
87         /**
88          * Handle an error condition programmatically without throwing an exception.
89          * This can be used in cases where recovery of the error is desirable.
90          * 
91          * @param message       the error message.
92          * @param exception     the exception that occurred.
93          */
94         public static void handleErrorCondition(String message, Exception exception) {
95                 handleErrorCondition(new InternalException(message, exception));
96         }
97         
98         
99         /**
100          * Handle an error condition programmatically without throwing an exception.
101          * This can be used in cases where recovery of the error is desirable.
102          * 
103          * @param exception             the exception that occurred.
104          */
105         public static void handleErrorCondition(final Exception exception) {
106                 final ExceptionHandler handler = instance;
107
108                 try {
109
110                         if (handler == null) {
111                                 // Not initialized, simply print the exception
112                                 exception.printStackTrace();
113                                 return;
114                         }
115
116                         final Thread thread = Thread.currentThread();
117
118                         if (SwingUtilities.isEventDispatchThread()) {
119                                 handler.showDialog(thread, exception);
120                         } else {
121                                 SwingUtilities.invokeAndWait(new Runnable() {
122                                         public void run() {
123                                                 handler.showDialog(thread, exception);
124                                         }
125                                 });
126                         }
127                 } catch (Exception e) {
128                         e.printStackTrace();
129                 }
130         }
131         
132         
133         /**
134          * The actual handling routine.
135          * 
136          * @param t             the thread that caused the exception, or <code>null</code>.
137          * @param e             the exception.
138          */
139         private void showDialog(Thread t, Throwable e) {
140                 
141                 // Out of memory
142                 if (isOutOfMemoryError(e)) {
143                         JOptionPane.showMessageDialog(null, 
144                                         new Object[] { 
145                                                 "OpenRocket can out of available memory!",
146                                                 "You should immediately close unnecessary design windows,",
147                                                 "save any unsaved designs and restart OpenRocket!"
148                                         }, "Out of memory", JOptionPane.ERROR_MESSAGE);
149                         return;
150                 }
151                 
152                 // Create the message
153                 String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
154                 if (msg.length() > 90) {
155                         msg = msg.substring(0, 80) + "...";
156                 }
157                 
158                 // Unknown Error
159                 if (!(e instanceof Exception)) {
160                         JOptionPane.showMessageDialog(null, 
161                                         new Object[] { 
162                                                 "An unknown Java error occurred:",
163                                                 msg,
164                                                 "<html>You should immediately close unnecessary design windows,<br>" +
165                                                 "save any unsaved designs and restart OpenRocket!"
166                                         }, "Unknown Java error", JOptionPane.ERROR_MESSAGE);
167                         return;
168                 }
169                 
170                 
171                 // Normal exception, show question dialog               
172                 int selection = JOptionPane.showOptionDialog(null, new Object[] {
173                                 "OpenRocket encountered an uncaught exception.  This typically signifies " +
174                                 "a bug in the software.", 
175                                 "<html><em>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + msg + "</em>",
176                                 " ",
177                                 "Please take a moment to report this bug to the developers.",
178                                 "This can be done automatically if you have an Internet connection."
179                                 }, "Uncaught exception", JOptionPane.DEFAULT_OPTION, 
180                                 JOptionPane.ERROR_MESSAGE, null, 
181                                 new Object[] { "View bug report", "Close" }, "View bug report");
182                 
183                 if (selection != 0) {
184                         // User cancelled
185                         return;
186                 }
187                 
188                 // Show bug report dialog
189                 BugReportDialog.showExceptionDialog(null, t, e);
190
191         }
192         
193         
194         
195         /**
196          * Registers the uncaught exception handler.  This should be used to ensure that
197          * all necessary registrations are performed.
198          */
199         public static void registerExceptionHandler() {
200                 
201                 if (instance == null) {
202                         instance = new ExceptionHandler();
203                         Thread.setDefaultUncaughtExceptionHandler(instance);
204                         
205                         // Handler for modal dialogs of Sun's Java implementation
206                         // See bug ID 4499199.
207                         System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
208                         
209                         reserveMemory();
210                 }
211                 
212         }
213         
214         
215         /**
216          * Reserve the buffer memory that is freed in case an OutOfMemoryError occurs.
217          */
218         private static void reserveMemory() {
219                 memoryReserve = new byte[MEMORY_RESERVE];
220                 for (int i=0; i<MEMORY_RESERVE; i++) {
221                         memoryReserve[i] = (byte)i;
222                 }
223         }
224
225         
226         
227         /**
228          * Return whether this throwable was caused by an OutOfMemoryError
229          * condition.  An exception is deemed to be caused by OutOfMemoryError
230          * if the throwable or any of its causes is of the type OutOfMemoryError.
231          * <p>
232          * This method is required because Apple's JRE implementation sometimes
233          * masks OutOfMemoryErrors within RuntimeExceptions.  Idiots.
234          * 
235          * @param t             the throwable to examine.
236          * @return              whether this is an out-of-memory condition.
237          */
238         private boolean isOutOfMemoryError(Throwable t) {
239                 while (t != null) {
240                         if (t instanceof OutOfMemoryError)
241                                 return true;
242                         t = t.getCause();
243                 }
244                 return false;
245         }
246         
247         
248         
249         /**
250          * Handler used in modal dialogs by Sun Java implementation.
251          */
252         public static class AwtHandler {
253                 public void handle(Throwable t) {
254                         
255                         /*
256                          * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
257                          */
258                         if (t instanceof ArrayIndexOutOfBoundsException) {
259                                 final String buggyClass = "sun.font.FontDesignMetrics";
260                                 StackTraceElement[] elements = t.getStackTrace();
261                                 if (elements.length >= 3 &&
262                                                 (buggyClass.equals(elements[0].getClassName()) ||
263                                                  buggyClass.equals(elements[1].getClassName()) ||
264                                                  buggyClass.equals(elements[2].getClassName()))) {
265                                         System.err.println("Ignoring Sun JRE bug 6828938:  " + t);
266                                         return;
267                                 }
268                         }
269                         
270                         
271                         if (instance != null) {
272                                 instance.uncaughtException(Thread.currentThread(), t);
273                         }
274                 }
275         }
276         
277         
278         private static class InternalException extends Exception {
279                 public InternalException() {
280                         super();
281                 }
282
283                 public InternalException(String message, Throwable cause) {
284                         super(message, cause);
285                 }
286
287                 public InternalException(String message) {
288                         super(message);
289                 }
290
291                 public InternalException(Throwable cause) {
292                         super(cause);
293                 }
294         }
295 }