/*******************************************************************************
* Copyright (c) 2013 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.javafx.driver;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.WeakEventHandler;
import javafx.scene.Scene;
import javafx.stage.Window;
import org.eclipse.jubula.rc.common.driver.IEventMatcher;
import org.eclipse.jubula.rc.common.driver.IRobotEventConfirmer;
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.javafx.components.ParentGetter;
import org.eclipse.jubula.rc.javafx.tester.util.JavaFXEventConverter;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
/**
* <p>
* This event confirmer works on a class of AWT events defined by an
* <code>InterceptorOptions</code> instance. The confirmer adds a
* {@link java.awt.event.AWTEventListener} to the AWT event queue using the
* <code>InterceptorOptions</code> event mask.
* </p>
*
* <p>
* To confirm an event, call <code>waitToConfirm()</code>.
* </p>
*
* @author BREDEX GmbH
* @created 30.10.2013
*/
class RobotEventConfirmerJavaFXImpl implements IRobotEventConfirmer,
EventHandler<Event> {
/**
* The logger.
*/
private static AutServerLogger log = new AutServerLogger(
RobotEventConfirmerJavaFXImpl.class);
/**
* Stores if the confirmer is enabled.
*/
private volatile 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 volatile InterceptorOptions m_options;
/**
* Stores all events of a given class after the confirmer has been enabled.
*/
private LinkedBlockingQueue<Event> m_eventList =
new LinkedBlockingQueue<Event>();
/**
* Stores Windows on wich Events could occur, this includes Popups such as
* contextmenus
*/
private LinkedBlockingQueue<ReadOnlyObjectProperty
<? extends Window>> m_sceneGraphs;
/**
* Creates a new confirmer for a class of events defined by
* <code>options</code>.
*
* @param options
* The options.
* @param sceneGraphs
* List with instances of Windows and their Scene-Graphs
*/
protected RobotEventConfirmerJavaFXImpl(
InterceptorOptions options,
LinkedBlockingQueue<ReadOnlyObjectProperty
<? extends Window>> sceneGraphs) {
m_options = options;
m_sceneGraphs = sceneGraphs;
}
@Override
public void waitToConfirm(Object eventTarget, IEventMatcher matcher)
throws RobotException {
waitToConfirm(eventTarget, matcher,
RobotTiming.getEventConfirmTimeout());
}
@Override
public void waitToConfirm(Object eventTarget, IEventMatcher matcher,
long pTimeout) throws RobotException {
if (log.isDebugEnabled()) {
log.debug("Waiting for EventID: " + String.valueOf(matcher) //$NON-NLS-1$
+ " on Component: " + String.valueOf(eventTarget)); //$NON-NLS-1$
}
ArrayList<Event> history = new ArrayList<>();
try {
m_waiting = true;
try {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
do {
Event e = m_eventList.poll(timeout, TimeUnit.MILLISECONDS);
history.add(e);
if (isEventMatch(e, matcher, (EventTarget)eventTarget)) {
return;
}
now = System.currentTimeMillis();
timeout = done - now;
} while (m_waiting && (timeout > 0));
} catch (InterruptedException e) {
throw new RobotException(e);
}
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 = matcher
.isFallBackEventMatching(history,
eventTarget);
if (!fallBackMatching && !WorkaroundUtil.isIgnoreTimeout()) {
throw new RobotException(
"Timeout received before confirming the posted event: " //$NON-NLS-1$
+ matcher.getEventId(),
EventFactory
.createActionError(TestErrorEvent.
CONFIRMATION_TIMEOUT));
}
}
} finally {
setEnabled(false);
}
}
/**
*
* @param e The received event. May be <code>null</code>, in which case
* <code>false</code> will be returned.
* @param matcher The matcher to apply to the event.
* @param expectedTarget The expected target (Graphics Component) of the
* event. May be <code>null</code>, in which case the
* event target is ignored.
* @return <code>true</code> if the given event qualifies as a match
* according to the <code>matcher</code> and
* </code>expectedTarget</code>.
*/
private boolean isEventMatch(
Event e, IEventMatcher matcher, EventTarget expectedTarget) {
return e != null
&& isComponentMatch(expectedTarget, e.getTarget())
&& matcher.isMatching(e);
}
/**
*
* @param expectedTarget The expected event target (Graphics Component).
* May be <code>null</code>, in which case
* <code>true</code> will be returned.
* @param actualTarget The actual event target. May be <code>null</code>,
* in which case <code>false</code> will be returned
* <em>unless</em> <code>expectedTarget</code> is
* also <code>null</code>.
* @return <code>true</code> if <code>actualTarget</code> matches
* <code>expectedTarget</code>.
*/
private boolean isComponentMatch(
EventTarget expectedTarget, EventTarget actualTarget) {
if (expectedTarget == null) {
return true;
}
EventTarget currentExpectedTarget = expectedTarget;
while (currentExpectedTarget != null) {
if (currentExpectedTarget == actualTarget) {
return true;
}
currentExpectedTarget = ParentGetter.get(currentExpectedTarget);
}
EventTarget currentActualTarget = actualTarget;
while (currentActualTarget != null) {
if (currentActualTarget == expectedTarget) {
return true;
}
currentActualTarget = ParentGetter.get(currentActualTarget);
}
return false;
}
/**
* Enables or disables the confirmer. If the confirmer is enabled, the
* JavaFX Filter is added to the currently focused stage so that the
* confirmer starts storing events of the configured class of events. If it
* is disabled, the listener is removed from the AWT event queue.
*
* @param enabled
* <code>true</code> or <code>false</code>.
*/
public void setEnabled(boolean enabled) {
m_enabled = enabled;
m_eventList.clear();
if (m_enabled) {
final long[] masks = m_options.getEventMask();
for (int i = 0; i < masks.length; i++) {
for (final ReadOnlyObjectProperty<? extends Window> w
: m_sceneGraphs) {
if (w.getValue() == null || !(w.getValue().isShowing())) {
// Removing this property from the list because the
// window it belongs to is not present.
m_sceneGraphs.remove(w);
continue;
}
final Window win = w.get();
final long mask = masks[i];
final RobotEventConfirmerJavaFXImpl me = this;
EventThreadQueuerJavaFXImpl.invokeAndWait(
"Add EventFilter for conforming", //$NON-NLS-1$
new Callable<Void>() {
@Override
public Void call() throws Exception {
win.addEventFilter(
JavaFXEventConverter.awtToFX(mask),
new WeakEventHandler<>(me));
Scene s = win.getScene();
if (s != null
&& s.getFocusOwner() != null) {
s.getFocusOwner().addEventFilter(
JavaFXEventConverter
.awtToFX(mask),
new WeakEventHandler<>(me));
}
return null;
}
});
}
}
} else {
long[] masks = m_options.getEventMask();
for (int i = 0; i < masks.length; i++) {
for (ReadOnlyObjectProperty<? extends Window> w
: m_sceneGraphs) {
if (w.getValue() == null || !(w.getValue().isShowing())) {
// Removing this property from the list because the
// window it belongs to is not present.
m_sceneGraphs.remove(w);
continue;
}
final Window win = w.get();
final long mask = masks[i];
final RobotEventConfirmerJavaFXImpl me = this;
EventThreadQueuerJavaFXImpl.invokeAndWait(
"Remove EventFilter for conforming", //$NON-NLS-1$
new Callable<Void>() {
@Override
public Void call() throws Exception {
win.removeEventFilter(
JavaFXEventConverter.awtToFX(mask),
new WeakEventHandler<>(me));
Scene s = win.getScene();
if (s != null
&& s.getFocusOwner() != null) {
s.getFocusOwner().removeEventFilter(
JavaFXEventConverter
.awtToFX(mask),
new WeakEventHandler<>(me));
}
return null;
}
});
}
}
}
}
@Override
public void handle(Event event) {
try {
m_eventList.put(event);
} catch (InterruptedException e) {
log.error("InterruptedException: " + event); //$NON-NLS-1$
}
}
}