1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5
6package org.mockito.internal.configuration.injection;
7
8import org.mockito.exceptions.Reporter;
9import org.mockito.exceptions.base.MockitoException;
10import org.mockito.internal.configuration.injection.filter.FinalMockCandidateFilter;
11import org.mockito.internal.configuration.injection.filter.MockCandidateFilter;
12import org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter;
13import org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter;
14import org.mockito.internal.util.collections.ListUtil;
15import org.mockito.internal.util.reflection.FieldInitializationReport;
16import org.mockito.internal.util.reflection.FieldInitializer;
17
18import java.lang.reflect.Field;
19import java.lang.reflect.InvocationTargetException;
20import java.lang.reflect.Modifier;
21import java.util.*;
22
23import 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 */
59public 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
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
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
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
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
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> {
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