Skip to content

refactor: Replace cglib with bytebuddy #1923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ dependencies {
}
}
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'cglib:cglib:3.3.0'
implementation 'commons-validator:commons-validator:1.7'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'commons-io:commons-io:2.12.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public AppiumElementLocatorFactory(SearchContext searchContext, Duration duratio
return this.createLocator((AnnotatedElement) field);
}

@Override public @Nullable CacheableLocator createLocator(AnnotatedElement annotatedElement) {
@Override
@Nullable
public CacheableLocator createLocator(AnnotatedElement annotatedElement) {
Duration customDuration;
if (annotatedElement.isAnnotationPresent(WithTimeout.class)) {
WithTimeout withTimeout = annotatedElement.getAnnotation(WithTimeout.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.FieldDecorator;

import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
Expand Down Expand Up @@ -92,17 +93,17 @@ public AppiumFieldDecorator(SearchContext context, Duration duration) {
this.duration = duration;

defaultElementFieldDecoracor = new DefaultFieldDecorator(
new AppiumElementLocatorFactory(context, duration,
new DefaultElementByBuilder(platform, automation))) {
new AppiumElementLocatorFactory(context, duration, new DefaultElementByBuilder(platform, automation))
) {
@Override
protected WebElement proxyForLocator(ClassLoader ignored, ElementLocator locator) {
return proxyForAnElement(locator);
}

@Override
@SuppressWarnings("unchecked")
protected List<WebElement> proxyForListLocator(ClassLoader ignored, ElementLocator locator) {
ElementListInterceptor elementInterceptor = new ElementListInterceptor(locator);
//noinspection unchecked
return getEnhancedProxy(ArrayList.class, elementInterceptor);
}

Expand All @@ -121,14 +122,14 @@ protected boolean isDecoratableList(Field field) {
List<Type> bounds = (listType instanceof TypeVariable)
? Arrays.asList(((TypeVariable<?>) listType).getBounds())
: Collections.emptyList();

return availableElementClasses.stream()
.anyMatch((webElClass) -> webElClass.equals(listType) || bounds.contains(webElClass));
}
};

widgetLocatorFactory =
new AppiumElementLocatorFactory(context, duration, new WidgetByBuilder(platform, automation));
widgetLocatorFactory = new AppiumElementLocatorFactory(
context, duration, new WidgetByBuilder(platform, automation)
);
}

public AppiumFieldDecorator(SearchContext context) {
Expand All @@ -144,14 +145,10 @@ public AppiumFieldDecorator(SearchContext context) {
*/
public Object decorate(ClassLoader ignored, Field field) {
Object result = defaultElementFieldDecoracor.decorate(ignored, field);
if (result != null) {
return result;
}

return decorateWidget(field);
return result == null ? decorateWidget(field) : result;
}

@SuppressWarnings("unchecked")
@Nullable
private Object decorateWidget(Field field) {
Class<?> type = field.getType();
if (!Widget.class.isAssignableFrom(type) && !List.class.isAssignableFrom(type)) {
Expand All @@ -177,30 +174,34 @@ private Object decorateWidget(Field field) {
if (!Widget.class.isAssignableFrom((Class<?>) listType)) {
return null;
}
//noinspection unchecked
widgetType = (Class<? extends Widget>) listType;
} else {
return null;
}

} else {
//noinspection unchecked
widgetType = (Class<? extends Widget>) field.getType();
}

CacheableLocator locator = widgetLocatorFactory.createLocator(field);
Map<ContentType, Constructor<? extends Widget>> map =
OverrideWidgetReader.read(widgetType, field, platform);
Map<ContentType, Constructor<? extends Widget>> map = OverrideWidgetReader.read(widgetType, field, platform);

if (isAlist) {
return getEnhancedProxy(ArrayList.class,
new WidgetListInterceptor(locator, webDriver, map, widgetType,
duration));
return getEnhancedProxy(
ArrayList.class,
new WidgetListInterceptor(locator, webDriver, map, widgetType, duration)
);
}

Constructor<? extends Widget> constructor =
WidgetConstructorUtil.findConvenientConstructor(widgetType);
return getEnhancedProxy(widgetType, new Class[]{constructor.getParameterTypes()[0]},
Constructor<? extends Widget> constructor = WidgetConstructorUtil.findConvenientConstructor(widgetType);
return getEnhancedProxy(
widgetType,
new Class[]{constructor.getParameterTypes()[0]},
new Object[]{proxyForAnElement(locator)},
new WidgetInterceptor(locator, webDriver, null, map, duration));
new WidgetInterceptor(locator, webDriver, null, map, duration)
);
}

private WebElement proxyForAnElement(ElementLocator locator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,13 @@ public By buildBy() {
String idOrName = ((Field) annotatedElementContainer.getAnnotated()).getName();

if (defaultBy == null && mobileNativeBy == null) {
defaultBy =
new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName());
defaultBy = new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName());
mobileNativeBy = new By.ById(idOrName);
return returnMappedBy(defaultBy, mobileNativeBy);
}

if (defaultBy == null) {
defaultBy =
new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName());
defaultBy = new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName());
return returnMappedBy(defaultBy, mobileNativeBy);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
/**
* Intercepts requests to {@link WebElement}.
*/
class ElementInterceptor extends InterceptorOfASingleElement {
public class ElementInterceptor extends InterceptorOfASingleElement {

ElementInterceptor(ElementLocator locator, WebDriver driver) {
public ElementInterceptor(ElementLocator locator, WebDriver driver) {
super(locator, driver);
}

@Override protected Object getObject(WebElement element, Method method, Object[] args)
@Override
protected Object getObject(WebElement element, Method method, Object[] args)
throws Throwable {
try {
return method.invoke(element, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@
/**
* Intercepts requests to the list of {@link WebElement}.
*/
class ElementListInterceptor extends InterceptorOfAListOfElements {
public class ElementListInterceptor extends InterceptorOfAListOfElements {

ElementListInterceptor(ElementLocator locator) {
public ElementListInterceptor(ElementLocator locator) {
super(locator);
}

@Override protected Object getObject(List<WebElement> elements, Method method, Object[] args)
throws Throwable {
@Override
protected Object getObject(List<WebElement> elements, Method method, Object[] args) throws Throwable {
try {
return method.invoke(elements, args);
} catch (Throwable t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ protected static boolean isInvalidSelectorRootCause(Throwable e) {
return true;
}

if (String.valueOf(e.getMessage()).contains(INVALID_SELECTOR_PATTERN) || String
.valueOf(e.getMessage()).contains("Locator Strategy \\w+ is not supported")) {
if (String.valueOf(e.getMessage()).contains(INVALID_SELECTOR_PATTERN)
|| String.valueOf(e.getMessage()).contains("Locator Strategy \\w+ is not supported")) {
return true;
}

Expand All @@ -54,8 +54,8 @@ protected static boolean isStaleElementReferenceException(Throwable e) {
}

protected static Throwable extractReadableException(Throwable e) {
if (!RuntimeException.class.equals(e.getClass()) && !InvocationTargetException.class
.equals(e.getClass())) {
if (!RuntimeException.class.equals(e.getClass())
&& !InvocationTargetException.class.equals(e.getClass())) {
return e;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,55 +19,62 @@
import io.appium.java_client.pagefactory.bys.ContentType;
import io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement;
import io.appium.java_client.pagefactory.locator.CacheableLocator;
import net.sf.cglib.proxy.MethodProxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;

import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException;
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType;

class WidgetInterceptor extends InterceptorOfASingleElement {
public class WidgetInterceptor extends InterceptorOfASingleElement {

private final Map<ContentType, Constructor<? extends Widget>> instantiationMap;
private final Map<ContentType, Widget> cachedInstances = new HashMap<>();
private final Duration duration;
private WebElement cachedElement;

WidgetInterceptor(CacheableLocator locator, WebDriver driver, WebElement cachedElement,
Map<ContentType, Constructor<? extends Widget>> instantiationMap,
Duration duration) {
/**
* Proxy interceptor class for widgets.
*/
public WidgetInterceptor(
CacheableLocator locator,
WebDriver driver,
@Nullable
WebElement cachedElement,
Map<ContentType, Constructor<? extends Widget>> instantiationMap,
Duration duration
) {
super(locator, driver);
this.cachedElement = cachedElement;
this.instantiationMap = instantiationMap;
this.duration = duration;
}


@Override protected Object getObject(WebElement element, Method method, Object[] args)
throws Throwable {
@Override
protected Object getObject(WebElement element, Method method, Object[] args) throws Throwable {
ContentType type = getCurrentContentType(element);
if (cachedElement == null
|| (locator != null && !((CacheableLocator) locator)
.isLookUpCached())
|| cachedInstances.size() == 0) {
|| (locator != null && !((CacheableLocator) locator).isLookUpCached())
|| cachedInstances.isEmpty()
) {
cachedElement = element;

Constructor<? extends Widget> constructor = instantiationMap.get(type);
Class<? extends Widget> clazz = constructor.getDeclaringClass();

int modifiers = clazz.getModifiers();
if (Modifier.isAbstract(modifiers)) {
throw new InstantiationException(clazz.getName()
+ " is abstract so "
+ "it can't be instantiated");
if (Modifier.isAbstract(clazz.getModifiers())) {
throw new InstantiationException(
String.format("%s is abstract so it cannot be instantiated", clazz.getName())
);
}

Widget widget = constructor.newInstance(cachedElement);
Expand All @@ -82,11 +89,10 @@ class WidgetInterceptor extends InterceptorOfASingleElement {
}
}

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
if (locator != null) {
return super.intercept(obj, method, args, proxy);
}
return getObject(cachedElement, method, args);
@Override
public Object call(Object obj, Method method, Object[] args, Callable<?> original) throws Throwable {
return locator == null
? getObject(cachedElement, method, args)
: super.call(obj, method, args, original);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.appium.java_client.pagefactory.bys.ContentType;
import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements;
import io.appium.java_client.pagefactory.locator.CacheableLocator;
import io.appium.java_client.pagefactory.utils.ProxyFactory;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

Expand All @@ -31,19 +30,22 @@
import java.util.Map;

import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException;
import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy;
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType;
import static java.util.Optional.ofNullable;

class WidgetListInterceptor extends InterceptorOfAListOfElements {

public class WidgetListInterceptor extends InterceptorOfAListOfElements {
private final Map<ContentType, Constructor<? extends Widget>> instantiationMap;
private final List<Widget> cachedWidgets = new ArrayList<>();
private final Class<? extends Widget> declaredType;
private final Duration duration;
private final WebDriver driver;
private List<WebElement> cachedElements;

WidgetListInterceptor(CacheableLocator locator, WebDriver driver,
/**
* Proxy interceptor class for lists of widgets.
*/
public WidgetListInterceptor(CacheableLocator locator, WebDriver driver,
Map<ContentType, Constructor<? extends Widget>> instantiationMap,
Class<? extends Widget> declaredType, Duration duration) {
super(locator);
Expand All @@ -53,22 +55,22 @@ class WidgetListInterceptor extends InterceptorOfAListOfElements {
this.driver = driver;
}


@Override protected Object getObject(List<WebElement> elements, Method method, Object[] args)
throws Throwable {
if (cachedElements == null || (locator != null && !((CacheableLocator) locator)
.isLookUpCached())) {
@Override
protected Object getObject(List<WebElement> elements, Method method, Object[] args) throws Throwable {
if (cachedElements == null || (locator != null && !((CacheableLocator) locator).isLookUpCached())) {
cachedElements = elements;
cachedWidgets.clear();

ContentType type = null;
for (WebElement element : cachedElements) {
type = ofNullable(type).orElseGet(() -> getCurrentContentType(element));
Class<?>[] params =
new Class<?>[] {instantiationMap.get(type).getParameterTypes()[0]};
cachedWidgets.add(ProxyFactory
.getEnhancedProxy(declaredType, params, new Object[] {element},
new WidgetInterceptor(null, driver, element, instantiationMap, duration)));
Class<?>[] params = new Class<?>[] {instantiationMap.get(type).getParameterTypes()[0]};
cachedWidgets.add(
getEnhancedProxy(
declaredType, params, new Object[] {element},
new WidgetInterceptor(null, driver, element, instantiationMap, duration)
)
);
}
}
try {
Expand Down
Loading