/*******************************************************************************
* Copyright (c) 2012 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
*******************************************************************************/
package org.eclipse.jubula.rc.swing.tester;
import static org.eclipse.jubula.rc.common.driver.CheckWithTimeoutQueuer.invokeAndWait;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.util.ConcurrentModificationException;
import java.util.Set;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.components.AUTComponent;
import org.eclipse.jubula.rc.common.driver.IRobot;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.listener.EventLock;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.tester.AbstractApplicationTester;
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
import org.eclipse.jubula.rc.common.util.MatchUtil;
import org.eclipse.jubula.rc.common.util.Verifier;
import org.eclipse.jubula.rc.swing.listener.ComponentHandler;
import org.eclipse.jubula.rc.swing.listener.FocusTracker;
import org.eclipse.jubula.rc.swing.tester.util.EventListener;
import org.eclipse.jubula.rc.swing.tester.util.WindowHelper;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
import org.eclipse.jubula.tools.internal.utils.TimeUtil;
/**
*
* @author BREDEX GmbH
*
*/
public class SwingApplicationTester extends AbstractApplicationTester {
/**
* This condition is true if the event is an 'window opened' event
* and the event source is a frame/dialog with a certain title.
* It is also true if the event is a 'component shown' event and the
* event source is a frame/dialog with a certain title.
*/
private static class WindowOpenedCondition
implements EventListener.Condition {
/**
* the title
*/
private final String m_title;
/** the matches operation */
private final String m_operator;
/**
* constructor
*
* @param title the title
* @param operator the matches operation
*/
public WindowOpenedCondition(String title, String operator) {
m_title = title;
m_operator = operator;
}
/**
* {@inheritDoc}
*/
public boolean isTrue(AWTEvent event) {
if (event.getID() != WindowEvent.WINDOW_OPENED
&& event.getID() != ComponentEvent.COMPONENT_SHOWN) {
return false;
}
if (event.getSource() instanceof Frame) {
Frame frame = (Frame)event.getSource();
return MatchUtil.getInstance().match(
frame.getTitle(), m_title, m_operator);
} else if (event.getSource() instanceof Dialog) {
Dialog dialog = (Dialog)event.getSource();
return MatchUtil.getInstance().match(
dialog.getTitle(), m_title, m_operator);
} else {
return false;
}
}
}
/**
* This condition is true if the event is an 'window activated' event
* and the event source is a frame/dialog with a certain title.
*/
private static class WindowActivatedCondition
implements EventListener.Condition {
/**
* the title
*/
private final String m_title;
/** the matches operation */
private final String m_operator;
/**
* constructor
*
* @param title the title
* @param operator the matches operation
*/
public WindowActivatedCondition(String title, String operator) {
m_title = title;
m_operator = operator;
}
/**
* {@inheritDoc}
*/
public boolean isTrue(AWTEvent event) {
if (event.getID() != WindowEvent.WINDOW_ACTIVATED) {
return false;
}
if (event.getSource() instanceof Frame) {
Frame frame = (Frame)event.getSource();
return MatchUtil.getInstance().match(
frame.getTitle(), m_title, m_operator);
} else if (event.getSource() instanceof Dialog) {
Dialog dialog = (Dialog)event.getSource();
return MatchUtil.getInstance().match(
dialog.getTitle(), m_title, m_operator);
} else {
return false;
}
}
}
/**
* This condition is true if the event is an 'window closed' event
* and the event source is a frame/dialog with a certain title.
* It is also true if the event is a 'component hidden' event and the
* event source is a frame/dialog with a certain title.
*/
private static class WindowClosedCondition
implements EventListener.Condition {
/**
* the title
*/
private final String m_title;
/** the matches operation */
private final String m_operator;
/**
* constructor
*
* @param title the title
* @param operator the matches operation
*/
public WindowClosedCondition(String title, String operator) {
m_title = title;
m_operator = operator;
}
/**
* {@inheritDoc}
*/
public boolean isTrue(AWTEvent event) {
// we use lost focus, because it is triggered independently of hiding the window
// by setVisible(false) or dispose()
if (event.getID() == WindowEvent.WINDOW_LOST_FOCUS) {
if (event.getSource() instanceof Window) {
Window window = (Window) event.getSource();
if (!window.isVisible()) {
String name = null;
if (window instanceof Frame) {
Frame frame = (Frame) window;
name = frame.getTitle();
} else if (window instanceof Dialog) {
Dialog dialog = (Dialog) window;
name = dialog.getTitle();
} else {
// Window found, but we currently do not support it, because it has no title
return false;
}
return MatchUtil.getInstance()
.match(name, m_title, m_operator);
}
}
}
return false;
}
}
/**
* The logging.
*/
private static AutServerLogger log =
new AutServerLogger(SwingApplicationTester.class);
/**
* {@inheritDoc}
*/
public String[] getTextArrayFromComponent() {
return null;
}
/**
* Waits <code>timeMillSec</code> if the application opens a window
* with the given title.
*
* @param title the title
* @param operator the comparing operator
* @param pTimeout the time in ms
* @param delay delay after the window is shown
*/
public void rcWaitForWindow(final String title, String operator,
int pTimeout, int delay) {
EventListener.Condition cond =
new WindowOpenedCondition(title, operator);
EventLock lock = new EventLock();
AWTEventListener listener = new EventListener(lock, cond);
Toolkit.getDefaultToolkit().addAWTEventListener(listener,
AWTEvent.WINDOW_EVENT_MASK | AWTEvent.COMPONENT_EVENT_MASK);
if (isWindowOpen(title, operator)) {
lock.release();
}
try {
synchronized (lock) {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
while (!lock.isReleased() && (timeout > 0)) {
try {
lock.wait(timeout);
now = System.currentTimeMillis();
timeout = done - now;
} catch (InterruptedException e) {
// ignore
}
}
}
} finally {
Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
}
if (!lock.isReleased() && !isWindowOpen(title, operator)) {
throw new StepExecutionException("window did not open", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
/**
* Waits <code>timeMillSec</code> if the application activates a window
* with the given title.
*
* @param title the title
* @param operator the comparing operator
* @param pTimeout the time in ms
* @param delay delay after the window is activated
*/
public void rcWaitForWindowActivation(final String title, String operator,
int pTimeout, int delay) {
EventListener.Condition cond = new WindowActivatedCondition(title,
operator);
EventLock lock = new EventLock();
AWTEventListener listener = new EventListener(lock, cond);
Toolkit.getDefaultToolkit().addAWTEventListener(listener,
AWTEvent.WINDOW_EVENT_MASK);
if (isWindowActive(title, operator)) {
lock.release();
}
try {
synchronized (lock) {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
while (!lock.isReleased() && (timeout > 0)) {
try {
lock.wait(timeout);
now = System.currentTimeMillis();
timeout = done - now;
} catch (InterruptedException e) {
// ignore
}
}
}
} finally {
Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
}
if (!lock.isReleased() && !isWindowActive(title, operator)) {
throw new StepExecutionException("window was not activated", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
/**
* Waits <code>timeMillSec</code> if the application closes (or hides)
* a window with the given title. If no window with the given title can
* be found, then it is assumed that the window has already closed.
*
* @param title the title
* @param operator the comparing operator
* @param pTimeout the time in ms
* @param delay delay after the window is closed
*/
public void rcWaitForWindowToClose(final String title, String operator,
int pTimeout, int delay) {
EventListener.Condition cond =
new WindowClosedCondition(title, operator);
EventLock lock = new EventLock();
AWTEventListener listener = new EventListener(lock, cond);
Toolkit.getDefaultToolkit().addAWTEventListener(listener,
AWTEvent.WINDOW_EVENT_MASK);
if (!isWindowOpen(title, operator)) {
lock.release();
}
try {
synchronized (lock) {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
while (!lock.isReleased() && (timeout > 0)) {
try {
lock.wait(timeout);
now = System.currentTimeMillis();
timeout = done - now;
} catch (InterruptedException e) {
// ignore
}
}
}
} finally {
Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
}
if (!lock.isReleased() && isWindowOpen(title, operator)) {
throw new StepExecutionException("window did not close", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
/**
* Checks for the existence of a window with the given title
*
* @param title
* the title
* @param operator
* the comparing operator
* @param exists
* <code>True</code> if the window is expected to exist and be
* visible, otherwise <code>false</code>.
* @param timeout the amount of time to wait for the existence of the
* window to be checked
*/
public void rcCheckExistenceOfWindow(final String title,
final String operator, final boolean exists, int timeout) {
invokeAndWait("rcCheckExistenceOfWindow", timeout, new Runnable() { //$NON-NLS-1$
public void run() {
Verifier.equals(exists, isWindowOpen(title, operator));
}
});
}
/**
* {@inheritDoc}
*/
public Rectangle getActiveWindowBounds() {
Window activeWindow = WindowHelper.getActiveWindow();
if (activeWindow != null) {
Rectangle activeWindowBounds =
new Rectangle(activeWindow.getBounds());
activeWindowBounds.setLocation(activeWindow.getLocationOnScreen());
return activeWindowBounds;
}
return null;
}
/**
* {@inheritDoc}
*/
protected IRobot getRobot() {
return AUTServer.getInstance().getRobot();
}
/**
* perform a keystroke specified according <a
* href=http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)>
* string representation of a keystroke </a>,
*
* @param modifierSpec the string representation of the modifiers
* @param keySpec the string representation of the key
*/
public void rcKeyStroke(String modifierSpec, String keySpec) {
if (keySpec == null || keySpec.trim().length() == 0) {
throw new StepExecutionException(
"The base key of the key stroke must not be null or empty", //$NON-NLS-1$
EventFactory.createActionError());
}
String key = keySpec.trim().toUpperCase();
String mod = KeyStrokeUtil.getModifierString(modifierSpec);
if (mod.length() > 0) {
getRobot().keyStroke(mod.toString() + " " + key); //$NON-NLS-1$
} else {
int code = getKeyCode(key);
if (code != -1) {
rcKeyType(code);
} else {
getRobot().keyStroke(key);
}
}
}
/**
* {@inheritDoc}
*/
protected Object getFocusOwner() {
return FocusTracker.getFocusOwner();
}
/**
* {@inheritDoc}
*/
protected int getEventCode(int key) {
int event = 0;
switch (key) {
case 1 :
event = KeyEvent.VK_NUM_LOCK;
break;
case 2 :
event = KeyEvent.VK_CAPS_LOCK;
break;
case 3 :
event = KeyEvent.VK_SCROLL_LOCK;
break;
default :
break;
}
return event;
}
/**
* {@inheritDoc}
*/
protected Object getActiveWindow() {
return WindowHelper.getActiveWindow();
}
/**
* Returns <code>true</code> if a window with the given title is open and
* visible.
*
* @param title the title
* @param operator the matches/equals operator
* @return if the window is open and visible
*/
private boolean isWindowOpen(String title, String operator) {
boolean wasInterrupted;
do {
try {
wasInterrupted = false;
Set<? extends AUTComponent> components = ComponentHandler
.getAutHierarchy().getHierarchyMap().keySet();
for (AUTComponent component : components) {
Component c = ((AUTComponent<Component>) component)
.getComponent();
if (c.isShowing()) {
if (c instanceof Frame) {
Frame frame = (Frame) c;
if (MatchUtil.getInstance().match(frame.getTitle(),
title, operator)) {
return true;
}
}
if (c instanceof Dialog) {
Dialog dialog = (Dialog) c;
if (MatchUtil.getInstance().match(
dialog.getTitle(), title, operator)) {
return true;
}
}
}
}
} catch (ConcurrentModificationException e) {
log.debug("hierarchy modified while traversing", e); //$NON-NLS-1$
wasInterrupted = true;
}
} while (wasInterrupted);
return false;
}
/**
* Returns <code>true</code> if a window with the given title has focus
*
* @param title the title
* @param operator the matches/equals operator
* @return if the window has focus
*/
private boolean isWindowActive(String title, String operator) {
Window activeWindow = WindowHelper.getActiveWindow();
if (activeWindow != null) {
String windowTitle = null;
if (activeWindow instanceof Dialog) {
windowTitle = ((Dialog)activeWindow).getTitle();
} else if (activeWindow instanceof Frame) {
windowTitle = ((Frame)activeWindow).getTitle();
}
if (MatchUtil.getInstance().match(windowTitle, title, operator)) {
return true;
}
}
return false;
}
/**
* @param keyCodeName
* The name of a key code, e.g. <code>TAB</code> for a
* tabulator key code
* @return The key code or <code>-1</code>, if the key code name doesn't
* exist in the <code>KeyEvent</code> class
* @throws StepExecutionException
* If the key code name cannot be converted to a key code due to
* the reflection call
*/
public int getKeyCode(String keyCodeName) throws StepExecutionException {
int code = -1;
String codeName = "VK_" + keyCodeName; //$NON-NLS-1$
try {
code = KeyEvent.class.getField(codeName).getInt(KeyEvent.class);
} catch (IllegalArgumentException e) {
throw new StepExecutionException(e.getMessage(), EventFactory
.createActionError());
} catch (SecurityException e) {
throw new StepExecutionException(e.getMessage(), EventFactory
.createActionError());
} catch (IllegalAccessException e) {
throw new StepExecutionException(e.getMessage(), EventFactory
.createActionError());
} catch (NoSuchFieldException e) {
if (log.isInfoEnabled()) {
log.info("The key expression '" + keyCodeName //$NON-NLS-1$
+ "' is not a key code, typed as key stroke instead"); //$NON-NLS-1$
}
}
return code;
}
}