Skip to content

Commit

Permalink
Merge pull request #59 from gradle/pshevche/support-enum-types-in-ref…
Browse files Browse the repository at this point in the history
…lective-proxy

Adapt supplied enum values to the proxy's target enum types
  • Loading branch information
pshevche authored Aug 22, 2024
2 parents 63bd4a8 + 9312c51 commit d5cc4e3
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ dependencies {

testing {
suites {
val compatibilityApiTest by creating(JvmTestSuite::class) {
useJUnitJupiter()

dependencies {
implementation(sourceSetOutput("compatibilityApi"))
implementation(testFixtures(project()))
implementation(platform(libs.junit.bom))
implementation("org.junit.jupiter:junit-jupiter")
}
}

val enterpriseCompatibilityTest by creating(JvmTestSuite::class) {
useJUnitJupiter()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
public final class ProxyFactory {

public static <T> T createProxy(Object target, Class<T> targetInterface) {
return newProxyInstance(targetInterface, new ProxyingInvocationHandler(target));
return createProxy(target, targetInterface, target.getClass().getClassLoader());
}

private static <T> T createProxy(Object target, Class<T> targetInterface, ClassLoader develocityTypesClassLoader) {
return newProxyInstance(targetInterface, new ProxyingInvocationHandler(target, develocityTypesClassLoader));
}

@SuppressWarnings("unchecked")
Expand All @@ -25,31 +29,35 @@ private static <T> T newProxyInstance(Class<T> targetInterface, InvocationHandle
private static final class ProxyingInvocationHandler implements InvocationHandler {

private final Object target;
private final ClassLoader develocityTypesClassLoader;

private ProxyingInvocationHandler(Object target) {
private ProxyingInvocationHandler(Object target, ClassLoader develocityTypesClassLoader) {
this.target = target;
this.develocityTypesClassLoader = develocityTypesClassLoader;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
Method targetMethod = target.getClass().getMethod(method.getName(), convertTypes(method.getParameterTypes(), target.getClass().getClassLoader()));
Object[] targetArgs = toTargetArgs(args);
Method targetMethod = target.getClass().getMethod(method.getName(), convertTypes(method.getParameterTypes(), develocityTypesClassLoader));
Object[] targetArgs = toTargetArgs(args, develocityTypesClassLoader);

// we always invoke public methods, but we need to make it accessible when it is implemented in anonymous classes
targetMethod.setAccessible(true);

Object result = targetMethod.invoke(target, targetArgs);
if (result == null || isJdkTypeOrThrowable(result.getClass())) {
return result;
} else if (result instanceof Enum) {
return adaptEnumArg((Enum<?>) result, develocityTypesClassLoader);
}
return createProxy(result, method.getReturnType());
return createProxy(result, method.getReturnType(), develocityTypesClassLoader);
} catch (Throwable e) {
throw new RuntimeException("Failed to invoke " + method + " on " + target + " with args " + Arrays.toString(args), e);
}
}

private static Object[] toTargetArgs(Object[] args) {
private Object[] toTargetArgs(Object[] args, ClassLoader classLoader) throws ClassNotFoundException {
if (args == null || args.length == 0) {
return args;
}
Expand All @@ -62,28 +70,36 @@ private static Object[] toTargetArgs(Object[] args) {
if (args.length == 1 && args[0] instanceof Function) {
return new Object[]{adaptFunctionArg((Function<?, ?>) args[0])};
}
if (args.length == 1 && args[0] instanceof Enum) {
return new Object[]{adaptEnumArg((Enum<?>) args[0], classLoader)};
}
if (Arrays.stream(args).allMatch(it -> isJdkTypeOrThrowable(it.getClass()))) {
return args;
}
throw new RuntimeException("Unsupported argument types in " + Arrays.toString(args));
}

@SuppressWarnings({"rawtypes", "unchecked"})
private static Action<Object> adaptActionArg(Action action) {
private static Enum<?> adaptEnumArg(Enum<?> arg, ClassLoader classLoader) throws ClassNotFoundException {
return Enum.valueOf((Class<Enum>) classLoader.loadClass(arg.getClass().getName()), arg.name());
}

@SuppressWarnings({"rawtypes", "unchecked"})
private Action<Object> adaptActionArg(Action action) {
return arg -> action.execute(createLocalProxy(arg));
}

@SuppressWarnings({"rawtypes", "unchecked"})
private static Spec<Object> adaptSpecArg(Spec spec) {
private Spec<Object> adaptSpecArg(Spec spec) {
return arg -> spec.isSatisfiedBy(createLocalProxy(arg));
}

@SuppressWarnings({"rawtypes", "unchecked"})
private static Function<Object, Object> adaptFunctionArg(Function func) {
private Function<Object, Object> adaptFunctionArg(Function func) {
return arg -> func.apply(createLocalProxy(arg));
}

private static Object createLocalProxy(Object target) {
private Object createLocalProxy(Object target) {
if (isJdkTypeOrThrowable(target.getClass())) {
return target;
}
Expand All @@ -92,7 +108,7 @@ private static Object createLocalProxy(Object target) {
return Proxy.newProxyInstance(
localClassLoader,
convertTypes(collectInterfaces(target.getClass()), localClassLoader),
new ProxyingInvocationHandler(target)
new ProxyingInvocationHandler(target, develocityTypesClassLoader)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.gradle.develocity.agent.adapters.gradle.internal;

import com.gradle.develocity.agent.gradle.adapters.internal.ProxyFactory;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ProxyFactoryTest {

@Test
@DisplayName("adapts enum method arguments")
void testEnumArg() {
// given
TargetWithEnum target = new TargetWithEnumImpl();
TargetWithEnum proxy = ProxyFactory.createProxy(target, TargetWithEnum.class);

// when
String result = proxy.methodWithEnumArg(TargetWithEnum.TargetEnum.B);

// then
assertEquals("B", result);
}

@Test
@DisplayName("adapts enum return values")
void testEnumReturn() {
// given
TargetWithEnum target = new TargetWithEnumImpl();
TargetWithEnum proxy = ProxyFactory.createProxy(target, TargetWithEnum.class);

// when
TargetWithEnum.TargetEnum result = proxy.methodReturningEnum();

// then
assertEquals(TargetWithEnum.TargetEnum.A, result);
}

interface TargetWithEnum {
enum TargetEnum {
A, B, C
}

String methodWithEnumArg(TargetEnum arg);

TargetEnum methodReturningEnum();
}

class TargetWithEnumImpl implements TargetWithEnum {

@Override
public String methodWithEnumArg(TargetEnum arg) {
return arg.name();
}

@Override
public TargetEnum methodReturningEnum() {
return TargetEnum.A;
}
}

}

0 comments on commit d5cc4e3

Please sign in to comment.