/*******************************************************************************
* 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.components;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.eclipse.jubula.rc.common.AUTServerConfiguration;
import org.eclipse.jubula.rc.common.components.AUTComponent;
import org.eclipse.jubula.rc.common.components.AUTHierarchy;
import org.eclipse.jubula.rc.common.components.HierarchyContainer;
import org.eclipse.jubula.rc.common.exception.ComponentNotManagedException;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.javafx.listener.ComponentHandler;
import org.eclipse.jubula.tools.internal.exception.InvalidDataException;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.eclipse.jubula.tools.internal.objects.ComponentIdentifier;
import org.eclipse.jubula.tools.internal.objects.IComponentIdentifier;
import javafx.event.EventTarget;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
import javafx.stage.Window;
/**
* This class holds a hierarchy of the components of the AUT. <br>
*
* The hierarchy is composed with <code>JavaFXHierarchyContainer</code>s. For
* every component from the AUT a hierarchy container is created. The names for
* the components are stored in the appropriate hierarchy containers.<br>
*
* @author BREDEX GmbH
* @created 10.10.2013
*
*/
public class AUTJavaFXHierarchy extends AUTHierarchy<EventTarget> {
/** the logger */
private static AutServerLogger log = new AutServerLogger(
AUTJavaFXHierarchy.class);
/** Businessprocess for getting components */
private static FindJavaFXComponentBP findBP = new FindJavaFXComponentBP();
/** The lock for accessing the Hierarchy **/
private static volatile ReentrantLock lock = new ReentrantLock(true);
/**
* Default constructor
*/
public AUTJavaFXHierarchy() {
}
/**
* Creates the Hierarchy from a given Object
*
* @param eventTarget
* the Object
*/
public void createHierarchyFrom(EventTarget eventTarget) {
lock.lock();
try {
Map<EventTarget, AUTComponent<EventTarget>> realMap = getRealMap();
EventTarget parent = ParentGetter.get(eventTarget);
EventTarget lastParent = parent;
while (parent != null) {
if (realMap.containsKey(parent)) {
lastParent = parent;
break;
}
lastParent = parent;
parent = ParentGetter.get(parent);
}
createHierarchy(lastParent == null ? eventTarget : lastParent);
} finally {
lock.unlock();
}
}
/**
* Adds <code>parent</code> and its sub-tree (children, children's
* children, etc) to the hierarchy.
*
* @param parent
* The component / sub-tree to add to the hierarchy. May be
* <code>null</code>, in which case this method returns without
* modifying the hierarchy.
*/
private void createHierarchy(EventTarget parent) {
if (parent == null) {
return;
}
final JavaFXHierarchyContainer parentCont;
if (getRealMap().containsKey(parent)) {
parentCont = getHierarchyContainer(parent);
} else {
parentCont = initContainer(parent);
name(parentCont);
addToHierachyMap(parentCont);
}
List<EventTarget> children = ChildrenGetter.getAsList(parent);
for (EventTarget child : children) {
createHierarchy(child);
JavaFXHierarchyContainer childCont = getHierarchyContainer(child);
if (!(parentCont.contains(childCont))) {
parentCont.add(childCont);
childCont.setPrnt(parentCont);
} else if (childCont.getPrnt() == null) {
childCont.setPrnt(parentCont);
}
name(childCont);
}
}
/**
* Convenience Method for creating Container.
*
* @param node
* the Object to create the Container for.
* @return the Container
*/
private JavaFXHierarchyContainer initContainer(EventTarget node) {
AUTComponent<EventTarget> comp = new JavaFXComponent(node);
JavaFXHierarchyContainer cont = new JavaFXHierarchyContainer(comp);
return cont;
}
/**
* Removes a component from the hierarchy. This means that the following
* references will be removed: <br>
* -Container of the component from the hierarchy Map <br>
* -The given component from the real map <br>
* -The reference from the parent container to the container of this
* component <br>
* -The actions mentioned above for all children of this component
*
* @param component
* the component that will be deleted
*/
public void removeComponentFromHierarchy(EventTarget component) {
if (component != null) {
JavaFXHierarchyContainer cont = getHierarchyContainer(component);
if (cont != null) {
removeContainer(cont);
}
}
}
/**
* @param comp the component to check, whether it's disappeared or not
* @return true, if the component disappeared
*/
public boolean isComponentInHierarchy(EventTarget comp) {
if (comp != null) {
return getHierarchyContainer(comp) != null;
}
return false;
}
/**
* Removes a container from the hierarchy. This means that the following
* references will be removed: <br>
* -Container from the hierarchy Map <br>
* -The component of the given container from the real map <br>
* -The reference from the parent container to the given container <br>
* -The actions mentioned above for all children of this container
*
* @param container
* the container that will be deleted
*/
public void removeContainer(HierarchyContainer<EventTarget> container) {
lock.lock();
try {
Map<? extends AUTComponent<EventTarget>,
HierarchyContainer<EventTarget>>
contMap = getHierarchyMap();
Map<EventTarget, AUTComponent<EventTarget>> realMap = getRealMap();
JavaFXComponent fxComp = (JavaFXComponent)container.getCompID();
fxComp.removeChangeListener();
contMap.remove(container.getCompID());
realMap.remove(fxComp.getComponent());
HierarchyContainer<EventTarget> parent = container.getPrnt();
if (parent != null) {
parent.remove(container);
}
HierarchyContainer<EventTarget>[] children = container.getComps();
for (HierarchyContainer<EventTarget> child : children) {
removeContainer(child);
}
} finally {
lock.unlock();
}
}
@Override
public IComponentIdentifier[] getAllComponentId() {
List<IComponentIdentifier> result = new Vector<IComponentIdentifier>();
Set<? extends AUTComponent<EventTarget>> keys =
getHierarchyMap().keySet();
for (Iterator<? extends AUTComponent<EventTarget>> itr = keys
.iterator(); itr.hasNext();) {
AUTComponent<EventTarget> wrapComp = itr.next();
EventTarget comp = wrapComp.getComponent();
try {
if (AUTServerConfiguration.getInstance().isSupported(comp)) {
result.add(getComponentIdentifier(comp));
}
} catch (IllegalArgumentException iae) {
// from isSupported -> log
log.error("hierarchy map contains null values", iae); //$NON-NLS-1$
// and continue
} catch (ComponentNotManagedException e) {
// from isSupported -> log
log.error("component '" + comp.getClass().getName() + "' not found!", e); //$NON-NLS-1$ //$NON-NLS-2$
// and continue
}
}
return result.toArray(new IComponentIdentifier[result.size()]);
}
/**
* Investigates the given <code>component</code> for an identifier. To
* obtain this identifier the name of the component and the container
* hierarchy is used.
*
* @param component
* the component to create an identifier for, must not be null.
* @throws ComponentNotManagedException
* if component is null or <br>
* (one of the) component(s) in the hierarchy is not managed
* @return the identifier for <code>component</code>
*/
public IComponentIdentifier getComponentIdentifier(EventTarget component)
throws ComponentNotManagedException {
IComponentIdentifier result = new ComponentIdentifier();
try {
// fill the componentIdentifier
result.setComponentClassName(component.getClass().getName());
result.setSupportedClassName(AUTServerConfiguration.getInstance()
.getTestableClass(component.getClass()).getName());
List<String> hierarchy = getPathToRoot(component);
result.setHierarchyNames(hierarchy);
result.setNeighbours(getComponentContext(component));
JavaFXHierarchyContainer cont = getHierarchyContainer(component);
setAlternativeDisplayName(cont, component, result);
if (component.equals(findBP.findComponent(result,
ComponentHandler.getAutHierarchy()))) {
result.setEqualOriginalFound(true);
}
return result;
} catch (IllegalArgumentException iae) {
// from getPathToRoot()
log.error(iae);
throw new ComponentNotManagedException(
"getComponentIdentifier() called for an unmanaged component: " //$NON-NLS-1$
+ component, MessageIDs.E_COMPONENT_NOT_MANAGED);
// let pass the ComponentNotManagedException from getPathToRoot()
}
}
/**
* Searches for the component in the AUT with the given
* <code>componentIdentifier</code>.
*
* @param componentIdentifier
* the identifier created in object mapping mode
* @throws IllegalArgumentException
* if the given identifier is null or <br>
* the hierarchy is not valid: empty or containing null elements
* @throws InvalidDataException
* if the hierarchy in the componentIdentifier does not consist
* of strings
* @throws ComponentNotManagedException
* if no component could be found for the identifier
* @return the instance of the component of the AUT
*/
public Object findComponent(IComponentIdentifier componentIdentifier)
throws IllegalArgumentException, ComponentNotManagedException,
InvalidDataException {
Object comp = findBP.findComponent(componentIdentifier,
ComponentHandler.getAutHierarchy());
if (comp != null) {
return comp;
}
throw new ComponentNotManagedException(
"unmanaged component with identifier: '" //$NON-NLS-1$
+ componentIdentifier.toString() + "'.", //$NON-NLS-1$
MessageIDs.E_COMPONENT_NOT_MANAGED);
}
/**
* Returns the path from the given component to root. The List contains
* Strings (the name of the containers).
*
* @param component
* the component to start, it's an instance from the AUT, must
* not be null
* @throws IllegalArgumentException
* if component is null
* @throws ComponentNotManagedException
* if no hierarchy container exists for the component
* @return the path to root, the first elements contains the root, the last
* element contains the component itself.
*/
public List<String> getPathToRoot(EventTarget component)
throws IllegalArgumentException, ComponentNotManagedException {
if (log.isInfoEnabled()) {
log.info("pathToRoot called for " + component); //$NON-NLS-1$
}
Validate.notNull(component, "The component must not be null"); //$NON-NLS-1$
ArrayList<String> hierarchy = new ArrayList<String>();
JavaFXHierarchyContainer container = getHierarchyContainer(component);
if (container == null) {
log.error("component '" + component //$NON-NLS-1$
+ "' is not managed by this hierarchy"); //$NON-NLS-1$
throw new ComponentNotManagedException("unmanaged component " //$NON-NLS-1$
+ component.toString(), MessageIDs.E_COMPONENT_NOT_MANAGED);
}
hierarchy.add(container.getName());
JavaFXHierarchyContainer parent = (JavaFXHierarchyContainer) container
.getPrnt();
while (parent != null) {
container = parent;
hierarchy.add(0, container.getName());
parent = (JavaFXHierarchyContainer) container.getPrnt();
}
return hierarchy;
}
@Override
protected List<String> getComponentContext(EventTarget component) {
JavaFXHierarchyContainer parent;
JavaFXHierarchyContainer comp;
List<String> context = new ArrayList<String>();
if (component instanceof JavaFXHierarchyContainer) {
comp = (JavaFXHierarchyContainer) component;
} else {
comp = getHierarchyContainer(component);
}
parent = (JavaFXHierarchyContainer) comp.getPrnt();
if (parent != null) {
HierarchyContainer<EventTarget>[] comps = parent.getComps();
for (HierarchyContainer<EventTarget> child : comps) {
if (!child.equals(comp)) {
String toAdd = child.getName();
context.add(toAdd);
}
}
}
return context;
}
/**
* Returns the hierarchy container for <code>component</code>.
*
* @param component
* the component from the AUT, must no be null
* @throws IllegalArgumentException
* if component is null
* @return the hierarchy container or null if the component is not yet
* managed
*/
public JavaFXHierarchyContainer getHierarchyContainer(EventTarget component)
throws IllegalArgumentException {
Validate.notNull(component, "The component must not be null"); //$NON-NLS-1$
JavaFXHierarchyContainer result = null;
try {
AUTComponent<EventTarget> compID = getRealMap().get(component);
if (compID != null) {
result = (JavaFXHierarchyContainer) getHierarchyMap().get(
compID);
}
} catch (ClassCastException cce) {
log.error(cce);
} catch (NullPointerException npe) {
log.error(npe);
}
return result;
}
/**
* Names the given hierarchy container. <br>
* If the managed component has a unique name, this name is used. Otherwise
* a name (unique for the hierarchy level) is created.
*
* @param hierarchyContainer
* the SwingHierarchyContainer to name, if
* SwingHierarchyContainer is null, no action is performed and no
* exception is thrown.
*/
protected void name(JavaFXHierarchyContainer hierarchyContainer) {
final AUTComponent<EventTarget> comp = hierarchyContainer.getCompID();
String compName;
EventTarget realComponent = comp.getComponent();
if (realComponent instanceof Node) {
compName = ((Node) realComponent).getId();
} else if (realComponent instanceof MenuItem) {
compName = ((MenuItem) realComponent).getId();
} else {
compName = null;
}
// Because stages don't have a list of children we can't include the
// relationship between stages in the hierarchy. Therefore this
// workaround is necessary to create a unique name for a stage container.
if (realComponent instanceof Window) {
List<Window> windows = CurrentStages.getWindowList();
ArrayList<String> names = new ArrayList<>();
for (Window win : windows) {
JavaFXHierarchyContainer c = getHierarchyContainer(win);
if (c != null && c != hierarchyContainer) {
names.add(c.getName());
}
}
String name = null;
int count = 0;
while (!isUniqueName(name, names)) {
count++;
name = createName(realComponent, count);
}
comp.setName(name);
hierarchyContainer.setName(name, true);
return;
}
HierarchyContainer<EventTarget> hierParent =
hierarchyContainer.getPrnt();
if (hierarchyContainer.getName() != null
&& hierarchyContainer.getName().length() != 0
&& !(hierarchyContainer.getName().trim().isEmpty())) {
// In extra if, for logging purposes
if (isUniqueName(hierParent, hierarchyContainer.getName(),
hierarchyContainer)) {
return;
} else if (log.isInfoEnabled()) {
log.info("New name created for " + hierarchyContainer.getName() //$NON-NLS-1$
+ "even though there was already a name!"); //$NON-NLS-1$
}
}
int count = 0;
String originalName = null;
String newName = null;
boolean newNameGenerated = (compName == null);
if (!StringUtils.isBlank(compName)) {
originalName = compName;
newName = compName;
}
if (newName == null) {
while (!isUniqueName(hierParent, newName, hierarchyContainer)) {
count++;
newName = createName(realComponent, count);
}
} else {
while (!isUniqueName(hierParent, newName, hierarchyContainer)) {
count++;
newName = createName(originalName, count);
}
}
comp.setName(newName);
hierarchyContainer.setName(newName, newNameGenerated);
}
/**
* Checks for uniqueness of <code>name</code> for the components in
* <code>parent</code>.<br>
* If parent is null every name is unique, a null name is NEVER unique. If
* both parameters are null, false is returned. <br>
*
* @param parent
* the hierarchy container containing the components which are
* checked.
* @param name
* the name to check
* @param container
* The component for which the name is being checked.
* @return true if the name is treated as unique, false otherwise.
*/
protected boolean isUniqueName(HierarchyContainer<EventTarget> parent,
String name, HierarchyContainer<EventTarget> container) {
if (name == null) {
return false;
}
if (parent == null) {
return true;
}
HierarchyContainer<EventTarget>[] compIDs = parent.getComps();
for (HierarchyContainer<EventTarget> childContainer: compIDs) {
String childName = childContainer.getName();
if (name.equals(childName) && childContainer != container) {
return false;
}
}
return true;
}
/**
* Checks for uniqueness of given name using the given list
*
* @param name the name to check
* @param otherNames the list with the names
* @return true or false
*/
protected boolean isUniqueName(String name, List<String> otherNames) {
if (name == null || otherNames == null) {
return false;
} else if (otherNames.isEmpty()) {
return true;
}
return !otherNames.contains(name);
}
/**
* Returns the lock of the hierarchy
*
* @return the lock
*/
public static ReentrantLock getLock() {
return lock;
}
/**
* {@inheritDoc}
*/
public boolean isInActiveWindow(EventTarget component) {
if (component != null && component instanceof Node) {
Node node = (Node) component;
Scene scene = node.getScene();
if (scene != null) {
Window window = scene.getWindow();
if (window != null) {
return window.isFocused();
}
}
}
return false;
}
}