Home | History | Annotate | Download | only in reflection
      1 /*
      2  * Copyright (c) 2007 Mockito contributors
      3  * This program is made available under the terms of the MIT License.
      4  */
      5 package org.mockito.internal.util.reflection;
      6 
      7 import org.mockito.exceptions.base.MockitoException;
      8 import org.mockito.internal.util.MockUtil;
      9 
     10 import static java.lang.reflect.Modifier.isStatic;
     11 import static org.mockito.internal.util.reflection.FieldSetter.setField;
     12 
     13 import java.lang.reflect.Constructor;
     14 import java.lang.reflect.Field;
     15 import java.lang.reflect.InvocationTargetException;
     16 import java.lang.reflect.Modifier;
     17 import java.util.Arrays;
     18 import java.util.Collections;
     19 import java.util.Comparator;
     20 import java.util.List;
     21 
     22 /**
     23  * Initialize a field with type instance if a default constructor can be found.
     24  *
     25  * <p>
     26  * If the given field is already initialized, then <strong>the actual instance is returned</strong>.
     27  * This initializer doesn't work with inner classes, local classes, interfaces or abstract types.
     28  * </p>
     29  *
     30  */
     31 public class FieldInitializer {
     32 
     33     private final Object fieldOwner;
     34     private final Field field;
     35     private final ConstructorInstantiator instantiator;
     36 
     37 
     38     /**
     39      * Prepare initializer with the given field on the given instance.
     40      *
     41      * <p>
     42      * This constructor fail fast if the field type cannot be handled.
     43      * </p>
     44      *
     45      * @param fieldOwner Instance of the test.
     46      * @param field Field to be initialize.
     47      */
     48     public FieldInitializer(Object fieldOwner, Field field) {
     49         this(fieldOwner, field, new NoArgConstructorInstantiator(fieldOwner, field));
     50     }
     51 
     52     /**
     53      * Prepare initializer with the given field on the given instance.
     54      *
     55      * <p>
     56      * This constructor fail fast if the field type cannot be handled.
     57      * </p>
     58      *
     59      * @param fieldOwner Instance of the test.
     60      * @param field Field to be initialize.
     61      * @param argResolver Constructor parameters resolver
     62      */
     63     public FieldInitializer(Object fieldOwner, Field field, ConstructorArgumentResolver argResolver) {
     64         this(fieldOwner, field, new ParameterizedConstructorInstantiator(fieldOwner, field, argResolver));
     65     }
     66 
     67     private FieldInitializer(Object fieldOwner, Field field, ConstructorInstantiator instantiator) {
     68         if(new FieldReader(fieldOwner, field).isNull()) {
     69             checkNotLocal(field);
     70             checkNotInner(field);
     71             checkNotInterface(field);
     72             checkNotEnum(field);
     73             checkNotAbstract(field);
     74 
     75         }
     76         this.fieldOwner = fieldOwner;
     77         this.field = field;
     78         this.instantiator = instantiator;
     79     }
     80 
     81     /**
     82      * Initialize field if not initialized and return the actual instance.
     83      *
     84      * @return Actual field instance.
     85      */
     86     public FieldInitializationReport initialize() {
     87         final AccessibilityChanger changer = new AccessibilityChanger();
     88         changer.enableAccess(field);
     89 
     90         try {
     91             return acquireFieldInstance();
     92         } catch(IllegalAccessException e) {
     93             throw new MockitoException("Problems initializing field '" + field.getName() + "' of type '" + field.getType().getSimpleName() + "'", e);
     94         } finally {
     95             changer.safelyDisableAccess(field);
     96         }
     97     }
     98 
     99     private void checkNotLocal(Field field) {
    100         if(field.getType().isLocalClass()) {
    101             throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is a local class.");
    102         }
    103     }
    104 
    105     private void checkNotInner(Field field) {
    106         Class<?> type = field.getType();
    107         if(type.isMemberClass() && !isStatic(type.getModifiers())) {
    108             throw new MockitoException("the type '" + type.getSimpleName() + "' is an inner non static class.");
    109         }
    110     }
    111 
    112     private void checkNotInterface(Field field) {
    113         if(field.getType().isInterface()) {
    114             throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an interface.");
    115         }
    116     }
    117 
    118     private void checkNotAbstract(Field field) {
    119         if(Modifier.isAbstract(field.getType().getModifiers())) {
    120             throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an abstract class.");
    121         }
    122     }
    123 
    124     private void checkNotEnum(Field field) {
    125         if(field.getType().isEnum()) {
    126             throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an enum.");
    127         }
    128     }
    129 
    130 
    131     private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException {
    132         Object fieldInstance = field.get(fieldOwner);
    133         if(fieldInstance != null) {
    134             return new FieldInitializationReport(fieldInstance, false, false);
    135         }
    136 
    137         return instantiator.instantiate();
    138     }
    139 
    140     /**
    141      * Represents the strategy used to resolve actual instances
    142      * to be given to a constructor given the argument types.
    143      */
    144     public interface ConstructorArgumentResolver {
    145 
    146         /**
    147          * Try to resolve instances from types.
    148          *
    149          * <p>
    150          * Checks on the real argument type or on the correct argument number
    151          * will happen during the field initialization {@link FieldInitializer#initialize()}.
    152          * I.e the only responsibility of this method, is to provide instances <strong>if possible</strong>.
    153          * </p>
    154          *
    155          * @param argTypes Constructor argument types, should not be null.
    156          * @return The argument instances to be given to the constructor, should not be null.
    157          */
    158         Object[] resolveTypeInstances(Class<?>... argTypes);
    159     }
    160 
    161     private interface ConstructorInstantiator {
    162         FieldInitializationReport instantiate();
    163     }
    164 
    165     /**
    166      * Constructor instantiating strategy for no-arg constructor.
    167      *
    168      * <p>
    169      * If a no-arg constructor can be found then the instance is created using
    170      * this constructor.
    171      * Otherwise a technical MockitoException is thrown.
    172      * </p>
    173      */
    174     static class NoArgConstructorInstantiator implements ConstructorInstantiator {
    175         private final Object testClass;
    176         private final Field field;
    177 
    178         /**
    179          * Internal, checks are done by FieldInitializer.
    180          * Fields are assumed to be accessible.
    181          */
    182         NoArgConstructorInstantiator(Object testClass, Field field) {
    183             this.testClass = testClass;
    184             this.field = field;
    185         }
    186 
    187         public FieldInitializationReport instantiate() {
    188             final AccessibilityChanger changer = new AccessibilityChanger();
    189             Constructor<?> constructor = null;
    190             try {
    191                 constructor = field.getType().getDeclaredConstructor();
    192                 changer.enableAccess(constructor);
    193 
    194                 final Object[] noArg = new Object[0];
    195                 Object newFieldInstance = constructor.newInstance(noArg);
    196                 setField(testClass, field,newFieldInstance);
    197 
    198                 return new FieldInitializationReport(field.get(testClass), true, false);
    199             } catch (NoSuchMethodException e) {
    200                 throw new MockitoException("the type '" + field.getType().getSimpleName() + "' has no default constructor", e);
    201             } catch (InvocationTargetException e) {
    202                 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);
    203             } catch (InstantiationException e) {
    204                 throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
    205             } catch (IllegalAccessException e) {
    206                 throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
    207             } finally {
    208                 if(constructor != null) {
    209                     changer.safelyDisableAccess(constructor);
    210                 }
    211             }
    212         }
    213     }
    214 
    215     /**
    216      * Constructor instantiating strategy for parameterized constructors.
    217      *
    218      * <p>
    219      * Choose the constructor with the highest number of parameters, then
    220      * call the ConstructorArgResolver to get actual argument instances.
    221      * If the argResolver fail, then a technical MockitoException is thrown is thrown.
    222      * Otherwise the instance is created with the resolved arguments.
    223      * </p>
    224      */
    225     static class ParameterizedConstructorInstantiator implements ConstructorInstantiator {
    226         private final Object testClass;
    227         private final Field field;
    228         private final ConstructorArgumentResolver argResolver;
    229         private final Comparator<Constructor<?>> byParameterNumber = new Comparator<Constructor<?>>() {
    230             public int compare(Constructor<?> constructorA, Constructor<?> constructorB) {
    231                 int argLengths = constructorB.getParameterTypes().length - constructorA.getParameterTypes().length;
    232                 if (argLengths == 0) {
    233                     int constructorAMockableParamsSize = countMockableParams(constructorA);
    234                     int constructorBMockableParamsSize = countMockableParams(constructorB);
    235                     return constructorBMockableParamsSize - constructorAMockableParamsSize;
    236                 }
    237                 return argLengths;
    238             }
    239 
    240             private int countMockableParams(Constructor<?> constructor) {
    241                 int constructorMockableParamsSize = 0;
    242                 for (Class<?> aClass : constructor.getParameterTypes()) {
    243                     if(MockUtil.typeMockabilityOf(aClass).mockable()){
    244                         constructorMockableParamsSize++;
    245                     }
    246                 }
    247                 return constructorMockableParamsSize;
    248             }
    249         };
    250 
    251         /**
    252          * Internal, checks are done by FieldInitializer.
    253          * Fields are assumed to be accessible.
    254          */
    255         ParameterizedConstructorInstantiator(Object testClass, Field field, ConstructorArgumentResolver argumentResolver) {
    256             this.testClass = testClass;
    257             this.field = field;
    258             this.argResolver = argumentResolver;
    259         }
    260 
    261         public FieldInitializationReport instantiate() {
    262             final AccessibilityChanger changer = new AccessibilityChanger();
    263             Constructor<?> constructor = null;
    264             try {
    265                 constructor = biggestConstructor(field.getType());
    266                 changer.enableAccess(constructor);
    267 
    268                 final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes());
    269                 Object newFieldInstance = constructor.newInstance(args);
    270                 setField(testClass, field,newFieldInstance);
    271 
    272                 return new FieldInitializationReport(field.get(testClass), false, true);
    273             } catch (IllegalArgumentException e) {
    274                 throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e);
    275             } catch (InvocationTargetException e) {
    276                 throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e);
    277             } catch (InstantiationException e) {
    278                 throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
    279             } catch (IllegalAccessException e) {
    280                 throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
    281             } finally {
    282                 if(constructor != null) {
    283                     changer.safelyDisableAccess(constructor);
    284                 }
    285             }
    286         }
    287 
    288         private void checkParameterized(Constructor<?> constructor, Field field) {
    289             if(constructor.getParameterTypes().length == 0) {
    290                 throw new MockitoException("the field " + field.getName() + " of type " + field.getType() + " has no parameterized constructor");
    291             }
    292         }
    293 
    294         private Constructor<?> biggestConstructor(Class<?> clazz) {
    295             final List<? extends Constructor<?>> constructors = Arrays.asList(clazz.getDeclaredConstructors());
    296             Collections.sort(constructors, byParameterNumber);
    297 
    298             Constructor<?> constructor = constructors.get(0);
    299             checkParameterized(constructor, field);
    300             return constructor;
    301         }
    302     }
    303 }
    304