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.injection.constructor;
6   
7   import static mockit.internal.injection.InjectionPoint.getQualifiedName;
8   import static mockit.internal.injection.InjectionPoint.getTypeOfInjectionPointFromVarargsParameter;
9   import static mockit.internal.injection.InjectionPoint.kindOfInjectionPoint;
10  import static mockit.internal.injection.InjectionPoint.wrapInProviderIfNeeded;
11  import static mockit.internal.injection.InjectionProvider.NULL;
12  import static mockit.internal.reflection.ConstructorReflection.invokeAccessible;
13  import static mockit.internal.util.Utilities.NO_ARGS;
14  import static mockit.internal.util.Utilities.ensureThatMemberIsAccessible;
15  import static mockit.internal.util.Utilities.getClassType;
16  
17  import edu.umd.cs.findbugs.annotations.NonNull;
18  import edu.umd.cs.findbugs.annotations.Nullable;
19  
20  import java.lang.reflect.Array;
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Type;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import mockit.asm.types.JavaType;
27  import mockit.internal.injection.InjectionPoint.KindOfInjectionPoint;
28  import mockit.internal.injection.InjectionProvider;
29  import mockit.internal.injection.InjectionProviders;
30  import mockit.internal.injection.InjectionState;
31  import mockit.internal.injection.Injector;
32  import mockit.internal.injection.TestedClass;
33  import mockit.internal.injection.full.FullInjection;
34  import mockit.internal.state.ParameterNames;
35  import mockit.internal.state.TestRun;
36  import mockit.internal.util.MethodFormatter;
37  import mockit.internal.util.StackTrace;
38  
39  public final class ConstructorInjection extends Injector {
40      @NonNull
41      private final Constructor<?> constructor;
42  
43      public ConstructorInjection(@NonNull InjectionState injectionState, @Nullable FullInjection fullInjection,
44              @NonNull Constructor<?> constructor) {
45          super(injectionState, fullInjection);
46          ensureThatMemberIsAccessible(constructor);
47          this.constructor = constructor;
48      }
49  
50      @Nullable
51      public Object instantiate(@NonNull List<InjectionProvider> parameterProviders, @NonNull TestedClass testedClass,
52              boolean required, boolean needToConstruct) {
53          Type[] parameterTypes = constructor.getGenericParameterTypes();
54          int n = parameterTypes.length;
55          List<InjectionProvider> consumedInjectables = n == 0 ? null
56                  : injectionState.injectionProviders.saveConsumedInjectionProviders();
57          Object[] arguments = n == 0 ? NO_ARGS : new Object[n];
58          boolean varArgs = constructor.isVarArgs();
59  
60          if (varArgs) {
61              n--;
62          }
63  
64          for (int i = 0; i < n; i++) {
65              @NonNull
66              InjectionProvider parameterProvider = parameterProviders.get(i);
67              Object value;
68  
69              if (parameterProvider instanceof ConstructorParameter) {
70                  value = createOrReuseArgumentValue((ConstructorParameter) parameterProvider, required);
71  
72                  if (value == null && !needToConstruct) {
73                      return null;
74                  }
75              } else {
76                  value = getArgumentValueToInject(parameterProvider, i);
77              }
78  
79              if (value != NULL) {
80                  Type parameterType = parameterTypes[i];
81                  arguments[i] = wrapInProviderIfNeeded(parameterType, value);
82              }
83          }
84  
85          if (varArgs) {
86              Type parameterType = parameterTypes[n];
87              arguments[n] = obtainInjectedVarargsArray(parameterType, testedClass);
88          }
89  
90          if (consumedInjectables != null) {
91              injectionState.injectionProviders.restoreConsumedInjectionProviders(consumedInjectables);
92          }
93  
94          return invokeConstructor(arguments);
95      }
96  
97      @Nullable
98      private Object createOrReuseArgumentValue(@NonNull ConstructorParameter constructorParameter, boolean required) {
99          Object givenValue = constructorParameter.getValue(null);
100 
101         if (givenValue != null) {
102             return givenValue;
103         }
104 
105         assert fullInjection != null;
106 
107         Class<?> parameterClass = constructorParameter.getClassOfDeclaredType();
108         Object newOrReusedValue = null;
109 
110         if (FullInjection.isInstantiableType(parameterClass)) {
111             Type parameterType = constructorParameter.getDeclaredType();
112             KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(constructor);
113             injectionState.injectionProviders.setTypeOfInjectionPoint(parameterType, kindOfInjectionPoint);
114             String qualifiedName = getQualifiedName(constructorParameter.getAnnotations());
115             TestedClass nextTestedClass = new TestedClass(parameterType, parameterClass);
116 
117             newOrReusedValue = fullInjection.createOrReuseInstance(nextTestedClass, this, constructorParameter,
118                     qualifiedName);
119         } else {
120             fullInjection.setInjectionProvider(constructorParameter);
121         }
122 
123         if (newOrReusedValue == null && required) {
124             String parameterName = constructorParameter.getName();
125             String message = "Missing @Tested or @Injectable" + missingValueDescription(parameterName)
126                     + "\r\n  when initializing " + fullInjection;
127             IllegalStateException injectionFailure = new IllegalStateException(message);
128             StackTrace.filterStackTrace(injectionFailure);
129             throw injectionFailure;
130         }
131 
132         return newOrReusedValue;
133     }
134 
135     @NonNull
136     private Object getArgumentValueToInject(@NonNull InjectionProvider injectable, int parameterIndex) {
137         Object argument = injectionState.getValueToInject(injectable);
138 
139         if (argument == null) {
140             String classDesc = getClassDesc();
141             String constructorDesc = getConstructorDesc();
142             String parameterName = ParameterNames.getName(classDesc, constructorDesc, parameterIndex);
143 
144             if (parameterName == null) {
145                 parameterName = injectable.getName();
146             }
147 
148             throw new IllegalArgumentException(
149                     "No injectable value available" + missingValueDescription(parameterName));
150         }
151 
152         return argument;
153     }
154 
155     @NonNull
156     private String getClassDesc() {
157         return JavaType.getInternalName(constructor.getDeclaringClass());
158     }
159 
160     @NonNull
161     private String getConstructorDesc() {
162         return "<init>" + JavaType.getConstructorDescriptor(constructor);
163     }
164 
165     @NonNull
166     private Object obtainInjectedVarargsArray(@NonNull Type parameterType, @NonNull TestedClass testedClass) {
167         Type varargsElementType = getTypeOfInjectionPointFromVarargsParameter(parameterType);
168         KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(constructor);
169         InjectionProviders injectionProviders = injectionState.injectionProviders;
170         injectionProviders.setTypeOfInjectionPoint(varargsElementType, kindOfInjectionPoint);
171 
172         List<Object> varargValues = new ArrayList<>();
173         InjectionProvider injectable;
174 
175         while ((injectable = injectionProviders.findNextInjectableForInjectionPoint(testedClass)) != null) {
176             Object value = injectionState.getValueToInject(injectable);
177 
178             if (value != null) {
179                 value = wrapInProviderIfNeeded(varargsElementType, value);
180                 varargValues.add(value);
181             }
182         }
183 
184         return newArrayFromList(varargsElementType, varargValues);
185     }
186 
187     @NonNull
188     private static Object newArrayFromList(@NonNull Type elementType, @NonNull List<Object> values) {
189         Class<?> componentType = getClassType(elementType);
190         int elementCount = values.size();
191         Object array = Array.newInstance(componentType, elementCount);
192 
193         for (int i = 0; i < elementCount; i++) {
194             Array.set(array, i, values.get(i));
195         }
196 
197         return array;
198     }
199 
200     @NonNull
201     private String missingValueDescription(@NonNull String name) {
202         String classDesc = getClassDesc();
203         String constructorDesc = getConstructorDesc();
204         String constructorDescription = new MethodFormatter(classDesc, constructorDesc).toString();
205         int p = constructorDescription.indexOf('#');
206         // noinspection DynamicRegexReplaceableByCompiledPattern
207         String friendlyConstructorDesc = constructorDescription.substring(p + 1).replace("java.lang.", "");
208 
209         return " for parameter \"" + name + "\" in constructor " + friendlyConstructorDesc;
210     }
211 
212     @NonNull
213     private Object invokeConstructor(@NonNull Object[] arguments) {
214         TestRun.exitNoMockingZone();
215 
216         try {
217             return invokeAccessible(constructor, arguments);
218         } finally {
219             TestRun.enterNoMockingZone();
220         }
221     }
222 }