package net.thucydides.core.annotations.locators;
import com.google.common.collect.ImmutableList;
import net.thucydides.core.annotations.findby.FindBy;
import net.thucydides.core.pages.PageObject;
import net.thucydides.core.pages.WebElementFacade;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.internal.Locatable;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import org.openqa.selenium.support.pagefactory.FieldDecorator;
import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler;
import org.openqa.selenium.support.pagefactory.internal.LocatingElementListHandler;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.List;
public class SmartFieldDecorator implements FieldDecorator {
protected ElementLocatorFactory factory;
protected WebDriver driver;
protected PageObject pageObject;
public SmartFieldDecorator(ElementLocatorFactory factory, WebDriver driver,
PageObject pageObject) {
this.driver = driver;
this.factory = factory;
this.pageObject = pageObject;
}
public Object decorate(ClassLoader loader, Field field) {
if (!(WebElement.class.isAssignableFrom(field.getType()) || isDecoratableList(field))) {
return null;
}
ElementLocator locator = factory.createLocator(field);
if (locator == null) {
return null;
}
Class<?> fieldType = field.getType();
if (WebElement.class.isAssignableFrom(fieldType)) {
return proxyForLocator(loader, fieldType, locator);
} else if (List.class.isAssignableFrom(fieldType)) {
Class<?> erasureClass = getErasureClass(field);
return proxyForListLocator(loader, erasureClass, locator);
} else {
return null;
}
}
@SuppressWarnings("rawtypes")
private Class getErasureClass(Field field) {
Type genericType = field.getGenericType();
if (!(genericType instanceof ParameterizedType)) {
return null;
}
return (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0];
}
@SuppressWarnings("rawtypes")
private boolean isDecoratableList(Field field) {
if (!List.class.isAssignableFrom(field.getType())) {
return false;
}
Class erasureClass = getErasureClass(field);
if (erasureClass == null || !WebElement.class.isAssignableFrom(erasureClass)) {
return false;
}
return annotatedByLegalFindByAnnotation(field);
}
private final static List<Class<? extends Annotation>> LEGAL_ANNOTATIONS
= ImmutableList.of(FindBy.class,
org.openqa.selenium.support.FindBy.class,
FindBys.class);
private boolean annotatedByLegalFindByAnnotation(Field field) {
for (Annotation annotation : field.getAnnotations()) {
if (LEGAL_ANNOTATIONS.contains(annotation.annotationType())) {
return true;
}
}
return false;
}
/* Generate a type-parameterized locator proxy for the element in question. */
@SuppressWarnings("unchecked")
protected <T> T proxyForLocator(ClassLoader loader, Class<T> interfaceType, ElementLocator locator) {
InvocationHandler handler;
T proxy = null;
if (WebElementFacade.class.isAssignableFrom(interfaceType)) {
handler = new SmartElementHandler(interfaceType, locator, driver, pageObject.waitForTimeoutInMilliseconds());
proxy = (T) Proxy.newProxyInstance(loader, new Class[]{interfaceType}, handler);
} else {
handler = new LocatingElementHandler(locator);
proxy = (T) Proxy.newProxyInstance(loader,
new Class[]{WebElement.class, WrapsElement.class, Locatable.class}, handler);
}
return proxy;
}
/* generates a proxy for a list of elements to be wrapped. */
@SuppressWarnings("unchecked")
protected <T> List<T> proxyForListLocator(ClassLoader loader, Class<T> interfaceType, ElementLocator locator) {
InvocationHandler handler = new LocatingElementListHandler(locator);
List<T> proxy;
proxy = (List<T>) Proxy.newProxyInstance(
loader, new Class[]{List.class}, handler);
return proxy;
}
}