/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.rc.swt.driver;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.driver.IEventMatcher;
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
import org.eclipse.jubula.rc.common.driver.IRobotEventConfirmer;
import org.eclipse.jubula.rc.common.driver.IRunnable;
import org.eclipse.jubula.rc.common.driver.InterceptorOptions;
import org.eclipse.jubula.rc.common.driver.RobotTiming;
import org.eclipse.jubula.rc.common.exception.RobotException;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.util.WorkaroundUtil;
import org.eclipse.jubula.rc.swt.SwtAUTServer;
import org.eclipse.jubula.rc.swt.utils.SwtUtils;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
/**
* <p> This event confirmer works on a class of SWT events defined by an
* <code>InterceptorOptions</code> instance. The confirmer adds a Listener
* to the SWT event queue
* using the <code>InterceptorOptions</code> event mask.</p>
*
* <p> To confirm an event, call <code>waitToConfirm()</code>.</p>
*
* @author BREDEX GmbH
* @created 26.07.2006
*/
class RobotEventConfirmerSwtImpl implements IRobotEventConfirmer,
Listener {
/** The logger. */
private static AutServerLogger log = new AutServerLogger(
RobotEventConfirmerSwtImpl.class);
/** Stores if the confirmer is enabled. */
private boolean m_enabled = false;
/** Stores if the confirmer is being waiting for an event to confirm. */
private boolean m_waiting = false;
/** The interceptor options. */
private InterceptorOptions m_options;
/** The graphics component on which the event occurs. */
private Object m_eventTarget;
/** The event matcher. */
private IEventMatcher m_eventMatcher;
/** Stores all events of a given class after the confirmer has been enabled. */
private List<Event> m_eventList = new LinkedList<Event>();
/**
* Creates a new confirmer for a class of events defined by <code>options</code>.
* @param options The options.
*/
RobotEventConfirmerSwtImpl(InterceptorOptions options) {
m_options = options;
}
/**
* Logs a list.
* @param list The list.
*/
private void logList(List<Event> list) {
log.debug("Stored SWTEvents["); //$NON-NLS-1$
List<Event> copy = (List<Event>)((LinkedList<Event>)list).clone();
for (Iterator<Event> it = copy.iterator(); it.hasNext();) {
Object element = it.next();
log.debug(element);
}
log.debug("]"); //$NON-NLS-1$
}
/**
* Checks if the given event matches.
* @param event The event.
* @return <code>true</code> if the event matches, otherwise <code>false</code>.
*/
private boolean isEventMatching(Event event) {
if (log.isDebugEnabled()) {
log.debug("SWTEvent matching?: " + event); //$NON-NLS-1$
log.debug("Matching ID? : " //$NON-NLS-1$
+ (event.type == m_eventMatcher.getEventId()));
log.debug("Matching source? : " //$NON-NLS-1$
+ (m_eventTarget == null
|| event.widget == m_eventTarget));
log.debug("*Source: " + event.widget); //$NON-NLS-1$
log.debug("*Target: " + m_eventTarget); //$NON-NLS-1$
}
return ((m_eventTarget == null
|| matchComponent(m_eventTarget, event.widget)
|| isInBounds(m_eventTarget, event.widget))
&& m_eventMatcher.isMatching(event));
}
/**
* Determines whether one widget is completely contained within another.
*
* @param eventTarget The "owning" widget.
* @param eventWidget The "child" widget.
* @return <code>true</code> if <code>eventWidget</code> is completely
* contained within <code>boundsWidget</code>.
*/
private boolean isInBounds(Object eventTarget, Widget eventWidget) {
return SwtUtils.isInBounds(
(Widget)eventTarget, eventWidget);
}
/**
* Calls isComponentMatching(...) interchanging the parameters,
* so that the event-target-component and its children will be checked for
* matching or the event-source-component and its children.
* @param evTarget evTarget
* @param evSource evSource
* @return boolean
*/
private boolean matchComponent(Object evTarget, Object evSource) {
return (isComponentMatching(evTarget, evSource)
|| isComponentMatching(evSource, evTarget));
}
/**
* Checks if the current component matches to the expected component.
* If the current Component does not match, its parents will be checked
* recursive.
* @param expComp the expected Component
* @param currComp the current component to check.
* @return true or false.
*/
private boolean isComponentMatching(Object expComp, Object currComp) {
return isComponentMatching(expComp, currComp, true);
}
/**
*
* @param eventTarget a target for the expected event.
* @return true if no EventMatching should be executed on the given
* event target, false otherwise.
*/
protected boolean isNoConfirmComponent(Object eventTarget) {
// Menu and MenuItem: We receive no mouse events
// Shell: We receive no mouse events under GTK when clicking/moving
// in a location where there is no component (ex. an empty
// editor pane in an RCP application).
return eventTarget instanceof Menu
|| eventTarget instanceof MenuItem
|| eventTarget instanceof Shell;
}
/**
* Checks if the current component matches to the expected component.
* If the current Component does not match, its parents will be checked
* recursive.
* @param expComp the expected Component
* @param currComp the current component to check.
* @param checkChildren should the children of the component be checked
* if the component itself does not match?
* @return true or false.
*/
private boolean isComponentMatching(Object expComp, Object currComp,
boolean checkChildren) {
if (log.isDebugEnabled()) {
log.debug("Matching source? : " + (expComp == currComp)); //$NON-NLS-1$
}
if (expComp == currComp) {
return true;
}
// if no matching, try to match children.
boolean match = false;
Widget curr = (Widget)currComp;
final Widget[] widgetChildren = SwtUtils.getWidgetChildren(curr, true);
if (checkChildren && widgetChildren.length > 0) {
Widget[] children = widgetChildren;
for (int i = 0; i < children.length; i++) {
match = isComponentMatching(expComp, children[i]);
if (match) {
return match;
}
}
}
/* corner case: subcomponents of a composite that should be treated
* as its own component
* See: SwtUtils.checkControlParent
*/
if (currComp instanceof Control) {
Control controlComp = (Control)currComp;
Control parentControl = SwtUtils.checkControlParent(controlComp);
if (controlComp != parentControl) {
match = isComponentMatching(expComp, parentControl, false);
}
if (match) {
return match;
}
}
return match;
}
/**
* Checks if one of the events stored into the given list matches.
* @param eventList The list of events
* @return <code>true</code> if one or more of the event matches, otherwise <code>false</code>.
*/
private boolean isEventMatching(List<Event> eventList) {
for (Iterator<Event> it = eventList.iterator(); it.hasNext();) {
Event event = it.next();
if (isEventMatching(event)) {
return true;
}
}
return false;
}
/**
* Adds the event to the event list.
* @param event The event.
*/
private void addEventToList(Event event) {
synchronized (m_eventList) {
((LinkedList<Event>)m_eventList).addFirst(event);
}
}
/**
* Stops the waiting status. That means, that the current waiting thread is
* being notified and the <code>m_waiting</code> property is set to
* <code>false</code>.
*/
private void stopWaiting() {
synchronized (this) {
m_waiting = false;
notify();
}
if (log.isDebugEnabled()) {
log.debug("Notified waiting thread"); //$NON-NLS-1$
}
}
/**
* Enables or disables the confirmer. If the confirmer is enabled, the AWT
* listener is added to the SWT event queue so that the confirmer starts
* storing events of the configured class of events. If it is disabled, the
* listener is removed from the SWT event queue.
* @param enabled <code>true</code> or <code>false</code>.
*/
void setEnabled(final boolean enabled) {
m_enabled = enabled;
synchronized (m_eventList) {
m_eventList.clear();
}
if (log.isDebugEnabled()) {
log.debug("Enabled? : " + enabled); //$NON-NLS-1$
if (enabled) {
log.debug("Storing SWTEvents with: " + m_options); //$NON-NLS-1$
}
}
final long[] eventMask = m_options.getEventMask();
final IEventThreadQueuer evThreadQueuer =
new EventThreadQueuerSwtImpl();
evThreadQueuer.invokeAndWait("add-/removeDisplayFilters", //$NON-NLS-1$
new IRunnable<Void>() {
public Void run() {
final Display autDisplay = ((SwtAUTServer)AUTServer
.getInstance()).getAutDisplay();
final int maskLength = eventMask.length;
for (int i = 0; i < maskLength; i++) {
if (enabled) {
autDisplay.addFilter((int)eventMask[i],
RobotEventConfirmerSwtImpl.this);
} else {
autDisplay.removeFilter((int)eventMask[i],
RobotEventConfirmerSwtImpl.this);
}
}
return null;
}
});
}
/**
* {@inheritDoc}
*/
public void waitToConfirm(final Object eventTarget,
IEventMatcher matcher, long timeout) throws RobotException {
m_eventTarget = eventTarget;
m_eventMatcher = matcher;
// Put every code in this try-block! Otherwise it is not ensured
// that this listener will ever be removed from the AUT!!!
try {
if (isNoConfirmComponent(eventTarget)) {
return;
}
synchronized (m_eventList) {
if (isEventMatching(m_eventList)) {
return;
}
}
m_waiting = true;
waitFor(timeout);
if (m_waiting) {
// I'm still waiting. This means that the event could not
// be confirmed during the confirm time interval, that means
// the event matcher didn't find a matching event.
// But the event matcher may accept a different event, which has
// already dispatched, as a fall back.
boolean fallBackMatching;
synchronized (m_eventList) {
fallBackMatching = m_eventMatcher
.isFallBackEventMatching(m_eventList, m_eventTarget);
}
if (log.isDebugEnabled()) {
if (!fallBackMatching) {
log.debug("Received timeout"); //$NON-NLS-1$
log.debug(m_options);
synchronized (m_eventList) {
logList(m_eventList);
}
} else {
log.debug("-> Fall back event has matched!"); //$NON-NLS-1$
}
}
if (!fallBackMatching && !WorkaroundUtil.isIgnoreTimeout()) {
throw new RobotException(
"Timeout received before confirming the posted event: " //$NON-NLS-1$
+ m_eventMatcher.getEventId(),
EventFactory.createActionError(
TestErrorEvent.CONFIRMATION_TIMEOUT));
}
}
if (log.isDebugEnabled()) {
log.debug("Got notification"); //$NON-NLS-1$
}
} finally {
setEnabled(false);
}
}
/**
* waits for the event to arrive with a timeout
* @param pTimeout timeout after this amount of ms
*/
private void waitFor(long pTimeout) {
synchronized (this) {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
do {
try {
wait(timeout);
} catch (InterruptedException e) {
// ignore
}
now = System.currentTimeMillis();
timeout = done - now;
} while (m_waiting && (timeout > 0));
}
}
/**
* {@inheritDoc}
*/
public void handleEvent(final Event event) {
if (!m_enabled) {
return;
}
// !! Never block in the GUI thread, it may cause deadlocks!
new Thread(new Runnable() {
public void run() {
try {
addEventToList(event);
synchronized (m_eventList) {
if (log.isDebugEnabled()) {
log.debug("SWTEvent : " + event); //$NON-NLS-1$
log.debug("Event target: " + m_eventTarget); //$NON-NLS-1$
log.debug("Waiting? : " + m_waiting); //$NON-NLS-1$
logList(m_eventList);
}
if (m_waiting && isEventMatching(m_eventList)) {
stopWaiting();
}
}
} catch (Throwable t) {
log.error("exception in handleEvent", t); //$NON-NLS-1$
}
}
}).start();
}
/**
* {@inheritDoc}
*/
public void waitToConfirm(Object eventTarget,
IEventMatcher matcher) throws RobotException {
waitToConfirm(eventTarget, matcher,
RobotTiming.getEventConfirmTimeout());
}
}