001: package abbot.util;
002:
003: import java.awt.EventQueue;
004:
005: import javax.swing.SwingUtilities;
006:
007: import abbot.Log;
008:
009: /** Handler for uncaught exceptions on any event dispatch thread.
010: Once this has been installed, the class must be accessible by any
011: subsequently launched dispatch thread.<p>
012:
013: This handler is installed by setting the System property
014: sun.awt.exception.handler. See javadoc for java.awt.EventDispatchThread
015: for details. This is sort of a patch to Sun's implementation, which only
016: checks the property once and caches the result ever after. This
017: implementation will always chain to the handler indicated by the current
018: value of the property.<p>
019:
020: It is most definitely NOT safe to try to install several of these on
021: different threads.
022: */
023: public class EventDispatchExceptionHandler {
024: /** See javadoc for java.awt.EventDispatchThread. */
025: public static final String PROP_NAME = "sun.awt.exception.handler";
026:
027: private static boolean installed = false;
028: private static boolean canInstall = true;
029:
030: /** Install a handler for event dispatch exceptions. This is kind
031: of a hack, but it's Sun's hack.
032: See the javadoc for java.awt.EventDispatchThread for details.
033: NOTE: we throw an exception immediately, which
034: ensures that our handler is installed, since otherwise
035: someone might set this property later.
036: java.awt.EventDispatchThread doesn't actually load the handler
037: specified by the property until an exception is caught by the
038: event dispatch thread. SwingSet2 in 1.4.1 installs its own.
039: Note that a new instance is created for each exception thrown.
040:
041: @throws RuntimeException if the handler cannot be installed.
042: @throws IllegalStateException if this method is invoked from an event
043: dispatch thread.
044: @throws IllegalArgumentException if the given class is not derived
045: from this one.
046:
047: // TODO: read the private static field
048: // String EventDispatchThread.handlerClassName and override it if
049: // necessary.
050: */
051: public void install() {
052: if (SwingUtilities.isEventDispatchThread()) {
053: throw new IllegalStateException(
054: "Handler must not be installed from the event dispatch thread");
055: }
056:
057: Class cls = getClass();
058: final String className = cls.getName();
059: try {
060: cls.newInstance();
061: } catch (Exception e) {
062: String msg = "Exception handler (" + cls
063: + ") must have an accessible no-args Constructor: "
064: + e;
065: throw new IllegalArgumentException(msg);
066: }
067: if (installed) {
068: // If we've already installed an instance of
069: // this handler, all we need to do is set the
070: // property name.
071: Log.debug("Exception handler class already installed");
072: System.setProperty(PROP_NAME, className);
073: } else if (!canInstall) {
074: Log.warn("Can't install event dispatch exception handler");
075: } else {
076: Log.log("Attempting to install handler " + className);
077: class PropertiesHolder {
078: /** Preserve the system properties state. */
079: public java.util.Properties properties = null;
080: }
081: final PropertiesHolder holder = new PropertiesHolder();
082: // Even if it's been set to something else, we can override it
083: // if there hasn't been an event exception thrown yet.
084: EventQueue.invokeLater(new Runnable() {
085: public void run() {
086: holder.properties = (java.util.Properties) System
087: .getProperties().clone();
088: // Set the property just before throwing the exception;
089: // OSX sets the property as part of AWT startup, so
090: // we have to override it here.
091: System.setProperty(PROP_NAME, className);
092: throw new DummyException();
093: }
094: });
095: // Does nothing but wait for the previous invocation to finish
096: AWT.invokeAndWait(new Runnable() {
097: public void run() {
098: }
099: });
100: System.setProperties(holder.properties);
101: String oldHandler = System.getProperty(PROP_NAME);
102:
103: if (installed) {
104: if (oldHandler != null) {
105: Log
106: .debug("Replaced an existing event exception handler ("
107: + oldHandler + ")");
108: }
109: } else {
110: canInstall = false;
111: String msg = "The handler for event "
112: + "dispatch thread exceptions could not be installed";
113: if (oldHandler != null) {
114: msg += " ("
115: + oldHandler
116: + " has already been "
117: + "set and cached; there is no way to override it)";
118: }
119: Log.warn(msg);
120: throw new RuntimeException(msg);
121: }
122: }
123: }
124:
125: /** Define this to handle the exception as needed.
126: * Default prints a warning to System.err.
127: */
128: protected void exceptionCaught(Throwable thrown) {
129: System.err
130: .println("Exception caught on event dispatch thread: "
131: + thrown);
132: }
133:
134: /** Handle exceptions thrown on the event dispatch thread. */
135: public void handle(Throwable thrown) {
136: Log.debug("Handling event dispatch exception: " + thrown);
137: String handler = System.getProperty(PROP_NAME);
138: boolean handled = false;
139: if (handler != null && !handler.equals(getClass().getName())) {
140: Log.debug("A user exception handler (" + handler
141: + ") has been set, invoking it");
142: try {
143: ClassLoader cl = Thread.currentThread()
144: .getContextClassLoader();
145: Class c = Class.forName(handler, true, cl);
146: c.getMethod("handle", new Class[] { Throwable.class })
147: .invoke(c.newInstance(),
148: new Object[] { thrown });
149: handled = true;
150: } catch (Throwable e) {
151: Log.warn("Could not invoke user handler: " + e);
152: }
153: }
154: // The exception may be created by a different class loader
155: // so compare by name only
156: if (thrown instanceof DummyException) {
157: // Install succeeded
158: Log.debug("Installation succeeded");
159: installed = true;
160: } else {
161: if (!handled) {
162: Log
163: .debug("Handling exception on event dispatch thread: "
164: + thrown + " with " + getClass());
165: Log.debug(thrown);
166: exceptionCaught(thrown);
167: }
168: }
169: Log.debug("Handling done");
170: }
171:
172: public static boolean isInstalled() {
173: return installed;
174: }
175:
176: private static class DummyException extends RuntimeException {
177: public String toString() {
178: return super .toString() + " " + getClass().getClassLoader();
179: }
180: }
181: }
|