• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 
6 package org.mockito.internal.configuration.injection;
7 
8 import org.mockito.exceptions.Reporter;
9 import org.mockito.exceptions.base.MockitoException;
10 import org.mockito.internal.configuration.injection.filter.FinalMockCandidateFilter;
11 import org.mockito.internal.configuration.injection.filter.MockCandidateFilter;
12 import org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter;
13 import org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter;
14 import org.mockito.internal.util.collections.ListUtil;
15 import org.mockito.internal.util.reflection.FieldInitializationReport;
16 import org.mockito.internal.util.reflection.FieldInitializer;
17 
18 import java.lang.reflect.Field;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Modifier;
21 import java.util.*;
22 
23 import static org.mockito.internal.util.collections.Sets.newMockSafeHashSet;
24 
25 /**
26  * Inject mocks using first setters then fields, if no setters available.
27  *
28  * <p>
29  * <u>Algorithm :<br></u>
30  * for each field annotated by @InjectMocks
31  *   <ul>
32  *   <li>initialize field annotated by @InjectMocks
33  *   <li>for each fields of a class in @InjectMocks type hierarchy
34  *     <ul>
35  *     <li>make a copy of mock candidates
36  *     <li>order fields rom sub-type to super-type, then by field name
37  *     <li>for the list of fields in a class try two passes of :
38  *         <ul>
39  *             <li>find mock candidate by type
40  *             <li>if more than <b>*one*</b> candidate find mock candidate on name
41  *             <li>if one mock candidate then
42  *                 <ul>
43  *                     <li>set mock by property setter if possible
44  *                     <li>else set mock by field injection
45  *                 </ul>
46  *             <li>remove mock from mocks copy (mocks are just injected once in a class)
47  *             <li>remove injected field from list of class fields
48  *         </ul>
49  *     <li>else don't fail, user will then provide dependencies
50  *     </ul>
51  *   </ul>
52  * </p>
53  *
54  * <p>
55  * <u>Note:</u> If the field needing injection is not initialized, the strategy tries
56  * to create one using a no-arg constructor of the field type.
57  * </p>
58  */
59 public class PropertyAndSetterInjection extends MockInjectionStrategy {
60 
61     private final MockCandidateFilter mockCandidateFilter = new TypeBasedCandidateFilter(new NameBasedCandidateFilter(new FinalMockCandidateFilter()));
62     private Comparator<Field> superTypesLast = new FieldTypeAndNameComparator();
63 
64     private ListUtil.Filter<Field> notFinalOrStatic = new ListUtil.Filter<Field>() {
65         public boolean isOut(Field object) {
66             return Modifier.isFinal(object.getModifiers()) || Modifier.isStatic(object.getModifiers());
67         }
68     };
69 
70 
processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates)71     public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates) {
72         // Set<Object> mocksToBeInjected = new HashSet<Object>(mockCandidates);
73         FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner);
74 
75         // for each field in the class hierarchy
76         boolean injectionOccurred = false;
77         Class<?> fieldClass = report.fieldClass();
78         Object fieldInstanceNeedingInjection = report.fieldInstance();
79         while (fieldClass != Object.class) {
80             injectionOccurred |= injectMockCandidates(fieldClass, newMockSafeHashSet(mockCandidates), fieldInstanceNeedingInjection);
81             fieldClass = fieldClass.getSuperclass();
82         }
83         return injectionOccurred;
84     }
85 
initializeInjectMocksField(Field field, Object fieldOwner)86     private FieldInitializationReport initializeInjectMocksField(Field field, Object fieldOwner) {
87         FieldInitializationReport report = null;
88         try {
89             report = new FieldInitializer(fieldOwner, field).initialize();
90         } catch (MockitoException e) {
91             if(e.getCause() instanceof InvocationTargetException) {
92                 Throwable realCause = e.getCause().getCause();
93                 new Reporter().fieldInitialisationThrewException(field, realCause);
94             }
95             new Reporter().cannotInitializeForInjectMocksAnnotation(field.getName(), e);
96         }
97         return report; // never null
98     }
99 
100 
injectMockCandidates(Class<?> awaitingInjectionClazz, Set<Object> mocks, Object instance)101     private boolean injectMockCandidates(Class<?> awaitingInjectionClazz, Set<Object> mocks, Object instance) {
102         boolean injectionOccurred = false;
103         List<Field> orderedInstanceFields = orderedInstanceFieldsFrom(awaitingInjectionClazz);
104         // pass 1
105         injectionOccurred |= injectMockCandidatesOnFields(mocks, instance, injectionOccurred, orderedInstanceFields);
106         // pass 2
107         injectionOccurred |= injectMockCandidatesOnFields(mocks, instance, injectionOccurred, orderedInstanceFields);
108         return injectionOccurred;
109     }
110 
injectMockCandidatesOnFields(Set<Object> mocks, Object instance, boolean injectionOccurred, List<Field> orderedInstanceFields)111     private boolean injectMockCandidatesOnFields(Set<Object> mocks, Object instance, boolean injectionOccurred, List<Field> orderedInstanceFields) {
112         for (Iterator<Field> it = orderedInstanceFields.iterator(); it.hasNext(); ) {
113             Field field = it.next();
114             Object injected = mockCandidateFilter.filterCandidate(mocks, field, instance).thenInject();
115             if (injected != null) {
116                 injectionOccurred |= true;
117                 mocks.remove(injected);
118                 it.remove();
119             }
120         }
121         return injectionOccurred;
122     }
123 
orderedInstanceFieldsFrom(Class<?> awaitingInjectionClazz)124     private List<Field> orderedInstanceFieldsFrom(Class<?> awaitingInjectionClazz) {
125         List<Field> declaredFields = Arrays.asList(awaitingInjectionClazz.getDeclaredFields());
126         declaredFields = ListUtil.filter(declaredFields, notFinalOrStatic);
127 
128         Collections.sort(declaredFields, superTypesLast);
129 
130         return declaredFields;
131     }
132 
133     static class FieldTypeAndNameComparator implements Comparator<Field> {
compare(Field field1, Field field2)134         public int compare(Field field1, Field field2) {
135             Class<?> field1Type = field1.getType();
136             Class<?> field2Type = field2.getType();
137 
138             // if same type, compares on field name
139             if (field1Type == field2Type) {
140                 return field1.getName().compareTo(field2.getName());
141             }
142             if(field1Type.isAssignableFrom(field2Type)) {
143                 return 1;
144             }
145             if(field2Type.isAssignableFrom(field1Type)) {
146                 return -1;
147             }
148             return 0;
149         }
150     }
151 }
152