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.mocking;
6   
7   import static mockit.internal.reflection.FieldReflection.getFieldValue;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.lang.instrument.ClassDefinition;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.HashMap;
16  import java.util.List;
17  import java.util.Map;
18  
19  import mockit.asm.classes.ClassReader;
20  import mockit.asm.types.JavaType;
21  import mockit.internal.BaseClassModifier;
22  import mockit.internal.capturing.CaptureOfImplementations;
23  import mockit.internal.startup.Startup;
24  import mockit.internal.state.MockFixture;
25  import mockit.internal.state.TestRun;
26  import mockit.internal.util.Utilities;
27  
28  public class CaptureOfNewInstances extends CaptureOfImplementations<MockedType> {
29      protected static final class Capture {
30          @NonNull
31          final MockedType typeMetadata;
32          @Nullable
33          private Object originalMockInstance;
34          @NonNull
35          private final List<Object> instancesCaptured;
36  
37          private Capture(@NonNull MockedType typeMetadata, @Nullable Object originalMockInstance) {
38              this.typeMetadata = typeMetadata;
39              this.originalMockInstance = originalMockInstance;
40              instancesCaptured = new ArrayList<>(4);
41          }
42  
43          private boolean isInstanceAlreadyCaptured(@NonNull Object mock) {
44              return Utilities.containsReference(instancesCaptured, mock);
45          }
46  
47          private boolean captureInstance(@Nullable Object fieldOwner, @NonNull Object instance) {
48              if (instancesCaptured.size() < typeMetadata.getMaxInstancesToCapture()) {
49                  if (fieldOwner != null && typeMetadata.field != null && originalMockInstance == null) {
50                      originalMockInstance = getFieldValue(typeMetadata.field, fieldOwner);
51                  }
52  
53                  instancesCaptured.add(instance);
54                  return true;
55              }
56  
57              return false;
58          }
59  
60          void reset() {
61              originalMockInstance = null;
62              instancesCaptured.clear();
63          }
64      }
65  
66      @NonNull
67      private final Map<Class<?>, List<Capture>> baseTypeToCaptures;
68  
69      CaptureOfNewInstances() {
70          baseTypeToCaptures = new HashMap<>();
71      }
72  
73      @NonNull
74      protected final Collection<List<Capture>> getCapturesForAllBaseTypes() {
75          return baseTypeToCaptures.values();
76      }
77  
78      @NonNull
79      @Override
80      protected BaseClassModifier createModifier(@Nullable ClassLoader cl, @NonNull ClassReader cr,
81              @NonNull Class<?> baseType, @Nullable MockedType typeMetadata) {
82          MockedClassModifier modifier = new MockedClassModifier(cl, cr, typeMetadata);
83          String baseTypeDesc = JavaType.getInternalName(baseType);
84          modifier.setClassNameForCapturedInstanceMethods(baseTypeDesc);
85          return modifier;
86      }
87  
88      @Override
89      protected void redefineClass(@NonNull Class<?> realClass, @NonNull byte[] modifiedClass) {
90          ClassDefinition newClassDefinition = new ClassDefinition(realClass, modifiedClass);
91          Startup.redefineMethods(newClassDefinition);
92  
93          MockFixture mockFixture = TestRun.mockFixture();
94          mockFixture.addRedefinedClass(newClassDefinition);
95          mockFixture.registerMockedClass(realClass);
96      }
97  
98      void registerCaptureOfNewInstances(@NonNull MockedType typeMetadata, @Nullable Object mockInstance) {
99          Class<?> baseType = typeMetadata.getClassType();
100 
101         if (!typeMetadata.isFinalFieldOrParameter()) {
102             makeSureAllSubtypesAreModified(typeMetadata);
103         }
104 
105         List<Capture> captures = baseTypeToCaptures.get(baseType);
106 
107         if (captures == null) {
108             captures = new ArrayList<>();
109             baseTypeToCaptures.put(baseType, captures);
110         }
111 
112         captures.add(new Capture(typeMetadata, mockInstance));
113     }
114 
115     void makeSureAllSubtypesAreModified(@NonNull MockedType typeMetadata) {
116         Class<?> baseType = typeMetadata.getClassType();
117         makeSureAllSubtypesAreModified(baseType, typeMetadata.fieldFromTestClass, typeMetadata);
118     }
119 
120     public boolean captureNewInstance(@Nullable Object fieldOwner, @NonNull Object mock) {
121         Class<?> mockedClass = mock.getClass();
122         List<Capture> captures = baseTypeToCaptures.get(mockedClass);
123         boolean constructorModifiedForCaptureOnly = captures == null;
124 
125         if (constructorModifiedForCaptureOnly) {
126             captures = findCaptures(mockedClass);
127 
128             if (captures == null) {
129                 return false;
130             }
131         }
132 
133         Capture captureFound = findCapture(fieldOwner, mock, captures);
134 
135         if (captureFound != null) {
136             if (captureFound.typeMetadata.injectable) {
137                 TestRun.getExecutingTest().addCapturedInstanceForInjectableMock(captureFound.originalMockInstance,
138                         mock);
139                 constructorModifiedForCaptureOnly = true;
140             } else {
141                 TestRun.getExecutingTest().addCapturedInstance(captureFound.originalMockInstance, mock);
142             }
143         }
144 
145         return constructorModifiedForCaptureOnly;
146     }
147 
148     @Nullable
149     private List<Capture> findCaptures(@NonNull Class<?> mockedClass) {
150         Class<?>[] interfaces = mockedClass.getInterfaces();
151 
152         for (Class<?> anInterface : interfaces) {
153             List<Capture> found = baseTypeToCaptures.get(anInterface);
154 
155             if (found != null) {
156                 return found;
157             }
158         }
159 
160         Class<?> superclass = mockedClass.getSuperclass();
161 
162         if (superclass == Object.class) {
163             return null;
164         }
165 
166         List<Capture> found = baseTypeToCaptures.get(superclass);
167 
168         return found != null ? found : findCaptures(superclass);
169     }
170 
171     @Nullable
172     private static Capture findCapture(@Nullable Object fieldOwner, @NonNull Object mock,
173             @NonNull List<Capture> captures) {
174         for (Capture capture : captures) {
175             if (capture.isInstanceAlreadyCaptured(mock)) {
176                 break;
177             } else if (capture.captureInstance(fieldOwner, mock)) {
178                 return capture;
179             }
180         }
181 
182         return null;
183     }
184 
185     public void cleanUp() {
186         baseTypeToCaptures.clear();
187     }
188 }