/** * Copyright (c) 2002-2005, Simone Bordet * All rights reserved. * * This software is distributable under the BSD license. * See the terms of the BSD license in the documentation provided with this software. */ package foxtrot.pumps; import java.lang.reflect.Method; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedAction; import java.awt.AWTEvent; import java.awt.EventQueue; import java.awt.Toolkit; import foxtrot.EventPump; import foxtrot.Task; /** * This implementation of EventPump calls the package protected method * <code>java.awt.EventDispatchThread.pumpEvents(Conditional)</code> * to pump events while a Task is executed. * @version $Revision: 1.1 $ */ public class ConditionalEventPump implements EventPump, EventFilterable { private static Class conditionalClass; private static Method pumpEventsMethod; static final boolean debug = false; static { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws ClassNotFoundException, NoSuchMethodException { ClassLoader loader = ClassLoader.getSystemClassLoader(); conditionalClass = loader.loadClass("java.awt.Conditional"); Class dispatchThreadClass = loader.loadClass("java.awt.EventDispatchThread"); pumpEventsMethod = dispatchThreadClass.getDeclaredMethod("pumpEvents", new Class[]{conditionalClass}); pumpEventsMethod.setAccessible(true); // The following code is no longer relevant in a JDK5 / // JDK6 context, now that we have // Thread.setDefaultUncaughtExceptionHandler(). // // By commenting out this next block, I am avoiding // having Foxtrot override a default uncaught exception // handler with its own handler. // // Jonathan Abbey // 10 September 2010 // See remarks for use of this property in java.awt.EventDispatchThread // String property = "sun.awt.exception.handler"; // String handler = System.getProperty(property); // if (handler == null) // { // handler = ThrowableHandler.class.getName(); // System.setProperty(property, handler); // if (debug) System.out.println("[ConditionalEventPump] Installing AWT Throwable Handler " + handler); // } // else // { // if (debug) System.out.println("[ConditionalEventPump] Using already installed AWT Throwable Handler " + handler); // } return null; } }); } catch (Throwable x) { if (debug) x.printStackTrace(); throw new Error(x.toString()); } } private EventFilter filter; public void setEventFilter(EventFilter filter) { this.filter = filter; } public EventFilter getEventFilter() { return filter; } public void pumpEvents(Task task) { // A null task may be passed for initialization of this class. if (task == null) return; try { if (debug) System.out.println("[ConditionalEventPump] Start pumping events - Pump is " + this + " - Task is " + task); // Invoke java.awt.EventDispatchThread.pumpEvents(new Conditional(task)); Object conditional = Proxy.newProxyInstance(conditionalClass.getClassLoader(), new Class[]{conditionalClass}, new Conditional(task)); pumpEventsMethod.invoke(Thread.currentThread(), new Object[]{conditional}); } catch (InvocationTargetException x) { // No exceptions should escape from java.awt.EventDispatchThread.pumpEvents(Conditional) // since we installed a throwable handler. But one provided by the user may fail. Throwable t = x.getTargetException(); System.err.println("[ConditionalEventPump] Exception occurred during event dispatching:"); t.printStackTrace(); // Rethrow. This will exit from Worker.post with a runtime exception or an error, and // the original event pump will take care of it. if (t instanceof RuntimeException) throw (RuntimeException)t; else throw (Error)t; } catch (Throwable x) { // Here we have an compiler bug System.err.println("[ConditionalEventPump] PANIC: uncaught exception in Foxtrot code"); x.printStackTrace(); } finally { // We're not done. Because of bug #4531693 (see Conditional) pumpEvents() may have returned // immediately, but the Task is not completed. Same may happen in case of buggy exception handler. // Here wait until the Task is completed. Side effect is freeze of the GUI. waitForTask(task); if (debug) System.out.println("[ConditionalEventPump] Stop pumping events - Pump is " + this + " - Task is " + task); } } private void waitForTask(Task task) { try { synchronized (task) { while (!task.isCompleted()) { if (debug) System.out.println("[ConditionalEventPump] Waiting for Task " + task + " to complete (GUI freeze)"); task.wait(); } if (debug) System.out.println("[ConditionalEventPump] Task " + task + " is completed"); } } catch (InterruptedException x) { // Someone interrupted the Event Dispatch Thread, re-interrupt Thread.currentThread().interrupt(); } } /** * This method is called before an event is got from the EventQueue and dispatched, * to see if pumping of events should continue or not. * Returns true to indicate that pumping should continue, false to indicate that pumping * should stop. */ private Boolean pumpEvent(Task task) { Boolean completed = task.isCompleted() ? Boolean.TRUE : Boolean.FALSE; // Task already completed, return false to indicate to stop pumping events if (completed.booleanValue()) return Boolean.FALSE; while (true) { // The task is still running, we should pump events AWTEvent nextEvent = waitForEvent(); if (nextEvent == null) return Boolean.FALSE; if (debug) System.out.println("[ConditionalEventPump] Next Event: " + nextEvent); // If this event cannot be pumped, we interrupt immediately event pumping // The GUI will freeze, but we still wait for the Task to finish (see pumpEvents()) if (!canPumpEvent(nextEvent)) return Boolean.FALSE; // Plug the event filtering mechanism if (filter == null || filter.accept(nextEvent)) return Boolean.TRUE; // The event has been filtered out, pop it from the EventQueue // then wait again for the next event nextEvent = getNextEvent(); if (nextEvent == null) return Boolean.FALSE; if (debug) System.out.println("[ConditionalEventPump] Filtered out AWT Event: " + nextEvent + " by filter " + filter); } } /** * Returns whether this event can be pumped from the EventQueue. * JDK 1.4 introduced SequencedEvent, which is an event holding a list SequencedEvents * that should be dispatched in order. * Bug #4531693 was caused by the fact that the first SequencedEvent of a list, when * dispatched, might end up calling an event listener that displayed a dialog (or called Foxtrot, * that uses the same event pumping mechanism); the new event pump might try to dispatch the * SequencedEvent second in the list (while the first wasn't completely dispatched yet), * causing the application to hang. * Bug #4531693 has been fixed in JDK 1.4.2, and backported to 1.4.1, so there is no longer * need to check for this situation, unless using JDK 1.4.0 or non-fixed versions of JDK 1.4.1. */ protected boolean canPumpEvent(AWTEvent event) { return true; } private EventQueue getEventQueue() { return (EventQueue)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return Toolkit.getDefaultToolkit().getSystemEventQueue(); } }); } private AWTEvent getNextEvent() { try { return getEventQueue().getNextEvent(); } catch (InterruptedException x) { Thread.currentThread().interrupt(); return null; } } /** * Waits until an event is available on the EventQueue. * This method uses the same synchronization mechanisms used by EventQueue to be notified when * an event is posted on the EventQueue. * Waiting for events is necessary in this case: a Task is posted and we would like to start * pumping, but no events have been posted yet ({@link #peekEvent} returns null). */ private AWTEvent waitForEvent() { EventQueue queue = getEventQueue(); AWTEvent nextEvent = null; synchronized (queue) { while ((nextEvent = peekEvent(queue)) == null) { if (debug) System.out.println("[ConditionalEventPump] Waiting for events..."); try { queue.wait(); } catch (InterruptedException x) { Thread.currentThread().interrupt(); return null; } } } return nextEvent; } /** * Peeks the EventQueue for the next event, without removing it. */ protected AWTEvent peekEvent(EventQueue queue) { return queue.peekEvent(); } /** * Implements the <code>java.awt.Conditional</code> interface, * that is package private, with a JDK 1.3+ dynamic proxy. */ private class Conditional implements InvocationHandler { private final Task task; /** * Creates a new invocation handler for the given task. */ private Conditional(Task task) { this.task = task; } /** * Implements method <code>java.awt.Conditional.evaluate()</code> */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return pumpEvent(task); } } /** * Handler for RuntimeExceptions or Errors thrown during dispatching of AWT events. <br /> * The name of this class is used as a value of the property <code>sun.awt.exception.handler</code>, * and the AWT event dispatch mechanism calls it when an unexpected runtime exception or error * is thrown during event dispatching. If the user specifies a different exception handler, * this one will not be used, and the user's one is used instead. * Use of this class is necessary in JDK 1.4, since RuntimeExceptions and Errors are propagated to * be handled by the ThreadGroup (but not for modal dialogs). */ public static class ThrowableHandler { /** * The callback method invoked by the AWT event dispatch mechanism when an unexpected * exception or error is thrown during event dispatching. <br> * It just logs the exception. */ public void handle(Throwable t) { System.err.println("[ConditionalEventPump] Exception occurred during event dispatching:"); t.printStackTrace(); } } }