2

I'm trying to organize filter chain of EventQueue.dispatchEvent. Something like java.io.FilterInputStream or javax.servlet.Filter.

Found EventQueueDelegate.Delegate to be intended for this?.. But in case of exception in EventQueue.dispatchEvent EventQueueDelegate.Delegate it knows nothing about this and ugly java.awt.EventDispatchThread.handleException appears on the scene.

  • Is this 'temporary hack' not yet solved since Java SE 1.1???!!!

I've also looked toward EventQueue.dispatchEvent to call in chain. But it appeared not suitable for this due to this method is protected and it require additional dancing with tambourines to make things work and code becomes not so lovely.

  • Any better solution?
Mike
  • 20,010
  • 25
  • 97
  • 140

2 Answers2

1

Next is dancing with tambourines around EventQueueDelegate.Delegate...

AwtExceptionHandler.java

package example;

/**
 * @see java.awt.EventDispatchThread#handleException(Throwable thrown)
 */
public interface AwtExceptionHandler {
    void handle(Throwable t) throws Throwable;
}

FilterEventQueueDelegate.java

package example;

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.lang.reflect.Method;
import java.util.ConcurrentModificationException;

import sun.awt.EventQueueDelegate;

/**
 * Aims to organise filter chain of {@link EventQueueDelegate.Delegate}.
 * 
 * <pre>
 * private static final AwtResponsivenessMonitor instance = FilterEventQueueDelegate.chain(new AwtResponsivenessMonitor());
 * </pre>
 * 
 * @author Mykhaylo Adamovych
 */
public abstract class FilterEventQueueDelegate implements EventQueueDelegate.Delegate, AwtExceptionHandler {
    public static final class ExceptionHandler {
        private static AwtExceptionHandler currentExceptionHandler;

        public void handle(Throwable t) throws Throwable {
            currentExceptionHandler.handle(t);
        }
    }

    private static final class SimpleFilterEventQueueDelegate extends FilterEventQueueDelegate {
        private EventQueueDelegate.Delegate thirdPartyDelegate;
        private Object thirdPartyExceptionHandler;

        @Override
        public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException {
            if (thirdPartyDelegate != null)
                thirdPartyDelegate.afterDispatch(arg0, arg1);
        }

        @Override
        public Object beforeDispatch(AWTEvent arg0) throws InterruptedException {
            if (thirdPartyDelegate != null)
                return thirdPartyDelegate.beforeDispatch(arg0);
            return arg0;
        }

        @Override
        public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException {
            if (thirdPartyDelegate != null)
                return thirdPartyDelegate.getNextEvent(arg0);
            return arg0.getNextEvent();
        }

        @Override
        public void handle(Throwable t) throws Throwable {
            if (thirdPartyExceptionHandler != null)
                try {
                    Class<? extends Object> c = thirdPartyExceptionHandler.getClass();
                    Method m = c.getMethod("handle", new Class[] { Throwable.class });
                    m.invoke(thirdPartyExceptionHandler, new Object[] { t });
                } catch (Throwable x) {
                    thirdPartyExceptionHandler = null; /* Do not try this again */
                    throw t;
                }
            else
                throw t;
        }

        public void setEventQueueDelegate(EventQueueDelegate.Delegate delegate) {
            thirdPartyDelegate = delegate;
        }

        public void setExceptionHandler(Object exceptionHandler) {
            thirdPartyExceptionHandler = exceptionHandler;
        }
    }

    public static <T extends FilterEventQueueDelegate> T chain(T delegate) {
        synchronized (EventQueueDelegate.class) {
            EventQueueDelegate.Delegate currentDelegate = EventQueueDelegate.getDelegate();
            FilterEventQueueDelegate currentFilterDelegate = null;
            if (currentDelegate instanceof FilterEventQueueDelegate)
                currentFilterDelegate = (FilterEventQueueDelegate) currentDelegate;
            else {
                SimpleFilterEventQueueDelegate simpleFilterDelegate = new SimpleFilterEventQueueDelegate();
                if (currentDelegate != null)
                    simpleFilterDelegate.setEventQueueDelegate(currentDelegate);
                Object currentExceptionHandler = null;
                try {
                    currentExceptionHandler = Class.forName(System.getProperty("sun.awt.exception.handler")).newInstance();
                } catch (Exception e) {
                }
                if (currentExceptionHandler != null)
                    simpleFilterDelegate.setExceptionHandler(currentExceptionHandler);
                System.setProperty("sun.awt.exception.handler", ExceptionHandler.class.getName());
                currentFilterDelegate = simpleFilterDelegate;
            }
            delegate.setNext(currentFilterDelegate);
            EventQueueDelegate.setDelegate(delegate);
            if (EventQueueDelegate.getDelegate() != delegate)
                throw new ConcurrentModificationException();
            ExceptionHandler.currentExceptionHandler = delegate;
            return delegate;
        }
    }

    protected FilterEventQueueDelegate next;

    @Override
    public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException {
        next.afterDispatch(arg0, arg1);
    }

    @Override
    public Object beforeDispatch(AWTEvent arg0) throws InterruptedException {
        return next.beforeDispatch(arg0);
    }

    @Override
    public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException {
        return next.getNextEvent(arg0);
    }

    @Override
    public void handle(Throwable t) throws Throwable {
        next.handle(t);
    }

    private void setNext(FilterEventQueueDelegate eventQueueDelegate) {
        next = eventQueueDelegate;
    }
}

AwtResponsivenessMonitor.java

package example;

import java.awt.AWTEvent;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Monitors {@code EventDispatchThread} responsiveness.
 * <p>
 * Singleton is initialised on first access.
 * 
 * @author Mykhaylo Adamovych
 */
public class AwtResponsivenessMonitor extends FilterEventQueueDelegate {
    private static final class DeamonThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            Thread result = new Thread(r);
            result.setName(AwtResponsivenessMonitor.class.getSimpleName());
            result.setDaemon(true);
            return result;
        }
    }

    private static final class NotResponsive extends RuntimeException {
        private static final long serialVersionUID = -1445765918431458354L;
    }

    public static final long DEFAULT_RESPONSIVENESS_TIMEOUT_S = 2;
    public static final long RESPONSIVENESS_WATCHDOG_MS = 50;
    private static final AwtResponsivenessMonitor instance = FilterEventQueueDelegate.chain(new AwtResponsivenessMonitor());

    public static AwtResponsivenessMonitor getInstance() {
        return instance;
    }

    public static long getResponsivenessTimeout() {
        return instance.responsivenessTimeoutMs.get();
    }

    public static void setResponsivenessTimeout(long timeoutMs) {
        instance.responsivenessTimeoutMs.set(timeoutMs);
    }

    private final AtomicLong responsivenessTimeoutMs = new AtomicLong(TimeUnit.SECONDS.toMillis(DEFAULT_RESPONSIVENESS_TIMEOUT_S));
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new DeamonThreadFactory());
    private long eventDispatchStartTime;
    private Thread currentWorkingThread;

    public AwtResponsivenessMonitor() {
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                checkResponsiveness();
            }
        }, RESPONSIVENESS_WATCHDOG_MS, RESPONSIVENESS_WATCHDOG_MS, TimeUnit.MILLISECONDS);
    }

    @Override
    public synchronized void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException {
        eventDispatchStartTime = 0;
        super.afterDispatch(arg0, arg1);
    }

    @Override
    public synchronized Object beforeDispatch(AWTEvent arg0) throws InterruptedException {
        eventDispatchStartTime = System.currentTimeMillis();
        currentWorkingThread = Thread.currentThread();
        return super.beforeDispatch(arg0);
    }

    private synchronized void checkResponsiveness() {
        if (eventDispatchStartTime != 0 && currentWorkingThread != null && System.currentTimeMillis() > eventDispatchStartTime + responsivenessTimeoutMs.get()) {
            Exception e = new NotResponsive();
            e.setStackTrace(currentWorkingThread.getStackTrace());
            e.printStackTrace();
            currentWorkingThread = null;
        }
    }

    @Override
    public synchronized void handle(Throwable t) throws Throwable {
        eventDispatchStartTime = 0;
        super.handle(t);
    }
}

AwtIdleTracker.java

package example;

import java.awt.AWTEvent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;

import javax.swing.SwingUtilities;

import sun.awt.SunToolkit;

/**
 * Tracks {@code EventDispatchThread} idleness.
 * <p>
 * Singleton is initialised on first access.
 * 
 * @author Mykhaylo Adamovych
 */
public class AwtIdleTracker extends FilterEventQueueDelegate {
    public static final long DEFAULT_IDLE_TIME_TO_TRACK_MS = 1000;
    private static final long IDLE_TIME_WATCHDOG_MS = 10;
    private static final AwtIdleTracker instance = FilterEventQueueDelegate.chain(new AwtIdleTracker());

    public static AwtIdleTracker getInstance() {
        return instance;
    }

    private volatile boolean inProgress;
    private final AtomicLong lastDispatchTime = new AtomicLong(0);

    @Override
    public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException {
        lastDispatchTime.set(System.currentTimeMillis());
        inProgress = false;
        super.afterDispatch(arg0, arg1);
    }

    @Override
    public Object beforeDispatch(AWTEvent arg0) throws InterruptedException {
        inProgress = true;
        return super.beforeDispatch(arg0);
    }

    @Override
    public void handle(Throwable t) throws Throwable {
        lastDispatchTime.set(System.currentTimeMillis());
        inProgress = false;
        super.handle(t);
    }

    public boolean isIdle() {
        return this.isIdle(DEFAULT_IDLE_TIME_TO_TRACK_MS);
    }

    public boolean isIdle(long idleTimeToTrackMs) {
        return !inProgress && SunToolkit.isPostEventQueueEmpty() && System.currentTimeMillis() > lastDispatchTime.get() + idleTimeToTrackMs;
    }

    public void waitForIdle() {
        waitForIdle(DEFAULT_IDLE_TIME_TO_TRACK_MS);
    }

    public void waitForIdle(long idleTimeToTrackMs) {
        waitForIdle(idleTimeToTrackMs, TimeUnit.DAYS.toMillis(365));
    }

    public void waitForIdle(long idleTimeToTrackMs, long timeoutMs) {
        if (SwingUtilities.isEventDispatchThread())
            throw new IllegalAccessError();
        long staleThreshold = System.currentTimeMillis() + timeoutMs;
        while (!isIdle(idleTimeToTrackMs)) {
            if (System.currentTimeMillis() > staleThreshold)
                throw new RuntimeException("GUI still is not idle.");
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(IDLE_TIME_WATCHDOG_MS));
        }
    }
}

Example.java

package example;

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import sun.awt.EventQueueDelegate;

public class Example {
    public static class ThirdPartyEventQueueDelegate implements EventQueueDelegate.Delegate {
        public static final void registerEventQueueDelegate() {
            EventQueueDelegate.setDelegate(new ThirdPartyEventQueueDelegate());
        }

        @Override
        public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException {
            System.out.println("Third party even queue delegate was not broken.");
        }

        @Override
        public Object beforeDispatch(AWTEvent arg0) throws InterruptedException {
            return arg0;
        }

        @Override
        public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException {
            return arg0.getNextEvent();
        }
    }

    public static class ThirdPartyExceptionHandler {
        public static void registerExceptionHandler() {
            System.setProperty("sun.awt.exception.handler", ThirdPartyExceptionHandler.class.getName());
        }

        public void handle(Throwable t) {
            System.out.println("Third party Exception handler was not broken.");
        }
    }

    private static boolean wasIdle = false;
    private static boolean isFistTime = true;

    public static synchronized void log(String msg) {
        System.out.println(new SimpleDateFormat("mm:ss.SSS").format(new Date()) + "\t" + msg);
    }

    public static void main(String[] args) {
        // let suppose there are some related stuff already
        ThirdPartyExceptionHandler.registerExceptionHandler();
        ThirdPartyEventQueueDelegate.registerEventQueueDelegate();
        // initialise singletons, build filter chain
        AwtIdleTracker.getInstance();
        AwtResponsivenessMonitor.setResponsivenessTimeout(TimeUnit.SECONDS.toMillis(2));
        testWaitForIdle();
        // testSomeGui();
    }

    public static void testSomeGui() {
        // some test with visible GUI
        JFrame frame = new JFrame();
        frame.setSize(300, 300);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
        while (true) {
            boolean isIdle = AwtIdleTracker.getInstance().isIdle();
            if (isFistTime || wasIdle != isIdle) {
                isFistTime = false;
                wasIdle = isIdle;
                String msg = isIdle
                        ? "idle"
                        : "busy";
                log("system becomes " + msg);
            }
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
        }
    }

    public static void testWaitForIdle() {
        // some long operation
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                log("task started");
                // throw new RuntimeException();
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
                log("task finished");
            }
        });
        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
        log("started waiting for idle");
        AwtIdleTracker.getInstance().waitForIdle();
        log("stopped waiting for idle");
    }
}
Mike
  • 20,010
  • 25
  • 97
  • 140
  • +1 for code, 1milions time -1 for reason why do that, use invokeAndWait() – mKorbel Nov 11 '11 at 14:25
  • when runnning integration tests I need to wait for EDT finished GUI reconstruction before asserting label text on some button. Also I'm waiting for background tasks to be completed but this is different story. invokeAndWait() will wait for dispatching events currently added to EventQueue. But what if while processing some of events new will be added to queue and what if this processing could be as much important as totally rebuilding gui controls tree?.. – Mike Nov 11 '11 at 14:38
  • 1) (`runnning integration tests I need to wait for EDT`) use `javax.swing.Action`, 2) (`background tasks to be completed but`) sometimes you have call `javax.swing.Action` from `javax.swing.Timer` – mKorbel Nov 11 '11 at 14:42
  • if you want ot something to test on EDT, then put there Substance Look and Fell, and save these errors to log, in majorities cases this engines has true, notice not integrations test, on EDT test – mKorbel Nov 11 '11 at 14:44
1

To synchronise tests and application under test here is available Sun's stuff:
SunToolkit#realSync()
SunToolkit#waitForIdle()

Mike
  • 20,010
  • 25
  • 97
  • 140