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.faking;
6   
7   import edu.umd.cs.findbugs.annotations.NonNull;
8   import edu.umd.cs.findbugs.annotations.Nullable;
9   
10  import java.lang.reflect.Member;
11  import java.lang.reflect.Method;
12  
13  import mockit.internal.faking.FakeMethods.FakeMethod;
14  import mockit.internal.reflection.MethodReflection;
15  import mockit.internal.reflection.RealMethodOrConstructor;
16  import mockit.internal.util.ClassLoad;
17  
18  final class FakeState {
19      private static final ClassLoader THIS_CL = FakeState.class.getClassLoader();
20  
21      @NonNull
22      final FakeMethod fakeMethod;
23      @Nullable
24      private Method actualFakeMethod;
25      @Nullable
26      private Member realMethodOrConstructor;
27      @Nullable
28      private Object realClass;
29  
30      // Current fake invocation state:
31      private int invocationCount;
32      @Nullable
33      private ThreadLocal<FakeInvocation> proceedingInvocation;
34  
35      // Helper field just for synchronization:
36      @NonNull
37      private final Object invocationCountLock;
38  
39      FakeState(@NonNull FakeMethod fakeMethod) {
40          this.fakeMethod = fakeMethod;
41          invocationCountLock = new Object();
42  
43          if (fakeMethod.canBeReentered()) {
44              makeReentrant();
45          }
46      }
47  
48      FakeState(@NonNull FakeState fakeState) {
49          fakeMethod = fakeState.fakeMethod;
50          actualFakeMethod = fakeState.actualFakeMethod;
51          realMethodOrConstructor = fakeState.realMethodOrConstructor;
52          invocationCountLock = new Object();
53  
54          if (fakeState.proceedingInvocation != null) {
55              makeReentrant();
56          }
57      }
58  
59      @NonNull
60      Class<?> getRealClass() {
61          return fakeMethod.getRealClass();
62      }
63  
64      private void makeReentrant() {
65          proceedingInvocation = new ThreadLocal<>();
66      }
67  
68      boolean update() {
69          if (proceedingInvocation != null) {
70              FakeInvocation invocation = proceedingInvocation.get();
71  
72              if (invocation != null && invocation.proceeding) {
73                  invocation.proceeding = false;
74                  return false;
75              }
76          }
77  
78          synchronized (invocationCountLock) {
79              invocationCount++;
80          }
81  
82          return true;
83      }
84  
85      int getTimesInvoked() {
86          synchronized (invocationCountLock) {
87              return invocationCount;
88          }
89      }
90  
91      @NonNull
92      Member getRealMethodOrConstructor(@NonNull String fakedClassDesc, @NonNull String fakedMethodName,
93              @NonNull String fakedMethodDesc) {
94          Class<?> fakedClass = ClassLoad.loadFromLoader(THIS_CL, fakedClassDesc.replace('/', '.'));
95          return getRealMethodOrConstructor(fakedClass, fakedMethodName, fakedMethodDesc);
96      }
97  
98      @NonNull
99      Member getRealMethodOrConstructor(@NonNull Class<?> fakedClass, @NonNull String fakedMethodName,
100             @NonNull String fakedMethodDesc) {
101         Member member = realMethodOrConstructor;
102 
103         if (member == null || !fakedClass.equals(realClass)) {
104             String memberName = "$init".equals(fakedMethodName) ? "<init>" : fakedMethodName;
105 
106             RealMethodOrConstructor realMember;
107             try {
108                 realMember = new RealMethodOrConstructor(fakedClass, memberName, fakedMethodDesc);
109             } catch (NoSuchMethodException e) {
110                 throw new RuntimeException(e);
111             }
112 
113             member = realMember.getMember();
114 
115             if (!fakeMethod.isAdvice) {
116                 realMethodOrConstructor = member;
117                 realClass = fakedClass;
118             }
119         }
120 
121         return member;
122     }
123 
124     boolean shouldProceedIntoRealImplementation(@Nullable Object fake, @NonNull String classDesc) {
125         if (proceedingInvocation != null) {
126             FakeInvocation pendingInvocation = proceedingInvocation.get();
127 
128             // noinspection RedundantIfStatement
129             if (pendingInvocation != null && pendingInvocation.isMethodInSuperclass(fake, classDesc)) {
130                 return true;
131             }
132         }
133 
134         return false;
135     }
136 
137     void prepareToProceed(@NonNull FakeInvocation invocation) {
138         if (proceedingInvocation == null) {
139             throw new UnsupportedOperationException("Cannot proceed into abstract/interface method");
140         }
141 
142         if (fakeMethod.isForNativeMethod()) {
143             throw new UnsupportedOperationException("Cannot proceed into real implementation of native method");
144         }
145 
146         FakeInvocation previousInvocation = proceedingInvocation.get();
147 
148         if (previousInvocation != null) {
149             invocation.setPrevious(previousInvocation);
150         }
151 
152         proceedingInvocation.set(invocation);
153     }
154 
155     void prepareToProceedFromNonRecursiveFake(@NonNull FakeInvocation invocation) {
156         assert proceedingInvocation != null;
157         proceedingInvocation.set(invocation);
158     }
159 
160     void clearProceedIndicator() {
161         assert proceedingInvocation != null;
162         FakeInvocation currentInvocation = proceedingInvocation.get();
163         FakeInvocation previousInvocation = (FakeInvocation) currentInvocation.getPrevious();
164         proceedingInvocation.set(previousInvocation);
165     }
166 
167     @NonNull
168     Method getFakeMethod(@NonNull Class<?> fakeClass, @NonNull Class<?>[] parameterTypes) {
169         if (actualFakeMethod == null) {
170             actualFakeMethod = MethodReflection.findCompatibleMethod(fakeClass, fakeMethod.name, parameterTypes);
171         }
172 
173         return actualFakeMethod;
174     }
175 }