1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.configuration;
6
7import org.mockito.*;
8import org.mockito.configuration.AnnotationEngine;
9import org.mockito.exceptions.Reporter;
10import org.mockito.exceptions.base.MockitoException;
11import org.mockito.internal.util.MockUtil;
12import org.mockito.internal.util.reflection.FieldInitializationReport;
13import org.mockito.internal.util.reflection.FieldInitializer;
14
15import java.lang.annotation.Annotation;
16import java.lang.reflect.Field;
17
18import static org.mockito.Mockito.withSettings;
19
20/**
21 * Process fields annotated with @Spy.
22 *
23 * <p>
24 * Will try transform the field in a spy as with <code>Mockito.spy()</code>.
25 * </p>
26 *
27 * <p>
28 * If the field is not initialized, will try to initialize it, with a no-arg constructor.
29 * </p>
30 *
31 * <p>
32 * If the field is also annotated with the <strong>compatible</strong> &#64;InjectMocks then the field will be ignored,
33 * The injection engine will handle this specific case.
34 * </p>
35 *
36 * <p>This engine will fail, if the field is also annotated with incompatible Mockito annotations.
37 */
38@SuppressWarnings({"unchecked"})
39public class SpyAnnotationEngine implements AnnotationEngine {
40
41    public Object createMockFor(Annotation annotation, Field field) {
42        return null;
43    }
44
45    @SuppressWarnings("deprecation") // for MockitoAnnotations.Mock
46    public void process(Class<?> context, Object testInstance) {
47        Field[] fields = context.getDeclaredFields();
48        for (Field field : fields) {
49            if (field.isAnnotationPresent(Spy.class) && !field.isAnnotationPresent(InjectMocks.class)) {
50                assertNoIncompatibleAnnotations(Spy.class, field, Mock.class, org.mockito.MockitoAnnotations.Mock.class, Captor.class);
51                Object instance = null;
52                try {
53                    FieldInitializationReport report = new FieldInitializer(testInstance, field).initialize();
54                    instance = report.fieldInstance();
55                } catch (MockitoException e) {
56                    new Reporter().cannotInitializeForSpyAnnotation(field.getName(), e);
57                }
58                try {
59                    if (new MockUtil().isMock(instance)) {
60                        // instance has been spied earlier
61                        // for example happens when MockitoAnnotations.initMocks is called two times.
62                        Mockito.reset(instance);
63                    } else {
64                        field.setAccessible(true);
65                        field.set(testInstance, Mockito.mock(instance.getClass(), withSettings()
66                                .spiedInstance(instance)
67                                .defaultAnswer(Mockito.CALLS_REAL_METHODS)
68                                .name(field.getName())));
69                    }
70                } catch (IllegalAccessException e) {
71                    throw new MockitoException("Problems initiating spied field " + field.getName(), e);
72                }
73            }
74        }
75    }
76
77    //TODO duplicated elsewhere
78    void assertNoIncompatibleAnnotations(Class annotation, Field field, Class... undesiredAnnotations) {
79        for (Class u : undesiredAnnotations) {
80            if (field.isAnnotationPresent(u)) {
81                new Reporter().unsupportedCombinationOfAnnotations(annotation.getSimpleName(), annotation.getClass().getSimpleName());
82            }
83        }
84    }
85}
86