/* * Copyright (c) 2007 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.configuration.injection; import static org.mockito.internal.exceptions.Reporter.cannotInitializeForInjectMocksAnnotation; import static org.mockito.internal.exceptions.Reporter.fieldInitialisationThrewException; import static org.mockito.internal.util.collections.Sets.newMockSafeHashSet; import static org.mockito.internal.util.reflection.SuperTypesLastSorter.sortSuperTypesLast; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.configuration.injection.filter.MockCandidateFilter; import org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter; import org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter; import org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter; import org.mockito.internal.util.collections.ListUtil; import org.mockito.internal.util.reflection.FieldInitializationReport; import org.mockito.internal.util.reflection.FieldInitializer; import org.mockito.internal.util.reflection.SuperTypesLastSorter; /** * Inject mocks using first setters then fields, if no setters available. * * <p> * <u>Algorithm :<br></u> * for each field annotated by @InjectMocks * <ul> * <li>initialize field annotated by @InjectMocks * <li>for each fields of a class in @InjectMocks type hierarchy * <ul> * <li>make a copy of mock candidates * <li>order fields from sub-type to super-type, then by field name * <li>for the list of fields in a class try two passes of : * <ul> * <li>find mock candidate by type * <li>if more than <b>*one*</b> candidate find mock candidate on name * <li>if one mock candidate then * <ul> * <li>set mock by property setter if possible * <li>else set mock by field injection * </ul> * <li>remove mock from mocks copy (mocks are just injected once in a class) * <li>remove injected field from list of class fields * </ul> * <li>else don't fail, user will then provide dependencies * </ul> * </ul> * </p> * * <p> * <u>Note:</u> If the field needing injection is not initialized, the strategy tries * to create one using a no-arg constructor of the field type. * </p> */ public class PropertyAndSetterInjection extends MockInjectionStrategy { private final MockCandidateFilter mockCandidateFilter = new TypeBasedCandidateFilter( new NameBasedCandidateFilter( new TerminalMockCandidateFilter())); private final ListUtil.Filter<Field> notFinalOrStatic = new ListUtil.Filter<Field>() { public boolean isOut(Field object) { return Modifier.isFinal(object.getModifiers()) || Modifier.isStatic(object.getModifiers()); } }; public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates) { FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner); // for each field in the class hierarchy boolean injectionOccurred = false; Class<?> fieldClass = report.fieldClass(); Object fieldInstanceNeedingInjection = report.fieldInstance(); while (fieldClass != Object.class) { injectionOccurred |= injectMockCandidates(fieldClass, fieldInstanceNeedingInjection, newMockSafeHashSet(mockCandidates)); fieldClass = fieldClass.getSuperclass(); } return injectionOccurred; } private FieldInitializationReport initializeInjectMocksField(Field field, Object fieldOwner) { try { return new FieldInitializer(fieldOwner, field).initialize(); } catch (MockitoException e) { if(e.getCause() instanceof InvocationTargetException) { Throwable realCause = e.getCause().getCause(); throw fieldInitialisationThrewException(field, realCause); } throw cannotInitializeForInjectMocksAnnotation(field.getName(),e.getMessage()); } } private boolean injectMockCandidates(Class<?> awaitingInjectionClazz, Object injectee, Set<Object> mocks) { boolean injectionOccurred; List<Field> orderedCandidateInjecteeFields = orderedInstanceFieldsFrom(awaitingInjectionClazz); // pass 1 injectionOccurred = injectMockCandidatesOnFields(mocks, injectee, false, orderedCandidateInjecteeFields); // pass 2 injectionOccurred |= injectMockCandidatesOnFields(mocks, injectee, injectionOccurred, orderedCandidateInjecteeFields); return injectionOccurred; } private boolean injectMockCandidatesOnFields(Set<Object> mocks, Object injectee, boolean injectionOccurred, List<Field> orderedCandidateInjecteeFields) { for (Iterator<Field> it = orderedCandidateInjecteeFields.iterator(); it.hasNext(); ) { Field candidateField = it.next(); Object injected = mockCandidateFilter.filterCandidate(mocks, candidateField, orderedCandidateInjecteeFields, injectee) .thenInject(); if (injected != null) { injectionOccurred |= true; mocks.remove(injected); it.remove(); } } return injectionOccurred; } private List<Field> orderedInstanceFieldsFrom(Class<?> awaitingInjectionClazz) { List<Field> declaredFields = Arrays.asList(awaitingInjectionClazz.getDeclaredFields()); declaredFields = ListUtil.filter(declaredFields, notFinalOrStatic); return sortSuperTypesLast(declaredFields); } }