package org.fluentlenium.core.components;
import static org.fluentlenium.core.domain.ElementUtils.getWrappedElement;
import org.fluentlenium.core.FluentControl;
import org.fluentlenium.core.proxy.LocatorProxies;
import org.fluentlenium.core.proxy.ProxyElementListener;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Manage living components for a WebDriver instance.
*
* A component is an Object implementing no particular interface, but capable of wrapping
* a {@link org.openqa.selenium.WebElement}.
*
* {@link org.fluentlenium.core.domain.FluentWebElement} is the most common component.
*/
public class ComponentsManager extends AbstractComponentInstantiator
implements ComponentInstantiator, ComponentsAccessor, ProxyElementListener {
private final DefaultComponentInstantiator instantiator;
private final Map> components = new IdentityHashMap<>();
private final List listeners = new ArrayList<>();
/**
* Creates a new components manager.
*
* @param control control interface
*/
public ComponentsManager(FluentControl control) {
instantiator = new DefaultComponentInstantiator(control, this);
}
/**
* Get the component instantiator used by this components manager.
*
* @return component instantiator
*/
public ComponentInstantiator getInstantiator() {
return instantiator;
}
@Override
public Set getComponents(WebElement element) {
return components.get(unwrapElement(element));
}
@Override
public boolean isComponentClass(Class componentClass) {
return instantiator.isComponentClass(componentClass);
}
@Override
public boolean isComponentListClass(Class> componentListClass) {
return instantiator.isComponentListClass(componentListClass);
}
@Override
public T newComponent(Class componentClass, WebElement element) {
T component = instantiator.newComponent(componentClass, element);
register(element, component);
return component;
}
@Override
public boolean addComponentsListener(ComponentsListener listener) {
synchronized (this) {
return listeners.add(listener);
}
}
@Override
public boolean removeComponentsListener(ComponentsListener listener) {
synchronized (this) {
return listeners.remove(listener);
}
}
/**
* Fire component registered event.
*
* @param element underlying element
* @param component registered component
*/
protected void fireComponentRegistered(WebElement element, Object component) {
synchronized (this) {
for (ComponentsListener listener : listeners) {
listener.componentRegistered(element, component);
}
}
}
/**
* Fire component released event.
*
* @param element underlying element
* @param component released component
*/
protected void fireComponentReleased(WebElement element, Object component) {
synchronized (this) {
for (ComponentsListener listener : listeners) {
listener.componentReleased(element, component);
}
}
}
private void register(WebElement element, T component) {
WebElement webElement = unwrapElement(element);
LocatorProxies.addProxyListener(webElement, this);
synchronized (this) {
Set elementComponents = components.computeIfAbsent(webElement, k -> new HashSet<>());
elementComponents.add(component);
fireComponentRegistered(element, component);
}
}
@Override
public , T> L newComponentList(Class listClass, Class componentClass, List componentsList) {
return instantiator.newComponentList(listClass, componentClass, componentsList);
}
@Override
public , T> L asComponentList(Class listClass, Class componentClass,
Iterable elementList) {
L componentList = instantiator.asComponentList(listClass, componentClass, elementList);
int i = 0;
for (WebElement element : elementList) {
register(element, componentList.get(i));
i++;
}
return componentList;
}
@Override
public void proxyElementSearch(Object proxy, ElementLocator locator) {
// Do nothing.
}
@Override
public void proxyElementFound(Object proxy, ElementLocator locator, List elements) {
synchronized (this) {
for (WebElement element : elements) {
Set proxyComponents = components.remove(proxy);
if (proxyComponents != null) {
components.put(unwrapElement(element), proxyComponents);
}
}
}
}
private WebElement unwrapElement(WebElement element) {
if (element instanceof WrapsElement) {
WebElement wrappedElement = getWrappedElement(element);
if (wrappedElement != element && wrappedElement != null) { // NOPMD CompareObjectsWithEquals
return unwrapElement(wrappedElement);
}
}
return element;
}
/**
* Release this manager.
*/
public void release() {
for (Map.Entry> elementEntry : components.entrySet()) {
LocatorProxies.removeProxyListener(elementEntry.getKey(), this);
for (Object component : elementEntry.getValue()) {
fireComponentRegistered(elementEntry.getKey(), component);
}
}
components.clear();
}
}