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.Morph;
import net.bytebuddy.test.utility.CallTraceable;
import net.bytebuddy.test.utility.JavaVersionRule;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import java.io.Serializable;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
public class MethodDelegationMorphTest {
private static final String FOO = "foo", BAR = "bar", QUX = "qux";
private static final int BAZ = 42;
private static final String DEFAULT_INTERFACE = "net.bytebuddy.test.precompiled.MorphDefaultInterface";
private static final String DEFAULT_INTERFACE_TARGET_EXPLICIT = "net.bytebuddy.test.precompiled.MorphDefaultDelegationTargetExplicit";
private static final String DEFAULT_INTERFACE_TARGET_IMPLICIT = "net.bytebuddy.test.precompiled.MorphDefaultDelegationTargetImplicit";
@Rule
public MethodRule javaVersionRule = new JavaVersionRule();
@Test
public void testMorph() throws Exception {
DynamicType.Loaded<Foo> loaded = new ByteBuddy()
.subclass(Foo.class)
.method(isDeclaredBy(Foo.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(new SimpleMorph(QUX)))
.make()
.load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.foo(FOO), is(QUX + BAR));
}
@Test
public void testMorphVoid() throws Exception {
SimpleMorph simpleMorph = new SimpleMorph(QUX);
DynamicType.Loaded<Bar> loaded = new ByteBuddy()
.subclass(Bar.class)
.method(isDeclaredBy(Bar.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(simpleMorph))
.make()
.load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
instance.foo();
instance.assertOnlyCall(FOO);
simpleMorph.assertOnlyCall(BAR);
}
@Test
public void testMorphPrimitive() throws Exception {
DynamicType.Loaded<Qux> loaded = new ByteBuddy()
.subclass(Qux.class)
.method(isDeclaredBy(Qux.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(new PrimitiveMorph(BAZ)))
.make()
.load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.foo(0), is(BAZ * 2L));
}
@Test
public void testMorphSerializable() throws Exception {
DynamicType.Loaded<Foo> loaded = new ByteBuddy()
.subclass(Foo.class)
.method(isDeclaredBy(Foo.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(SimpleMorphSerializable.class))
.make()
.load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.foo(FOO), is(QUX + BAR));
}
@Test(expected = IllegalStateException.class)
public void testMorphIllegal() throws Exception {
new ByteBuddy()
.subclass(Foo.class)
.method(isDeclaredBy(Foo.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(SimpleMorphIllegal.class))
.make();
}
@Test(expected = ClassCastException.class)
public void testMorphToIncompatibleTypeThrowsException() throws Exception {
DynamicType.Loaded<Foo> loaded = new ByteBuddy()
.subclass(Foo.class)
.method(isDeclaredBy(Foo.class))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(new SimpleMorph(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 testMorphTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception {
Morph.Binder.install(Serializable.class);
}
@Test(expected = IllegalArgumentException.class)
public void testMorphTypeDoesInheritFromOtherTypeThrowsException() throws Exception {
Morph.Binder.install(InheritingMorphingType.class);
}
@Test(expected = IllegalArgumentException.class)
public void testMorphTypeIsNotPublicThrowsException() throws Exception {
Morph.Binder.install(PackagePrivateMorphing.class);
}
@Test(expected = IllegalArgumentException.class)
public void testPipeTypeDoesNotDeclareCorrectMethodSignatureThrowsException() throws Exception {
Morph.Binder.install(WrongParametersMorphing.class);
}
@Test(expected = IllegalArgumentException.class)
public void testPipeTypeIsNotInterfaceThrowsException() throws Exception {
MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(Object.class)).to(new SimpleMorph(QUX));
}
@Test
@JavaVersionRule.Enforce(8)
public void testDefaultMethodExplicit() throws Exception {
DynamicType.Loaded<Object> loaded = new ByteBuddy()
.subclass(Object.class)
.implement(Class.forName(DEFAULT_INTERFACE))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(Class.forName(DEFAULT_INTERFACE_TARGET_EXPLICIT)))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.getClass().getDeclaredMethod(FOO, String.class)
.invoke(instance, QUX), is((Object) (FOO + BAR)));
}
@Test
@JavaVersionRule.Enforce(8)
public void testDefaultMethodImplicit() throws Exception {
DynamicType.Loaded<Object> loaded = new ByteBuddy()
.subclass(Object.class)
.implement(Class.forName(DEFAULT_INTERFACE))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(Class.forName(DEFAULT_INTERFACE_TARGET_IMPLICIT)))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
assertThat(instance.getClass().getDeclaredMethod(FOO, String.class)
.invoke(instance, QUX), is((Object) (FOO + BAR)));
}
public interface Morphing<T> {
T morph(Object... arguments);
}
public interface InheritingMorphingType<T> extends Morphing<T> {
/* empty */
}
@SuppressWarnings("unused")
private interface PackagePrivateMorphing<T> {
T morph(Object... arguments);
}
@SuppressWarnings("unused")
private interface WrongParametersMorphing<T> {
T morph(Object arguments);
}
public static class Foo {
public String foo(String foo) {
return foo + BAR;
}
}
public static class Bar extends CallTraceable {
public void foo() {
register(FOO);
}
}
public static class Qux {
public long foo(int argument) {
return argument * 2L;
}
}
public static class SimpleMorph extends CallTraceable {
private final Object[] arguments;
public SimpleMorph(Object... arguments) {
this.arguments = arguments;
}
public String intercept(@Morph Morphing<String> morphing) {
register(BAR);
assertThat(morphing, CoreMatchers.not(instanceOf(Serializable.class)));
return morphing.morph(arguments);
}
}
public static class PrimitiveMorph {
private final int argument;
public PrimitiveMorph(int argument) {
this.argument = argument;
}
public Long intercept(@Morph Morphing<Long> morphing) {
assertThat(morphing, CoreMatchers.not(instanceOf(Serializable.class)));
return morphing.morph(argument);
}
}
public static class SimpleMorphSerializable {
public static String intercept(@Morph(serializableProxy = true) Morphing<String> morphing) {
assertThat(morphing, instanceOf(Serializable.class));
return morphing.morph(QUX);
}
}
@SuppressWarnings("unused")
public static class SimpleMorphIllegal {
public static String intercept(@Morph Void morphing) {
return null;
}
}
}