/******************************************************************************* * 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.listener; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Window; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.WindowEvent; import java.util.EventListener; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang.SystemUtils; import org.eclipse.jubula.communication.internal.message.ChangeAUTModeMessage; import org.eclipse.jubula.rc.common.AUTServer; import org.eclipse.jubula.rc.common.exception.ComponentNotFoundException; import org.eclipse.jubula.rc.common.exception.ComponentNotManagedException; import org.eclipse.jubula.rc.common.exception.NoIdentifierForComponentException; import org.eclipse.jubula.rc.common.listener.BaseAUTListener; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.rc.swing.components.AUTSwingHierarchy; import org.eclipse.jubula.tools.internal.constants.TimingConstantsServer; import org.eclipse.jubula.tools.internal.exception.InvalidDataException; import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs; import org.eclipse.jubula.tools.internal.objects.IComponentIdentifier; import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils; import org.eclipse.jubula.tools.internal.utils.TimeUtil; /** * This class is responsible for handling the components of the AUT. <br> * This class implements the AWTEventListener interface, listening to * <code>WindowEvent.WINDOW_OPENED</code>. * * A instance of <code>AUTHierarchy</code> is notified for WindowEvents. <br> * * The static methods for fetching an identifier for a component and getting the * component for an identifer delegates to this AUTHierarchy. * * @author BREDEX GmbH * @created 24.08.2004 */ public class ComponentHandler extends BaseAWTEventListener implements BaseAUTListener { /** the logger */ private static AutServerLogger log = new AutServerLogger( ComponentHandler.class); /** the event mask for the events this listener is interesting in */ private static final long[] EVENT_MASK = new long[]{ AWTEvent.WINDOW_EVENT_MASK, AWTEvent.CONTAINER_EVENT_MASK, AWTEvent.COMPONENT_EVENT_MASK}; /** * Name of System Property to print thread trace output when a component * cannot be found. This can be used in order to track down racing * conditions in the RC. * * We do not advertise this property. It may be used internally for * tracking down tricky timing issues, but there's no real other use case * for it. It may be removed at any time. */ private static final String PROP_TRACE_COMPONENT_NOT_FOUND = "org.eclipse.jubula.rc.traceComponentNotFound"; //$NON-NLS-1$ /** * Whether to print thread trace output when a component cannot be found. * This can be used in order to track down racing conditions in the RC. */ private static final boolean TRACE_COMPONENT_NOT_FOUND = Boolean.parseBoolean(EnvironmentUtils.getProcessOrSystemProperty( PROP_TRACE_COMPONENT_NOT_FOUND)); /** the Container hierarchy of the AUT*/ private static AUTSwingHierarchy autHierarchy = new AUTSwingHierarchy(); /** * Investigates the given <code>component</code> for an identifier. It * must be distinct for the whole AUT. To obtain this identifier the * AUTHierarchy is queried. * @param component the component to get an identifier for * @throws NoIdentifierForComponentException if an identifer could not created for <code>component</code>. * @return the identifier, containing the identification */ public static IComponentIdentifier getIdentifier(Component component) throws NoIdentifierForComponentException { try { return autHierarchy.getComponentIdentifier(component); } catch (ComponentNotManagedException cnme) { log.warn(cnme); throw new NoIdentifierForComponentException( "unable to create an identifier for '" //$NON-NLS-1$ + component + "'", //$NON-NLS-1$ MessageIDs.E_COMPONENT_ID_CREATION); } } /** * returns an array of all componentIdentifier of (supported) components, * which are currently instantiated by the AUT. <br> * delegate to AUTHierarchy.getAllComponentId() * * @return array with componentIdentifier, never null */ public static IComponentIdentifier[] getAllComponentId() { return autHierarchy.getAllComponentId(); } /** * Searchs the component in the AUT, which belongs to the given * <code>componentIdentifier</code>. * * @param componentIdentifier * the identifier of the component to search for * @param retry number of tries to get object * @param timeout * timeout for retries * @throws ComponentNotFoundException * if no component is found for the given identifier. * @throws IllegalArgumentException * if the identifier is null or contains invalid data * {@inheritDoc} * @return the found component */ public static Component findComponent( IComponentIdentifier componentIdentifier, boolean retry, int timeout) throws ComponentNotFoundException, IllegalArgumentException { long start = System.currentTimeMillis(); // FIXME : waitForComponent try { return autHierarchy.findComponent(componentIdentifier); } catch (ComponentNotManagedException cnme) { if (retry) { while (System.currentTimeMillis() - start < timeout) { try { Thread.sleep(TimingConstantsServer .POLLING_DELAY_FIND_COMPONENT); return autHierarchy.findComponent(componentIdentifier); } catch (InterruptedException e) { // ok } catch (ComponentNotManagedException e) { // NOPMD by zeb on 10.04.07 15:25 // OK, we will throw a corresponding exception later // if we really can't find the component } catch (InvalidDataException ide) { // NOPMD by zeb on 10.04.07 15:25 // OK, we will throw a corresponding exception later // if we really can't find the component } } } logStacktrace(); throw new ComponentNotFoundException( cnme.getMessage(), MessageIDs.E_COMPONENT_NOT_FOUND); } catch (IllegalArgumentException iae) { log.error(iae); throw iae; } catch (InvalidDataException ide) { log.error(ide); throw new ComponentNotFoundException( ide.getMessage(), MessageIDs.E_COMPONENT_NOT_FOUND); } } /** * Checks the component in the AUT, which belongs to the given * <code>componentIdentifier</code> is disappeared or nor. * * @param componentIdentifier * the identifier of the component to search for * @param timeout * timeout for retry * @throws ComponentNotFoundException * if no component is found for the given identifier. * @throws IllegalArgumentException * if the identifier is null or contains invalid data * {@inheritDoc} * @return true if the component is disappeared else false */ public static boolean isComponentDisappeared( IComponentIdentifier componentIdentifier, int timeout) throws ComponentNotFoundException, IllegalArgumentException { long start = System.currentTimeMillis(); try { Component component = autHierarchy .findComponent(componentIdentifier); while (System.currentTimeMillis() - start < timeout) { TimeUtil.delay( TimingConstantsServer.POLLING_DELAY_FIND_COMPONENT); boolean isComponentDisappeared = !autHierarchy .isComponentInHierarchy(component); if (isComponentDisappeared) { return true; } } return false; } catch (ComponentNotManagedException cnme) { logStacktrace(); return true; } catch (IllegalArgumentException iae) { log.error(iae); throw iae; } catch (InvalidDataException ide) { log.error(ide); throw new ComponentNotFoundException( ide.getMessage(), MessageIDs.E_COMPONENT_NOT_FOUND); } } /** * {@inheritDoc} */ public long[] getEventMask() { long[] eventMask = EVENT_MASK; // see findBugs return eventMask; } // implementing method from interface AWTEventListener /** * {@inheritDoc} */ public void eventDispatched(AWTEvent event) { final ClassLoader originalCL = Thread.currentThread() .getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.getClass() .getClassLoader()); try { if (log.isDebugEnabled()) { log.debug(event.paramString()); } final int id = event.getID(); ComponentEvent componentEvent; switch (id) { case WindowEvent.WINDOW_ACTIVATED: case WindowEvent.WINDOW_OPENED: // add recursivly all components to AUTHierarchy // and create names for unnamed components Window window = ((WindowEvent) event).getWindow(); autHierarchy.add(window); break; case ContainerEvent.COMPONENT_ADDED: checkContainerListener((ContainerEvent)event); break; case ComponentEvent.COMPONENT_HIDDEN: componentEvent = (ComponentEvent)event; if (!hasListener(componentEvent.getComponent(), ComponentListener.class)) { autHierarchy.componentHidden(componentEvent); } break; case ComponentEvent.COMPONENT_SHOWN: componentEvent = (ComponentEvent)event; if (!hasListener(componentEvent.getComponent(), ComponentListener.class)) { autHierarchy.componentShown(componentEvent); } break; default: // do nothing } if (AUTServer.getInstance().getMode() == ChangeAUTModeMessage.OBJECT_MAPPING) { AUTServer.getInstance().updateHighLighter(); } } catch (Throwable t) { log.error("exception during ComponentHandler", t); //$NON-NLS-1$ } finally { Thread.currentThread().setContextClassLoader(originalCL); } } /** * Checks if there is already a listener of an AUTHierarchy instance * at the container of the added component. If there is no listener and if the * container is known by the AUTHierarchy, the component will be added * to the AUTHierarchy via this method by calling the componentAdded method * of the AUTHierarchy. <br> <br> * <b>Note:</b> This is only a workaround, because some applications may * clean the listeners of the container inclusive the listeners, so * we cannot notice added components anymore. * Therefor the global AWTEventListener at the Toolkit has to check this. * * @param event the ContainerEvent.COMPONENT_ADDED */ private void checkContainerListener(ContainerEvent event) { if (!hasListener(event.getContainer(), ContainerListener.class) && (autHierarchy.getHierarchyContainer(event.getContainer()) != null)) { if (log.isInfoEnabled()) { log.info("ComponentHandler called: autHierarchy.componentAdded"); //$NON-NLS-1$ } autHierarchy.componentAdded(event); } } /** * @param component the component to check for a listener. * @param listenerClass The class of listener for which to check. * @return <code>true</code> if an <code>AUTSwingHierarchy</code> is * registered as a <code>listenerClass</code> listener on the * given <code>component</code>. Otherwise, <code>false</code>. */ private boolean hasListener( Component component, Class<? extends EventListener> listenerClass) { EventListener[] listener = component.getListeners(listenerClass); int length = listener.length; for (int i = 0; i < length; i++) { if (listener[i] instanceof AUTSwingHierarchy) { return true; } } return false; } /** * * @return the AUT Hierarchy */ public static AUTSwingHierarchy getAutHierarchy() { return autHierarchy; } /** * Pretty prints the stack traces of all currently running threads to the * log. */ private static void logStacktrace() { if (TRACE_COMPONENT_NOT_FOUND) { StringBuilder builder = new StringBuilder(); builder.append("Logging stacktrace:" + SystemUtils.LINE_SEPARATOR); //$NON-NLS-1$ Thread currentThread = Thread.currentThread(); Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces(); for (Entry<Thread, StackTraceElement[]> stackTrace : stackTraces.entrySet()) { Thread thread = stackTrace.getKey(); if (thread == currentThread) { builder.append("[current-thread] - "); //$NON-NLS-1$ } builder.append(thread.getName() + ":" + SystemUtils.LINE_SEPARATOR); //$NON-NLS-1$ for (StackTraceElement e : stackTrace.getValue()) { builder.append("\t" + e + SystemUtils.LINE_SEPARATOR); //$NON-NLS-1$ } } builder.append(SystemUtils.LINE_SEPARATOR); log.warn(builder); } } }