Home | History | Annotate | Download | only in injection
      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 
     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