package net.bytebuddy.implementation; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeList; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.test.utility.JavaVersionRule; import net.bytebuddy.test.utility.ObjectPropertyAssertion; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.List; import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; import static net.bytebuddy.matcher.ElementMatchers.not; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class DefaultMethodCallTest { private static final String FOO = "foo", QUX = "qux"; private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface"; private static final String SINGLE_DEFAULT_METHOD_CLASS = "net.bytebuddy.test.precompiled.SingleDefaultMethodClass"; private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface"; private static final String NON_OVERRIDING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodNonOverridingInterface"; @Rule public MethodRule javaVersionRule = new JavaVersionRule(); @Test @JavaVersionRule.Enforce(8) public void testUnambiguousDefaultMethod() throws Exception { DynamicType.Loaded<?> loaded = new ByteBuddy() .subclass(Object.class) .implement(Class.forName(SINGLE_DEFAULT_METHOD)) .intercept(DefaultMethodCall.unambiguousOnly()) .make() .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1)); Method method = loaded.getLoaded().getDeclaredMethod(FOO); Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(method.invoke(instance), is((Object) FOO)); } @Test(expected = IllegalStateException.class) @JavaVersionRule.Enforce(8) public void testAmbiguousDefaultMethodThrowsException() throws Exception { new ByteBuddy() .subclass(Object.class) .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE)) .intercept(DefaultMethodCall.unambiguousOnly()) .make(); } @Test @JavaVersionRule.Enforce(8) public void testAmbiguousDefaultMethodWithExplicitPreference() throws Exception { Class<?> singleMethodInterface = Class.forName(SINGLE_DEFAULT_METHOD); Class<?> conflictingInterface = Class.forName(CONFLICTING_INTERFACE); assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface); assertConflictChoice(singleMethodInterface, conflictingInterface, QUX, conflictingInterface); assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface, conflictingInterface); assertConflictChoice(singleMethodInterface, conflictingInterface, QUX, conflictingInterface, singleMethodInterface); assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface, Class.forName(NON_OVERRIDING_INTERFACE)); assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, Class.forName(NON_OVERRIDING_INTERFACE), singleMethodInterface); } private void assertConflictChoice(Class<?> preferredInterface, Class<?> secondInterface, Object expectedResult, Class<?>... preferredInterfaces) throws Exception { DynamicType.Loaded<?> loaded = new ByteBuddy() .subclass(Object.class) .implement(preferredInterface, secondInterface) .intercept(DefaultMethodCall.prioritize(preferredInterfaces)) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1)); Method method = loaded.getLoaded().getDeclaredMethod(FOO); Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(method.invoke(instance), is(expectedResult)); } @Test(expected = IllegalStateException.class) @JavaVersionRule.Enforce(8) public void testUnrelatedPreferredDefaultMethodThrowsException() throws Exception { new ByteBuddy() .subclass(Object.class) .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE)) .intercept(DefaultMethodCall.prioritize(Class.forName(NON_OVERRIDING_INTERFACE))) .make(); } @Test(expected = IllegalStateException.class) @JavaVersionRule.Enforce(8) public void testNonDeclaredDefaultMethodThrowsException() throws Exception { new ByteBuddy() .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS)) .method(not(isDeclaredBy(Object.class))) .intercept(DefaultMethodCall.unambiguousOnly()) .make(); } @Test(expected = IllegalStateException.class) @JavaVersionRule.Enforce(8) public void testNonDeclaredPreferredDefaultMethodThrowsException() throws Exception { new ByteBuddy() .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS)) .method(not(isDeclaredBy(Object.class))) .intercept(DefaultMethodCall.prioritize(Class.forName(SINGLE_DEFAULT_METHOD))) .make(); } @Test @JavaVersionRule.Enforce(8) public void testDeclaredAndImplementedMethod() throws Exception { DynamicType.Loaded<?> loaded = new ByteBuddy() .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS)) .implement(Class.forName(SINGLE_DEFAULT_METHOD)) .method(not(isDeclaredBy(Object.class))) .intercept(DefaultMethodCall.unambiguousOnly()) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1)); Method method = loaded.getLoaded().getDeclaredMethod(FOO); Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(method.invoke(instance), is((Object) FOO)); } @Test(expected = IllegalStateException.class) @JavaVersionRule.Enforce(8) public void testDeclaredAndImplementedAmbiguousMethodThrowsException() throws Exception { new ByteBuddy() .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS)) .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE)) .method(not(isDeclaredBy(Object.class))) .intercept(DefaultMethodCall.unambiguousOnly()) .make(); } @Test @JavaVersionRule.Enforce(8) public void testDeclaredAndImplementedAmbiguousMethodWithPreference() throws Exception { DynamicType.Loaded<?> loaded = new ByteBuddy() .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS)) .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE)) .method(not(isDeclaredBy(Object.class))) .intercept(DefaultMethodCall.prioritize(Class.forName(SINGLE_DEFAULT_METHOD))) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER); assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1)); Method method = loaded.getLoaded().getDeclaredMethod(FOO); Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance(); assertThat(method.invoke(instance), is((Object) FOO)); } @Test public void testObjectProperties() throws Exception { ObjectPropertyAssertion.of(DefaultMethodCall.class).create(new ObjectPropertyAssertion.Creator<List<?>>() { @Override public List<?> create() { TypeDescription typeDescription = mock(TypeDescription.class); when(typeDescription.isInterface()).thenReturn(true); when(typeDescription.asErasure()).thenReturn(typeDescription); when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC); return Collections.singletonList(typeDescription); } }).apply(); final TypeDescription removalType = mock(TypeDescription.class); final TypeDescription.Generic genericRemovalType = mock(TypeDescription.Generic.class); when(genericRemovalType.asGenericType()).thenReturn(genericRemovalType); when(genericRemovalType.asErasure()).thenReturn(removalType); ObjectPropertyAssertion.of(DefaultMethodCall.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() { @Override public void apply(Implementation.Target mock) { TypeDescription typeDescription = mock(TypeDescription.class), otherType = mock(TypeDescription.class); TypeDescription.Generic otherGenericType = mock(TypeDescription.Generic.class); when(otherGenericType.asErasure()).thenReturn(otherType); when(otherGenericType.asGenericType()).thenReturn(otherGenericType); when(typeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(genericRemovalType, otherGenericType)); when(mock.getInstrumentedType()).thenReturn(typeDescription); } }).create(new ObjectPropertyAssertion.Creator<List<?>>() { @Override public List<?> create() { TypeDescription typeDescription = mock(TypeDescription.class); when(typeDescription.asErasure()).thenReturn(typeDescription); return Arrays.asList(removalType, typeDescription); } }).apply(); } }