View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.expectations.invocation;
7   
8   import static mockit.internal.reflection.MethodReflection.JAVA_LANG;
9   import static mockit.internal.reflection.MethodReflection.findNonPrivateHandlerMethod;
10  import static mockit.internal.reflection.MethodReflection.invoke;
11  
12  import edu.umd.cs.findbugs.annotations.NonNull;
13  import edu.umd.cs.findbugs.annotations.Nullable;
14  
15  import java.lang.reflect.Method;
16  import java.util.concurrent.locks.ReentrantLock;
17  
18  import mockit.Delegate;
19  import mockit.Invocation;
20  import mockit.asm.types.JavaType;
21  import mockit.internal.expectations.RecordAndReplayExecution;
22  import mockit.internal.reflection.ParameterReflection;
23  import mockit.internal.state.TestRun;
24  import mockit.internal.util.MethodFormatter;
25  import mockit.internal.util.TypeDescriptor;
26  
27  final class DelegatedResult extends InvocationResult {
28      private static final Object[] NO_ARGS = {};
29  
30      @NonNull
31      private final ExpectedInvocation recordedInvocation;
32      @NonNull
33      private final Object targetObject;
34      @NonNull
35      private final Method methodToInvoke;
36      @NonNull
37      private final Class<?> targetReturnType;
38      private final boolean hasInvocationParameter;
39      private final int numberOfRegularParameters;
40  
41      DelegatedResult(@NonNull ExpectedInvocation recordedInvocation, @NonNull Delegate<?> delegate) {
42          this.recordedInvocation = recordedInvocation;
43          targetObject = delegate;
44          methodToInvoke = findNonPrivateHandlerMethod(delegate);
45  
46          JavaType returnType = JavaType.getReturnType(recordedInvocation.getMethodNameAndDescription());
47          targetReturnType = TypeDescriptor.getClassForType(returnType);
48  
49          Class<?>[] parameters = methodToInvoke.getParameterTypes();
50          int n = parameters.length;
51  
52          hasInvocationParameter = n > 0 && parameters[0] == Invocation.class;
53          numberOfRegularParameters = hasInvocationParameter ? n - 1 : n;
54      }
55  
56      @Nullable
57      @Override
58      Object produceResult(@Nullable Object invokedObject, @NonNull ExpectedInvocation invocation,
59              @NonNull InvocationConstraints constraints, @NonNull Object[] args) {
60          Object[] delegateArgs = numberOfRegularParameters == 0 ? NO_ARGS : args;
61          return hasInvocationParameter
62                  ? invokeMethodWithContext(invokedObject, invocation, constraints, args, delegateArgs)
63                  : executeMethodToInvoke(delegateArgs);
64      }
65  
66      @Nullable
67      private Object invokeMethodWithContext(@Nullable Object mockOrRealObject,
68              @NonNull ExpectedInvocation expectedInvocation, @NonNull InvocationConstraints constraints,
69              @NonNull Object[] invokedArgs, @NonNull Object[] delegateArgs) {
70          Invocation delegateInvocation = new DelegateInvocation(mockOrRealObject, invokedArgs, expectedInvocation,
71                  constraints);
72          Object[] delegateArgsWithInvocation = ParameterReflection.argumentsWithExtraFirstValue(delegateArgs,
73                  delegateInvocation);
74          Object result = executeMethodToInvoke(delegateArgsWithInvocation);
75  
76          return expectedInvocation.isConstructor() && TestRun.getExecutingTest().isProceedingIntoRealImplementation()
77                  ? Void.class
78                  : result;
79      }
80  
81      @Nullable
82      private Object executeMethodToInvoke(@NonNull Object[] args) {
83          ReentrantLock reentrantLock = RecordAndReplayExecution.RECORD_OR_REPLAY_LOCK;
84  
85          if (!reentrantLock.isHeldByCurrentThread()) {
86              return executeTargetMethod(args);
87          }
88  
89          reentrantLock.unlock();
90  
91          try {
92              return executeTargetMethod(args);
93          } finally {
94              // noinspection LockAcquiredButNotSafelyReleased
95              reentrantLock.lock();
96          }
97      }
98  
99      @Nullable
100     private Object executeTargetMethod(@NonNull Object[] args) {
101         Object returnValue = invoke(targetObject, methodToInvoke, args);
102         Class<?> fromReturnType = methodToInvoke.getReturnType();
103 
104         if (returnValue == null || targetReturnType.isInstance(returnValue)) {
105             if (fromReturnType == void.class && fromReturnType != targetReturnType && targetReturnType.isPrimitive()) {
106                 String returnTypeName = JAVA_LANG.matcher(targetReturnType.getName()).replaceAll("");
107                 MethodFormatter methodDesc = new MethodFormatter(recordedInvocation.getClassDesc(),
108                         recordedInvocation.getMethodNameAndDescription());
109                 String msg = "void return type incompatible with return type " + returnTypeName + " of " + methodDesc;
110                 throw new IllegalArgumentException(msg);
111             }
112 
113             return returnValue;
114         }
115 
116         ReturnTypeConversion typeConversion = new ReturnTypeConversion(recordedInvocation, targetReturnType,
117                 returnValue);
118         return typeConversion.getConvertedValue();
119     }
120 }