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