• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockito.internal.creation.instance;
6 
7 import java.lang.reflect.Constructor;
8 import java.lang.reflect.InvocationTargetException;
9 import java.util.Arrays;
10 import java.util.LinkedList;
11 import java.util.List;
12 
13 import org.mockito.creation.instance.Instantiator;
14 import org.mockito.creation.instance.InstantiationException;
15 import org.mockito.internal.util.Primitives;
16 import org.mockito.internal.util.reflection.AccessibilityChanger;
17 
18 import static org.mockito.internal.util.StringUtil.join;
19 
20 public class ConstructorInstantiator implements Instantiator {
21 
22     /**
23      * Whether or not the constructors used for creating an object refer to an outer instance or not.
24      * This member is only used to for constructing error messages.
25      * If an outer inject exists, it would be the first ([0]) element of the {@link #constructorArgs} array.
26      */
27     private final boolean hasOuterClassInstance;
28     private final Object[] constructorArgs;
29 
ConstructorInstantiator(boolean hasOuterClassInstance, Object... constructorArgs)30     public ConstructorInstantiator(boolean hasOuterClassInstance, Object... constructorArgs) {
31         this.hasOuterClassInstance = hasOuterClassInstance;
32         this.constructorArgs = constructorArgs;
33     }
34 
newInstance(Class<T> cls)35     public <T> T newInstance(Class<T> cls) {
36         return withParams(cls, constructorArgs);
37     }
38 
withParams(Class<T> cls, Object... params)39     private <T> T withParams(Class<T> cls, Object... params) {
40         List<Constructor<?>> matchingConstructors = new LinkedList<Constructor<?>>();
41         try {
42             for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
43                 Class<?>[] types = constructor.getParameterTypes();
44                 if (paramsMatch(types, params)) {
45                     evaluateConstructor(matchingConstructors, constructor);
46                 }
47             }
48 
49             if (matchingConstructors.size() == 1) {
50                 return invokeConstructor(matchingConstructors.get(0), params);
51             }
52         } catch (Exception e) {
53             throw paramsException(cls, e);
54         }
55         if (matchingConstructors.size() == 0) {
56             throw noMatchingConstructor(cls);
57         } else {
58             throw multipleMatchingConstructors(cls, matchingConstructors);
59         }
60     }
61 
62     @SuppressWarnings("unchecked")
invokeConstructor(Constructor<?> constructor, Object... params)63     private static <T> T invokeConstructor(Constructor<?> constructor, Object... params) throws java.lang.InstantiationException, IllegalAccessException, InvocationTargetException {
64         AccessibilityChanger accessibility = new AccessibilityChanger();
65         accessibility.enableAccess(constructor);
66         return (T) constructor.newInstance(params);
67     }
68 
paramsException(Class<?> cls, Exception e)69     private InstantiationException paramsException(Class<?> cls, Exception e) {
70         return new InstantiationException(join(
71                 "Unable to create instance of '" + cls.getSimpleName() + "'.",
72                 "Please ensure the target class has " + constructorArgsString() + " and executes cleanly.")
73                 , e);
74     }
75 
constructorArgTypes()76     private String constructorArgTypes() {
77         int argPos = 0;
78         if (hasOuterClassInstance) {
79             ++argPos;
80         }
81         String[] constructorArgTypes = new String[constructorArgs.length - argPos];
82         for (int i = argPos; i < constructorArgs.length; ++i) {
83             constructorArgTypes[i - argPos] = constructorArgs[i] == null ? null : constructorArgs[i].getClass().getName();
84         }
85         return Arrays.toString(constructorArgTypes);
86     }
87 
noMatchingConstructor(Class<?> cls)88     private InstantiationException noMatchingConstructor(Class<?> cls) {
89         String constructorString = constructorArgsString();
90         String outerInstanceHint = "";
91         if (hasOuterClassInstance) {
92             outerInstanceHint = " and provided outer instance is correct";
93         }
94         return new InstantiationException(join("Unable to create instance of '" + cls.getSimpleName() + "'.",
95                 "Please ensure that the target class has " + constructorString + outerInstanceHint + ".")
96                 , null);
97     }
98 
constructorArgsString()99     private String constructorArgsString() {
100         String constructorString;
101         if (constructorArgs.length == 0 || (hasOuterClassInstance && constructorArgs.length == 1)) {
102             constructorString = "a 0-arg constructor";
103         } else {
104             constructorString = "a constructor that matches these argument types: " + constructorArgTypes();
105         }
106         return constructorString;
107     }
108 
multipleMatchingConstructors(Class<?> cls, List<Constructor<?>> constructors)109     private InstantiationException multipleMatchingConstructors(Class<?> cls, List<Constructor<?>> constructors) {
110         return new InstantiationException(join("Unable to create instance of '" + cls.getSimpleName() + "'.",
111                 "Multiple constructors could be matched to arguments of types " + constructorArgTypes() + ":",
112                 join("", " - ", constructors),
113                 "If you believe that Mockito could do a better job deciding on which constructor to use, please let us know.",
114                 "Ticket 685 contains the discussion and a workaround for ambiguous constructors using inner class.",
115                 "See https://github.com/mockito/mockito/issues/685"
116             ), null);
117     }
118 
paramsMatch(Class<?>[] types, Object[] params)119     private static boolean paramsMatch(Class<?>[] types, Object[] params) {
120         if (params.length != types.length) {
121             return false;
122         }
123         for (int i = 0; i < params.length; i++) {
124             if (params[i] == null) {
125                 if (types[i].isPrimitive()) {
126                     return false;
127                 }
128             } else if ((!types[i].isPrimitive() && !types[i].isInstance(params[i])) ||
129                     (types[i].isPrimitive() && !types[i].equals(Primitives.primitiveTypeOf(params[i].getClass())))) {
130                 return false;
131             }
132         }
133         return true;
134     }
135 
136     /**
137      * Evalutes {@code constructor} against the currently found {@code matchingConstructors} and determines if
138      * it's a better match to the given arguments, a worse match, or an equivalently good match.
139      * <p>
140      * This method tries to emulate the behavior specified in
141      * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2">JLS 15.12.2. Compile-Time
142      * Step 2: Determine Method Signature</a>. A constructor X is deemed to be a better match than constructor Y to the
143      * given argument list if they are both applicable, constructor X has at least one parameter than is more specific
144      * than the corresponding parameter of constructor Y, and constructor Y has no parameter than is more specific than
145      * the corresponding parameter in constructor X.
146      * </p>
147      * <p>
148      * If {@code constructor} is a better match than the constructors in the {@code matchingConstructors} list, the list
149      * is cleared, and it's added to the list as a singular best matching constructor (so far).<br/>
150      * If {@code constructor} is an equivalently good of a match as the constructors in the {@code matchingConstructors}
151      * list, it's added to the list.<br/>
152      * If {@code constructor} is a worse match than the constructors in the {@code matchingConstructors} list, the list
153      * will remain unchanged.
154      * </p>
155      *
156      * @param matchingConstructors A list of equivalently best matching constructors found so far
157      * @param constructor The constructor to be evaluated against this list
158      */
evaluateConstructor(List<Constructor<?>> matchingConstructors, Constructor<?> constructor)159     private void evaluateConstructor(List<Constructor<?>> matchingConstructors, Constructor<?> constructor) {
160         boolean newHasBetterParam = false;
161         boolean existingHasBetterParam = false;
162 
163         Class<?>[] paramTypes = constructor.getParameterTypes();
164         for (int i = 0; i < paramTypes.length; ++i) {
165             Class<?> paramType = paramTypes[i];
166             if (!paramType.isPrimitive()) {
167                 for (Constructor<?> existingCtor : matchingConstructors) {
168                     Class<?> existingParamType = existingCtor.getParameterTypes()[i];
169                     if (paramType != existingParamType) {
170                         if (paramType.isAssignableFrom(existingParamType)) {
171                             existingHasBetterParam = true;
172                         } else {
173                             newHasBetterParam = true;
174                         }
175                     }
176                 }
177             }
178         }
179         if (!existingHasBetterParam) {
180             matchingConstructors.clear();
181         }
182         if (newHasBetterParam || !existingHasBetterParam) {
183             matchingConstructors.add(constructor);
184         }
185     }
186 }
187