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