/*
* Created on Mar 27, 2008
*
* 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 @2008-2013 the original author or authors.
*/
package org.fest.swing.listener;
import static javax.swing.SwingUtilities.invokeLater;
import static javax.swing.SwingUtilities.isEventDispatchThread;
import static org.fest.util.Lists.newArrayList;
import static org.fest.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);
}