package net.bytebuddy.implementation.bind.annotation; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.method.ParameterList; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bind.ArgumentTypeResolver; import net.bytebuddy.implementation.bind.MethodDelegationBinder; import net.bytebuddy.implementation.bytecode.StackManipulation; import net.bytebuddy.implementation.bytecode.StackSize; import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.test.utility.MockitoRule; import net.bytebuddy.test.utility.ObjectPropertyAssertion; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.mockito.Mock; import org.objectweb.asm.MethodVisitor; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.*; public class TargetMethodAnnotationDrivenBinderTest { private static final String FOO = "foo", BAR = "bar", BAZ = "baz"; @Rule public TestRule mockitoRule = new MockitoRule(this); @Mock private TargetMethodAnnotationDrivenBinder.ParameterBinder<?> firstParameterBinder, secondParameterBinder; @Mock private TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler; @Mock private Assigner assigner; private Assigner.Typing typing = Assigner.Typing.STATIC; @Mock private StackManipulation assignmentBinding, methodInvocation, termination; @Mock private TargetMethodAnnotationDrivenBinder.MethodInvoker methodInvoker; @Mock private Implementation.Target implementationTarget; @Mock private MethodDescription sourceMethod, targetMethod; @Mock private TypeDescription.Generic sourceTypeDescription, targetTypeDescription; @Mock private TypeDescription instrumentedType; @Mock private AnnotationDescription.ForLoadedAnnotation<FirstPseudoAnnotation> firstPseudoAnnotation; @Mock private AnnotationDescription.ForLoadedAnnotation<SecondPseudoAnnotation> secondPseudoAnnotation; @Mock private MethodVisitor methodVisitor; @Mock private Implementation.Context implementationContext; @Mock private ParameterDescription firstParameter, secondParameter; @SuppressWarnings("unchecked") private static MethodDelegationBinder.ParameterBinding<?> prepareArgumentBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder<?> parameterBinder, Class<? extends Annotation> annotationType, Object identificationToken) { doReturn(annotationType).when(parameterBinder).getHandledType(); MethodDelegationBinder.ParameterBinding<?> parameterBinding = mock(MethodDelegationBinder.ParameterBinding.class); when(parameterBinding.isValid()).thenReturn(true); when(parameterBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0)); when(parameterBinding.getIdentificationToken()).thenReturn(identificationToken); when(((TargetMethodAnnotationDrivenBinder.ParameterBinder) parameterBinder).bind(any(AnnotationDescription.Loadable.class), any(MethodDescription.class), any(ParameterDescription.class), any(Implementation.Target.class), any(Assigner.class), any(Assigner.Typing.class))) .thenReturn(parameterBinding); return parameterBinding; } @Before @SuppressWarnings("unchecked") public void setUp() throws Exception { when(assignmentBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class))) .thenReturn(new StackManipulation.Size(0, 0)); when(assigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class))) .thenReturn(assignmentBinding); when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(methodInvocation); when(methodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class))) .thenReturn(new StackManipulation.Size(0, 0)); when(assignmentBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class))) .thenReturn(new StackManipulation.Size(0, 0)); TypeDescription declaringType = mock(TypeDescription.class); when(declaringType.getInternalName()).thenReturn(FOO); when(declaringType.isInterface()).thenReturn(false); when(targetMethod.getInternalName()).thenReturn(BAR); when(targetMethod.isStatic()).thenReturn(true); when(targetMethod.getDeclaringType()).thenReturn(declaringType); when(targetMethod.getDescriptor()).thenReturn(BAZ); when(firstParameter.getDeclaringMethod()).thenReturn(targetMethod); when(firstParameter.getIndex()).thenReturn(0); when(secondParameter.getDeclaringMethod()).thenReturn(targetMethod); when(secondParameter.getIndex()).thenReturn(1); when(targetMethod.getParameters()) .thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(firstParameter, secondParameter)); when(firstPseudoAnnotation.getAnnotationType()) .thenReturn(new TypeDescription.ForLoadedType(FirstPseudoAnnotation.class)); when(firstPseudoAnnotation.prepare(FirstPseudoAnnotation.class)).thenReturn(firstPseudoAnnotation); when(secondPseudoAnnotation.getAnnotationType()) .thenReturn(new TypeDescription.ForLoadedType(SecondPseudoAnnotation.class)); when(secondPseudoAnnotation.prepare(SecondPseudoAnnotation.class)).thenReturn(secondPseudoAnnotation); when(sourceTypeDescription.getStackSize()).thenReturn(StackSize.ZERO); when(targetTypeDescription.getStackSize()).thenReturn(StackSize.ZERO); when(sourceMethod.getReturnType()).thenReturn(sourceTypeDescription); when(targetMethod.getReturnType()).thenReturn(targetTypeDescription); when(terminationHandler.resolve(assigner, typing, sourceMethod, targetMethod)).thenReturn(termination); when(termination.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0)); when(implementationTarget.getInstrumentedType()).thenReturn(instrumentedType); } @After public void tearDown() throws Exception { verifyZeroInteractions(implementationContext); } @Test(expected = IllegalArgumentException.class) public void testConflictingBinderBinding() throws Exception { doReturn(FirstPseudoAnnotation.class).when(firstParameterBinder).getHandledType(); doReturn(FirstPseudoAnnotation.class).when(secondParameterBinder).getHandledType(); TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder)); } @Test public void testIgnoreForBindingAnnotation() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true); AnnotationDescription ignoreForBinding = mock(AnnotationDescription.class); when(ignoreForBinding.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(IgnoreForBinding.class)); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(ignoreForBinding))); when(termination.isValid()).thenReturn(true); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList()); assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner).isValid(), is(false)); verifyZeroInteractions(assigner); verifyZeroInteractions(implementationTarget); verifyZeroInteractions(sourceMethod); } @Test public void testNonAccessible() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(false); when(assignmentBinding.isValid()).thenReturn(true); when(methodInvocation.isValid()).thenReturn(true); when(termination.isValid()).thenReturn(true); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList()); assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner).isValid(), is(false)); verifyZeroInteractions(terminationHandler); verifyZeroInteractions(assigner); verifyZeroInteractions(methodInvoker); } @Test public void testTerminationBinderMismatch() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true); when(assignmentBinding.isValid()).thenReturn(false); when(methodInvocation.isValid()).thenReturn(true); when(termination.isValid()).thenReturn(false); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList()); assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner).isValid(), is(false)); verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod); verifyNoMoreInteractions(terminationHandler); verifyZeroInteractions(assigner); verifyZeroInteractions(methodInvoker); } @Test public void testDoNotBindOnIllegalMethodInvocation() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true); when(assignmentBinding.isValid()).thenReturn(true); when(methodInvocation.isValid()).thenReturn(false); when(termination.isValid()).thenReturn(true); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(firstPseudoAnnotation))); when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(secondPseudoAnnotation))); MethodDelegationBinder.ParameterBinding<?> firstBinding = prepareArgumentBinder( firstParameterBinder, FirstPseudoAnnotation.class, new Key(FOO)); MethodDelegationBinder.ParameterBinding<?> secondBinding = prepareArgumentBinder( secondParameterBinder, SecondPseudoAnnotation.class, new Key(BAR)); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder)); MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner); assertThat(methodBinding.isValid(), is(false)); verify(firstBinding).isValid(); verify(secondBinding).isValid(); verify(termination).isValid(); } @Test @SuppressWarnings("unchecked") public void testBindingByDefault() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true); when(assignmentBinding.isValid()).thenReturn(true); when(methodInvocation.isValid()).thenReturn(true); when(termination.isValid()).thenReturn(true); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getType()).thenReturn(TypeDescription.Generic.OBJECT); when(secondParameter.getType()).thenReturn(TypeDescription.Generic.OBJECT); when(sourceMethod.getParameters()).thenReturn(new ParameterList.Explicit(firstParameter, secondParameter)); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList()); MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner); assertThat(methodBinding.isValid(), is(true)); assertThat(methodBinding.getTarget(), is(targetMethod)); assertThat(methodBinding.getTargetParameterIndex(new ArgumentTypeResolver.ParameterIndexToken(0)), is(0)); assertThat(methodBinding.getTargetParameterIndex(new ArgumentTypeResolver.ParameterIndexToken(1)), is(1)); StackManipulation.Size size = methodBinding.apply(methodVisitor, implementationContext); assertThat(size.getSizeImpact(), is(2)); assertThat(size.getMaximalSize(), is(2)); verify(firstParameter, atLeast(1)).getDeclaredAnnotations(); verify(secondParameter, atLeast(1)).getDeclaredAnnotations(); verify(targetMethod, atLeast(1)).getDeclaredAnnotations(); verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod); verifyNoMoreInteractions(terminationHandler); verify(methodInvoker).invoke(targetMethod); verifyNoMoreInteractions(methodInvoker); } @Test @SuppressWarnings("unchecked") public void testBindingByParameterAnnotations() throws Exception { when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true); when(assignmentBinding.isValid()).thenReturn(true); when(methodInvocation.isValid()).thenReturn(true); when(termination.isValid()).thenReturn(true); when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty()); when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(secondPseudoAnnotation))); when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(firstPseudoAnnotation))); MethodDelegationBinder.ParameterBinding<?> firstBinding = prepareArgumentBinder( firstParameterBinder, FirstPseudoAnnotation.class, new Key(FOO)); MethodDelegationBinder.ParameterBinding<?> secondBinding = prepareArgumentBinder( secondParameterBinder, SecondPseudoAnnotation.class, new Key(BAR)); MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder)); MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget, sourceMethod, terminationHandler, methodInvoker, assigner); assertThat(methodBinding.isValid(), is(true)); assertThat(methodBinding.getTarget(), is(targetMethod)); assertThat(methodBinding.getTargetParameterIndex(new Key(FOO)), is(1)); assertThat(methodBinding.getTargetParameterIndex(new Key(BAR)), is(0)); StackManipulation.Size size = methodBinding.apply(methodVisitor, implementationContext); assertThat(size.getSizeImpact(), is(0)); assertThat(size.getMaximalSize(), is(0)); verifyZeroInteractions(methodVisitor); verify(targetMethod, atLeast(1)).getDeclaredAnnotations(); verify(firstParameter, atLeast(1)).getDeclaredAnnotations(); verify(secondParameter, atLeast(1)).getDeclaredAnnotations(); verifyNoMoreInteractions(assigner); verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod); verifyNoMoreInteractions(terminationHandler); verify(methodInvoker).invoke(targetMethod); verifyNoMoreInteractions(methodInvoker); verify(firstParameterBinder, atLeast(1)).getHandledType(); verify((TargetMethodAnnotationDrivenBinder.ParameterBinder) firstParameterBinder).bind(firstPseudoAnnotation, sourceMethod, secondParameter, implementationTarget, assigner, Assigner.Typing.STATIC); verifyNoMoreInteractions(firstParameterBinder); verify(secondParameterBinder, atLeast(1)).getHandledType(); verify((TargetMethodAnnotationDrivenBinder.ParameterBinder) secondParameterBinder).bind(secondPseudoAnnotation, sourceMethod, firstParameter, implementationTarget, assigner, Assigner.Typing.STATIC); verifyNoMoreInteractions(secondParameterBinder); verify(firstBinding, atLeast(1)).isValid(); verify(firstBinding).getIdentificationToken(); verify(secondBinding, atLeast(1)).isValid(); verify(secondBinding).getIdentificationToken(); } @Test public void testAnnotation() throws Exception { Argument argument = new TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Unbound.DefaultArgument(0); Argument sample = (Argument) Sample.class.getDeclaredMethod(FOO, Object.class).getParameterAnnotations()[0][0]; assertThat(argument.toString(), is(sample.toString())); assertThat(argument.hashCode(), is(sample.hashCode())); assertThat(argument, is(sample)); assertThat(argument, is(argument)); assertThat(argument, not(equalTo(null))); assertThat(argument, not(new Object())); } @Test public void testObjectProperties() throws Exception { ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.class).refine(new ObjectPropertyAssertion.Refinement<TargetMethodAnnotationDrivenBinder.ParameterBinder>() { @Override public void apply(TargetMethodAnnotationDrivenBinder.ParameterBinder mock) { when(mock.getHandledType()).thenReturn(Annotation.class); } }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() { @Override public void apply(TypeDescription mock) { when(mock.getStackSize()).thenReturn(StackSize.ZERO); } }).create(new ObjectPropertyAssertion.Creator<List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>>() { @Override public List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> create() { TargetMethodAnnotationDrivenBinder.ParameterBinder<?> parameterBinder = mock(TargetMethodAnnotationDrivenBinder.ParameterBinder.class); doReturn(Annotation.class).when(parameterBinder).getHandledType(); return Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>singletonList(parameterBinder); } }).apply(); ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.Record.class).apply(); ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.TerminationHandler.class).apply(); ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.class).apply(); ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Bound.class).apply(); ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Unbound.class).apply(); } private interface Sample { void foo(@Argument(0) Object foo); } private @interface FirstPseudoAnnotation { } private @interface SecondPseudoAnnotation { } private static class Key { private final String value; private Key(String value) { this.value = value; } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && value.equals(((Key) other).value); } @Override public int hashCode() { return value.hashCode(); } } }