package net.bytebuddy.implementation;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.test.utility.CallTraceable;
import net.bytebuddy.test.utility.ObjectPropertyAssertion;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
public class InvocationHandlerAdapterTest {
private static final String FOO = "foo", BAR = "bar", QUX = "qux";
private static final int BAZ = 42;
@Test
public void testStaticAdapterWithoutCache() throws Exception {
Foo foo = new Foo();
DynamicType.Loaded<Bar> loaded = new ByteBuddy()
.subclass(Bar.class)
.method(isDeclaredBy(Bar.class))
.intercept(InvocationHandlerAdapter.of(foo).withoutMethodCache())
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(1));
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(2));
assertThat(foo.methods.get(0), not(sameInstance(foo.methods.get(1))));
instance.assertZeroCalls();
}
@Test
public void testStaticAdapterWithoutCacheForPrimitiveValue() throws Exception {
Qux qux = new Qux();
DynamicType.Loaded<Baz> loaded = new ByteBuddy()
.subclass(Baz.class)
.method(isDeclaredBy(Baz.class))
.intercept(InvocationHandlerAdapter.of(qux).withoutMethodCache())
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
Baz instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.bar(BAZ), is(BAZ * 2L));
instance.assertZeroCalls();
}
@Test
public void testStaticAdapterWithMethodCache() throws Exception {
Foo foo = new Foo();
DynamicType.Loaded<Bar> loaded = new ByteBuddy()
.subclass(Bar.class)
.method(isDeclaredBy(Bar.class))
.intercept(InvocationHandlerAdapter.of(foo))
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
assertThat(loaded.getLoaded().getDeclaredFields().length, is(2));
Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(1));
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(2));
assertThat(foo.methods.get(0), sameInstance(foo.methods.get(1)));
instance.assertZeroCalls();
}
@Test
public void testInstanceAdapterWithoutCache() throws Exception {
DynamicType.Loaded<Bar> loaded = new ByteBuddy()
.subclass(Bar.class)
.defineField(QUX, InvocationHandler.class, Visibility.PUBLIC)
.method(isDeclaredBy(Bar.class))
.intercept(InvocationHandlerAdapter.toField(QUX).withoutMethodCache())
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
Field field = loaded.getLoaded().getDeclaredField(QUX);
assertThat(field.getModifiers(), is(Modifier.PUBLIC));
field.setAccessible(true);
Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
Foo foo = new Foo();
field.set(instance, foo);
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(1));
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(2));
assertThat(foo.methods.get(0), not(sameInstance(foo.methods.get(1))));
instance.assertZeroCalls();
}
@Test
public void testInstanceAdapterWithMethodCache() throws Exception {
DynamicType.Loaded<Bar> loaded = new ByteBuddy()
.subclass(Bar.class)
.defineField(QUX, InvocationHandler.class, Visibility.PUBLIC)
.method(isDeclaredBy(Bar.class))
.intercept(InvocationHandlerAdapter.toField(QUX))
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
assertThat(loaded.getLoaded().getDeclaredFields().length, is(2));
Field field = loaded.getLoaded().getDeclaredField(QUX);
assertThat(field.getModifiers(), is(Modifier.PUBLIC));
field.setAccessible(true);
Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
Foo foo = new Foo();
field.set(instance, foo);
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(1));
assertThat(instance.bar(FOO), is((Object) instance));
assertThat(foo.methods.size(), is(2));
assertThat(foo.methods.get(0), sameInstance(foo.methods.get(1)));
instance.assertZeroCalls();
}
@Test(expected = IllegalStateException.class)
public void testStaticMethod() throws Exception {
new ByteBuddy()
.subclass(Object.class)
.defineField(QUX, InvocationHandler.class)
.defineMethod(FOO, void.class, Ownership.STATIC)
.intercept(InvocationHandlerAdapter.toField(QUX))
.make();
}
@Test(expected = IllegalStateException.class)
public void testNonExistentField() throws Exception {
new ByteBuddy()
.subclass(Object.class)
.defineMethod(FOO, void.class)
.intercept(InvocationHandlerAdapter.toField(QUX))
.make();
}
@Test(expected = IllegalStateException.class)
public void testIncompatibleFieldType() throws Exception {
new ByteBuddy()
.subclass(Object.class)
.defineField(QUX, Object.class)
.defineMethod(FOO, void.class)
.intercept(InvocationHandlerAdapter.toField(QUX))
.make();
}
@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.class).apply();
ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.Appender.class).apply();
ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.Appender.class).skipSynthetic().apply();
ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForField.class).apply();
ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForField.Appender.class).skipSynthetic().apply();
}
private static class Foo implements InvocationHandler {
private final String marker;
public List<Method> methods;
private Foo() {
marker = FOO;
methods = new ArrayList<Method>();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
methods.add(method);
assertThat(args.length, is(1));
assertThat(args[0], is((Object) FOO));
assertThat(method.getName(), is(BAR));
assertThat(proxy, instanceOf(Bar.class));
return proxy;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& marker.equals(((Foo) other).marker);
}
@Override
public int hashCode() {
return marker.hashCode();
}
}
public static class Bar extends CallTraceable {
public Object bar(Object o) {
register(BAR);
return o;
}
}
private static class Qux implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return ((Integer) args[0]) * 2L;
}
}
public static class Baz extends CallTraceable {
public long bar(int o) {
register(BAR);
return o;
}
}
}