/** * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * Copyright 2012-2015 the original author or authors. */ package org.assertj.swing.listener; import static javax.swing.SwingUtilities.invokeLater; import static javax.swing.SwingUtilities.isEventDispatchThread; import static org.assertj.core.util.Lists.newArrayList; import static org.assertj.core.util.Preconditions.checkNotNull; import java.awt.AWTEvent; import java.awt.event.AWTEventListener; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; /** * <p> * AWT event listener that ensures all events are handled on the event dispatch thread (EDT). * </p> * * <p> * NOTE from Abbot: {@code Applet} runners may run several simultaneous event dispatch threads when displaying multiple * {@code Applet}s simultaneously. If this listener is installed in the parent context of those dispatch threads, it * will be invoked on each of those threads, possibly simultaneously. * </p> * * @author Yvonne Wang * @author Alex Ruiz */ @ThreadSafe public abstract class EventDispatchThreadedEventListener implements AWTEventListener { @GuardedBy("lock") private final List<AWTEvent> deferredEvents = newArrayList(); private final Object lock = new Object(); private final Runnable processDeferredEventsTask = new Runnable() { @Override public void run() { processDeferredEvents(); } }; /** * If this method is called in the event dispatch thread, it processes the given event and the queued ones. Otherwise * it will add the given event to the queue and process all the events in the queue in the event dispatch thread * (EDT). * * @param event the event to process. */ @Override public void eventDispatched(AWTEvent event) { if (!isEventDispatchThread()) { // Often the application under test will invoke Window.show, which spawns hierarchy events. We want to ensure we // respond to those events on the dispatch thread to avoid deadlock. synchronized (lock) { deferredEvents.add(event); } // Ensure that in the absence of any subsequent event thread events deferred events still get processed. // If regular events are received before this action is run, the deferred events will be processed prior to those // events and the action will do nothing. invokeLater(processDeferredEventsTask); return; } // Ensure any deferred events are processed prior to subsequently posted events. processDeferredEvents(); processEvent(checkNotNull(event)); } /** Processes any events that were generated off the event queue but not immediately handled. */ protected void processDeferredEvents() { // Make a copy of the deferred events and empty the queue List<AWTEvent> queue = newArrayList(); synchronized (lock) { // In the rare case where there are multiple simultaneous dispatch threads, it's possible for deferred events to // get posted while another event is being processed. At most this will mean a few events get processed out of // order, but they will likely be from different event dispatch contexts, so it shouldn't matter. queue.addAll(deferredEvents); deferredEvents.clear(); } while (queue.size() > 0) { AWTEvent event = queue.get(0); queue.remove(0); processEvent(checkNotNull(event)); } } /** * This method is not protected by any synchronization locks (nor should it be); in the presence of multiple * simultaneous event dispatch threads, the listener must be thread-safe. * * @param event the event to process. */ protected abstract void processEvent(@Nonnull AWTEvent event); }