All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mockito.internal.util.reflection.FieldInitializer Maven / Gradle / Ivy

There is a newer version: 5.12.0
Show newest version
/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockito.internal.util.reflection;

import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.util.MockUtil;

import static java.lang.reflect.Modifier.isStatic;
import static org.mockito.internal.util.reflection.FieldSetter.setField;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Initialize a field with type instance if a default constructor can be found.
 *
 * 

* If the given field is already initialized, then the actual instance is returned. * This initializer doesn't work with inner classes, local classes, interfaces or abstract types. *

* */ public class FieldInitializer { private final Object fieldOwner; private final Field field; private final ConstructorInstantiator instantiator; /** * Prepare initializer with the given field on the given instance. * *

* This constructor fail fast if the field type cannot be handled. *

* * @param fieldOwner Instance of the test. * @param field Field to be initialize. */ public FieldInitializer(Object fieldOwner, Field field) { this(fieldOwner, field, new NoArgConstructorInstantiator(fieldOwner, field)); } /** * Prepare initializer with the given field on the given instance. * *

* This constructor fail fast if the field type cannot be handled. *

* * @param fieldOwner Instance of the test. * @param field Field to be initialize. * @param argResolver Constructor parameters resolver */ public FieldInitializer(Object fieldOwner, Field field, ConstructorArgumentResolver argResolver) { this(fieldOwner, field, new ParameterizedConstructorInstantiator(fieldOwner, field, argResolver)); } private FieldInitializer(Object fieldOwner, Field field, ConstructorInstantiator instantiator) { if(new FieldReader(fieldOwner, field).isNull()) { checkNotLocal(field); checkNotInner(field); checkNotInterface(field); checkNotEnum(field); checkNotAbstract(field); } this.fieldOwner = fieldOwner; this.field = field; this.instantiator = instantiator; } /** * Initialize field if not initialized and return the actual instance. * * @return Actual field instance. */ public FieldInitializationReport initialize() { final AccessibilityChanger changer = new AccessibilityChanger(); changer.enableAccess(field); try { return acquireFieldInstance(); } catch(IllegalAccessException e) { throw new MockitoException("Problems initializing field '" + field.getName() + "' of type '" + field.getType().getSimpleName() + "'", e); } finally { changer.safelyDisableAccess(field); } } private void checkNotLocal(Field field) { if(field.getType().isLocalClass()) { throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is a local class."); } } private void checkNotInner(Field field) { Class type = field.getType(); if(type.isMemberClass() && !isStatic(type.getModifiers())) { throw new MockitoException("the type '" + type.getSimpleName() + "' is an inner non static class."); } } private void checkNotInterface(Field field) { if(field.getType().isInterface()) { throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an interface."); } } private void checkNotAbstract(Field field) { if(Modifier.isAbstract(field.getType().getModifiers())) { throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an abstract class."); } } private void checkNotEnum(Field field) { if(field.getType().isEnum()) { throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an enum."); } } private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException { Object fieldInstance = field.get(fieldOwner); if(fieldInstance != null) { return new FieldInitializationReport(fieldInstance, false, false); } return instantiator.instantiate(); } /** * Represents the strategy used to resolve actual instances * to be given to a constructor given the argument types. */ public interface ConstructorArgumentResolver { /** * Try to resolve instances from types. * *

* Checks on the real argument type or on the correct argument number * will happen during the field initialization {@link FieldInitializer#initialize()}. * I.e the only responsibility of this method, is to provide instances if possible. *

* * @param argTypes Constructor argument types, should not be null. * @return The argument instances to be given to the constructor, should not be null. */ Object[] resolveTypeInstances(Class... argTypes); } private interface ConstructorInstantiator { FieldInitializationReport instantiate(); } /** * Constructor instantiating strategy for no-arg constructor. * *

* If a no-arg constructor can be found then the instance is created using * this constructor. * Otherwise a technical MockitoException is thrown. *

*/ static class NoArgConstructorInstantiator implements ConstructorInstantiator { private final Object testClass; private final Field field; /** * Internal, checks are done by FieldInitializer. * Fields are assumed to be accessible. */ NoArgConstructorInstantiator(Object testClass, Field field) { this.testClass = testClass; this.field = field; } public FieldInitializationReport instantiate() { final AccessibilityChanger changer = new AccessibilityChanger(); Constructor constructor = null; try { constructor = field.getType().getDeclaredConstructor(); changer.enableAccess(constructor); final Object[] noArg = new Object[0]; Object newFieldInstance = constructor.newInstance(noArg); setField(testClass, field,newFieldInstance); return new FieldInitializationReport(field.get(testClass), true, false); } catch (NoSuchMethodException e) { throw new MockitoException("the type '" + field.getType().getSimpleName() + "' has no default constructor", e); } catch (InvocationTargetException e) { throw new MockitoException("the default constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e); } catch (InstantiationException e) { throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e); } catch (IllegalAccessException e) { throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e); } finally { if(constructor != null) { changer.safelyDisableAccess(constructor); } } } } /** * Constructor instantiating strategy for parameterized constructors. * *

* Choose the constructor with the highest number of parameters, then * call the ConstructorArgResolver to get actual argument instances. * If the argResolver fail, then a technical MockitoException is thrown is thrown. * Otherwise the instance is created with the resolved arguments. *

*/ static class ParameterizedConstructorInstantiator implements ConstructorInstantiator { private final Object testClass; private final Field field; private final ConstructorArgumentResolver argResolver; private final Comparator> byParameterNumber = new Comparator>() { public int compare(Constructor constructorA, Constructor constructorB) { int argLengths = constructorB.getParameterTypes().length - constructorA.getParameterTypes().length; if (argLengths == 0) { int constructorAMockableParamsSize = countMockableParams(constructorA); int constructorBMockableParamsSize = countMockableParams(constructorB); return constructorBMockableParamsSize - constructorAMockableParamsSize; } return argLengths; } private int countMockableParams(Constructor constructor) { int constructorMockableParamsSize = 0; for (Class aClass : constructor.getParameterTypes()) { if(MockUtil.typeMockabilityOf(aClass).mockable()){ constructorMockableParamsSize++; } } return constructorMockableParamsSize; } }; /** * Internal, checks are done by FieldInitializer. * Fields are assumed to be accessible. */ ParameterizedConstructorInstantiator(Object testClass, Field field, ConstructorArgumentResolver argumentResolver) { this.testClass = testClass; this.field = field; this.argResolver = argumentResolver; } public FieldInitializationReport instantiate() { final AccessibilityChanger changer = new AccessibilityChanger(); Constructor constructor = null; try { constructor = biggestConstructor(field.getType()); changer.enableAccess(constructor); final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes()); Object newFieldInstance = constructor.newInstance(args); setField(testClass, field,newFieldInstance); return new FieldInitializationReport(field.get(testClass), false, true); } catch (IllegalArgumentException e) { throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e); } catch (InvocationTargetException e) { throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e); } catch (InstantiationException e) { throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e); } catch (IllegalAccessException e) { throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e); } finally { if(constructor != null) { changer.safelyDisableAccess(constructor); } } } private void checkParameterized(Constructor constructor, Field field) { if(constructor.getParameterTypes().length == 0) { throw new MockitoException("the field " + field.getName() + " of type " + field.getType() + " has no parameterized constructor"); } } private Constructor biggestConstructor(Class clazz) { final List> constructors = Arrays.asList(clazz.getDeclaredConstructors()); Collections.sort(constructors, byParameterNumber); Constructor constructor = constructors.get(0); checkParameterized(constructor, field); return constructor; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy