/******************************************************************************* * 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.swing; import java.awt.AWTError; import java.awt.AWTEvent; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.lang.reflect.InvocationTargetException; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.jubula.rc.common.AUTServer; import org.eclipse.jubula.rc.common.driver.IRobot; import org.eclipse.jubula.rc.common.driver.IRobotFactory; import org.eclipse.jubula.rc.common.exception.ComponentNotFoundException; import org.eclipse.jubula.rc.common.listener.BaseAUTListener; import org.eclipse.jubula.rc.swing.driver.RobotFactoryConfig; import org.eclipse.jubula.rc.swing.listener.CheckListener; import org.eclipse.jubula.rc.swing.listener.ComponentHandler; import org.eclipse.jubula.rc.swing.listener.FocusTracker; import org.eclipse.jubula.rc.swing.listener.MappingListener; import org.eclipse.jubula.rc.swing.listener.RecordListener; import org.eclipse.jubula.tools.internal.constants.AUTServerExitConstants; import org.eclipse.jubula.tools.internal.objects.IComponentIdentifier; import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The AutServer controlling the AUT. <br> * A quasi singleton: the instance is created from main(). <br> * Expected arguments to main are, see also * StartAUTServerCommand.createCmdArray(): * <ul> * <li>The name of host the client is running on, must be InetAddress * conform.</li> * <li>The port the JubulaClient is listening to.</li> * <li>The main class of the AUT.</li> * <li>Any further arguments are interpreted as arguments to the AUT.</li> * <ul> * When a connection to the JubulaClient could made, any errors will send as a * message to the JubulaClient. * * Changing the mode to OBJECT_MAPPING results in installing an AWTEventListener * (an instance of <code>MappingListener</code>). For simplification the * virtual machine is closed without sending a message to the client when an * error occurs during the installation of the AWTEventListener. The exit code is * the appropriate EXIT_* constant * * Changing the mode to TESTING removes the installed MappingListener. * * @author BREDEX GmbH * @created 26.07.2004 */ public class SwingAUTServer extends AUTServer { /** the logger */ private static final Logger LOG = LoggerFactory.getLogger(SwingAUTServer.class); /** * name of Environment Variable or Java Property that defines the * regular expression to use when waiting for the Event Dispatch Thread * to start */ private static final String EDT_NAME_REGEX_KEY = "TEST_EDT_NAME_REGEX"; //$NON-NLS-1$ /** * private constructor * instantiates the listeners */ public SwingAUTServer() { super(new MappingListener(), new RecordListener(), new CheckListener()); } /** * Starts the AWT-EventQueue-Thread. <br> * <b>Important:</b> Must be called in complete AUT environment! * (Thread, ClassLoader, etc.) */ protected void startToolkitThread() { // add a dummy listener to start the AWT-Thread Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener() { public void eventDispatched(AWTEvent event) { // do nothing } }, 0L); } /** * {@inheritDoc} */ protected void addToolkitEventListeners() { // install the component handler addToolkitEventListener(new ComponentHandler()); // install the focus tracker addToolkitEventListener(new FocusTracker()); } /** * {@inheritDoc} */ protected void addToolkitEventListener(BaseAUTListener listener) { if (LOG.isInfoEnabled()) { LOG.info("installing AWTEventListener " //$NON-NLS-1$ + listener.toString()); } try { long mask = 0; for (int i = 0; i < listener.getEventMask().length; i++) { mask = mask | listener.getEventMask()[i]; } Toolkit.getDefaultToolkit().addAWTEventListener( (AWTEventListener)listener, mask); } catch (AWTError awte) { // no default toolkit LOG.error(awte.getLocalizedMessage(), awte); } catch (SecurityException se) { // no permission to add an AWTEventListener LOG.error(se.getLocalizedMessage(), se); System.exit(AUTServerExitConstants .EXIT_SECURITY_VIOLATION_AWT_EVENT_LISTENER); } } /** * {@inheritDoc} */ protected void removeToolkitEventListener(BaseAUTListener listener) { if (LOG.isInfoEnabled()) { LOG.info("removing AWTEventListener " //$NON-NLS-1$ + listener.toString()); } try { Toolkit.getDefaultToolkit().removeAWTEventListener( (AWTEventListener)listener); } catch (AWTError awte) { // no default toolkit LOG.error(awte.getLocalizedMessage(), awte); } catch (SecurityException se) { // no permission to remove an AWTEventListener, // should not occur, because addAWTEventListener() should be called // first. But just in case, close the vm LOG.error(se.getLocalizedMessage(), se); System.exit(AUTServerExitConstants .EXIT_SECURITY_VIOLATION_AWT_EVENT_LISTENER); } } /** * {@inheritDoc} */ protected void startTasks() throws ExceptionInInitializerError, InvocationTargetException, NoSuchMethodException { String edtNameRegEx = EnvironmentUtils .getProcessOrSystemProperty(EDT_NAME_REGEX_KEY); if (edtNameRegEx != null) { // fail fast if the regex is malformed try { Pattern.compile(edtNameRegEx); } catch (PatternSyntaxException pse) { throw new InvocationTargetException(pse, "Invalid " + EDT_NAME_REGEX_KEY + " value."); //$NON-NLS-1$ //$NON-NLS-2$ } final String accessibleEdtNameRegEx = edtNameRegEx; Thread addListenersThread = new Thread("Register initial Jubula Swing / AWT listeners") { //$NON-NLS-1$ public void run() { boolean isThreadFound = false; ThreadGroup rootThreadGroup = Thread.currentThread().getThreadGroup(); while (rootThreadGroup.getParent() != null) { rootThreadGroup = rootThreadGroup.getParent(); } while (!isThreadFound) { Thread[] activeThreads = getActiveThreads(); for (int i = 0; i < activeThreads.length; i++) { if (activeThreads[i].getName().matches( accessibleEdtNameRegEx)) { isThreadFound = true; break; } } try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing. the next loop iteration will simply // occur earlier than expected, which is not a // problem. } } addToolKitEventListenerToAUT(); } }; addListenersThread.setDaemon(true); addListenersThread.start(); } else { startToolkitThread(); addToolKitEventListenerToAUT(); } AUTServer.getInstance().invokeAUT(); } /** * {@inheritDoc} */ public IRobot getRobot() { IRobotFactory robotFactory = new RobotFactoryConfig().getRobotFactory(); return robotFactory.getRobot(); } /** * Adapted from LGPLed code from an online Java article. * Modified to work with Java 1.4 and pass Jubula's checkstyle. * * @return all currently active threads. * @see http://nadeausoftware.com/articles/2008/04/java_tip_how_list_and_find_threads_and_thread_groups */ private static Thread[] getActiveThreads() { ThreadGroup rootThreadGroup = Thread.currentThread().getThreadGroup(); while (rootThreadGroup.getParent() != null) { rootThreadGroup = rootThreadGroup.getParent(); } int nAlloc = rootThreadGroup.activeCount(); int n = 0; Thread[] threads; do { nAlloc *= 2; threads = new Thread[ nAlloc ]; n = rootThreadGroup.enumerate(threads); } while (n == nAlloc); Thread[] returnArray = new Thread[n]; System.arraycopy(threads, 0, returnArray, 0, n); return returnArray; } /** * {@inheritDoc} */ public Object findComponent(IComponentIdentifier ci, int timeout) throws ComponentNotFoundException, IllegalArgumentException { return ComponentHandler.findComponent(ci, true, timeout); } /** * {@inheritDoc} */ @Override public boolean isComponentDisappeared(IComponentIdentifier ci, int timeout) throws ComponentNotFoundException, IllegalArgumentException { return ComponentHandler.isComponentDisappeared(ci, timeout); } }