package net.bytebuddy.implementation; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.bind.annotation.Pipe; import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.test.utility.CallTraceable; import org.junit.Test; import java.io.Serializable; import java.util.concurrent.Callable; import static net.bytebuddy.matcher.ElementMatchers.isClone; import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; public class MethodDelegationPipeTest { private static final String FOO = "foo", BAR = "bar", QUX = "qux"; private static final int BAZ = 42; @Test public void testPipeToIdenticalType() throws Exception { DynamicType.Loaded<Foo> loaded = new ByteBuddy() .subclass(Foo.class) .method(isDeclaredBy(Foo.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new ForwardingInterceptor(new Foo(FOO)))) .make() .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(instance.foo(QUX), is(FOO + QUX)); } @Test public void testPipeToIdenticalTypeVoid() throws Exception { DynamicType.Loaded<Qux> loaded = new ByteBuddy() .subclass(Qux.class) .method(isDeclaredBy(Qux.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new ForwardingInterceptor(new Qux()))) .make() .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); instance.foo(); instance.assertZeroCalls(); } @Test public void testPipeToIdenticalTypePrimitive() throws Exception { DynamicType.Loaded<Baz> loaded = new ByteBuddy() .subclass(Baz.class) .method(isDeclaredBy(Baz.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new PrimitiveForwardingInterceptor(new Baz()))) .make() .load(Baz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Baz instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(instance.foo(BAZ), is(BAZ * 2L)); instance.assertZeroCalls(); } @Test public void testPipeToSubtype() throws Exception { DynamicType.Loaded<Foo> loaded = new ByteBuddy() .subclass(Foo.class) .method(isDeclaredBy(Foo.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new ForwardingInterceptor(new Bar(FOO)))) .make() .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(instance.foo(QUX), is(FOO + QUX)); } @Test public void testPipeSerialization() throws Exception { DynamicType.Loaded<Foo> loaded = new ByteBuddy() .subclass(Foo.class) .method(isDeclaredBy(Foo.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new SerializableForwardingInterceptor(new Foo(FOO)))) .make() .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(instance.foo(QUX), is(FOO + QUX)); } @Test(expected = ClassCastException.class) public void testPipeToIncompatibleTypeThrowsException() throws Exception { DynamicType.Loaded<Foo> loaded = new ByteBuddy() .subclass(Foo.class) .method(isDeclaredBy(Foo.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new ForwardingInterceptor(new Object()))) .make() .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); instance.foo(QUX); } @Test(expected = IllegalArgumentException.class) public void testPipeTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception { MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(Serializable.class)) .to(new ForwardingInterceptor(new Object())); } @Test(expected = IllegalArgumentException.class) public void testPipeTypeDoesInheritFromOtherTypeThrowsException() throws Exception { MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(InheritingForwardingType.class)) .to(new ForwardingInterceptor(new Object())); } @Test(expected = IllegalArgumentException.class) public void testPipeTypeIsNotPublicThrowsException() throws Exception { MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(PackagePrivateForwardingType.class)) .to(new ForwardingInterceptor(new Object())); } @Test(expected = IllegalArgumentException.class) public void testPipeTypeDoesNotDeclareCorrectMethodSignatureThrowsException() throws Exception { MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(WrongParametersForwardingType.class)) .to(new ForwardingInterceptor(new Object())); } @Test(expected = IllegalArgumentException.class) public void testPipeTypeIsNotInterfaceThrowsException() throws Exception { MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(Object.class)) .to(new ForwardingInterceptor(new Object())); } @Test(expected = IllegalStateException.class) public void testPipeTypeOnTargetInterceptorThrowsException() throws Exception { new ByteBuddy() .subclass(Foo.class) .method(isDeclaredBy(Foo.class)) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(WrongParameterTypeTarget.class)) .make(); } @Test(expected = IllegalStateException.class) public void testPipeToPreotectedMethod() throws Exception { new ByteBuddy() .subclass(Object.class) .method(isClone()) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Pipe.Binder.install(ForwardingType.class)) .to(new ForwardingInterceptor(new Object()))) .make(); } public interface ForwardingType<T, S> { S doPipe(T target); } public interface InheritingForwardingType extends ForwardingType<Object, Object> { /* empty */ } interface PackagePrivateForwardingType<T, S> { S doPipe(T target); } public interface WrongParametersForwardingType<T extends Number, S> { S doPipe(T target); } public static class ForwardingInterceptor { private final Object target; public ForwardingInterceptor(Object target) { this.target = target; } public String intercept(@Pipe ForwardingType<Object, String> pipe) { assertThat(pipe, not(instanceOf(Serializable.class))); return pipe.doPipe(target); } } public static class Foo { private final String prefix; public Foo() { prefix = BAR; } public Foo(String prefix) { this.prefix = prefix; } public String foo(String input) { return prefix + input; } } public static class Bar extends Foo { public Bar(String prefix) { super(prefix); } } public static class Qux extends CallTraceable { public void foo() { register(FOO); } } public static class PrimitiveForwardingInterceptor { private final Object target; public PrimitiveForwardingInterceptor(Object target) { this.target = target; } @RuntimeType public Object intercept(@Pipe ForwardingType<Object, Object> pipe) { assertThat(pipe, not(instanceOf(Serializable.class))); return pipe.doPipe(target); } } public static class Baz extends CallTraceable { public long foo(int value) { register(FOO); return value * 2L; } } public static class WrongParameterTypeTarget { public static String intercept(@Pipe Callable<?> pipe) { return null; } } public static class SerializableForwardingInterceptor { private final Object target; public SerializableForwardingInterceptor(Object target) { this.target = target; } public String intercept(@Pipe(serializableProxy = true) ForwardingType<Object, String> pipe) { assertThat(pipe, instanceOf(Serializable.class)); return pipe.doPipe(target); } } }