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.util.reflection;
6
7import org.mockito.internal.util.Checks;
8import org.mockito.internal.util.collections.ListUtil;
9import org.mockito.internal.util.collections.ListUtil.Filter;
10
11import java.lang.annotation.Annotation;
12import java.lang.reflect.Field;
13import java.util.ArrayList;
14import java.util.List;
15
16/**
17 * Small fluent reflection tools to work with fields.
18 *
19 * Code is very new and might need rework.
20 */
21public abstract class Fields {
22
23    /**
24     * Instance fields declared in the class and superclasses of the given instance.
25     *
26     * @param instance Instance from which declared fields will be retrieved.
27     * @return InstanceFields of this object instance.
28     */
29    public static InstanceFields allDeclaredFieldsOf(Object instance) {
30        List<InstanceField> instanceFields = new ArrayList<InstanceField>();
31        for (Class<?> clazz = instance.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
32            instanceFields.addAll(instanceFieldsIn(instance, clazz.getDeclaredFields()));
33        }
34        return new InstanceFields(instance, instanceFields);
35    }
36
37    /**
38     * Instance fields declared in the class of the given instance.
39     *
40     * @param instance Instance from which declared fields will be retrieved.
41     * @return InstanceFields of this object instance.
42     */
43    public static InstanceFields declaredFieldsOf(Object instance) {
44        List<InstanceField> instanceFields = new ArrayList<InstanceField>();
45        instanceFields.addAll(instanceFieldsIn(instance, instance.getClass().getDeclaredFields()));
46        return new InstanceFields(instance, instanceFields);
47    }
48
49    private static List<InstanceField> instanceFieldsIn(Object instance, Field[] fields) {
50        List<InstanceField> instanceDeclaredFields = new ArrayList<InstanceField>();
51        for (Field field : fields) {
52            InstanceField instanceField = new InstanceField(field, instance);
53            instanceDeclaredFields.add(instanceField);
54        }
55        return instanceDeclaredFields;
56    }
57
58    /**
59     * Accept fields annotated by the given annotations.
60     *
61     * @param annotations Annotation types to check.
62     * @return The filter.
63     */
64    @SuppressWarnings({"unchecked", "vararg"})
65    public static Filter<InstanceField> annotatedBy(final Class<? extends Annotation>... annotations) {
66        return new Filter<InstanceField>() {
67            public boolean isOut(InstanceField instanceField) {
68                Checks.checkNotNull(annotations, "Provide at least one annotation class");
69
70                for (Class<? extends Annotation> annotation : annotations) {
71                    if(instanceField.isAnnotatedBy(annotation)) {
72                        return false;
73                    }
74                }
75                return true;
76            }
77        };
78    }
79
80    /**
81     * Accept fields with non null value.
82     *
83     * @return The filter.
84     */
85    private static Filter<InstanceField> nullField() {
86        return new Filter<InstanceField>() {
87            public boolean isOut(InstanceField instanceField) {
88                return instanceField.isNull();
89            }
90        };
91    }
92
93    /**
94     * Accept fields with non null value.
95     *
96     * @return The filter.
97     */
98    public static Filter<InstanceField> syntheticField() {
99        return new Filter<InstanceField>() {
100            public boolean isOut(InstanceField instanceField) {
101                return instanceField.isSynthetic();
102            }
103        };
104    }
105
106    public static class InstanceFields {
107        private final Object instance;
108
109        private final List<InstanceField> instanceFields;
110
111        public InstanceFields(Object instance, List<InstanceField> instanceFields) {
112            this.instance = instance;
113            this.instanceFields = instanceFields;
114        }
115
116        public InstanceFields filter(Filter<InstanceField> withFilter) {
117            return new InstanceFields(instance, ListUtil.filter(instanceFields, withFilter));
118        }
119
120        public InstanceFields notNull() {
121            return filter(nullField());
122        }
123
124        public List<InstanceField> instanceFields() {
125            return new ArrayList<InstanceField>(instanceFields);
126        }
127
128        public List<Object> assignedValues() {
129            List<Object> values = new ArrayList<Object>(instanceFields.size());
130            for (InstanceField instanceField : instanceFields) {
131                values.add(instanceField.read());
132            }
133            return values;
134        }
135
136        public List<String> names() {
137            List<String> fieldNames = new ArrayList<String>(instanceFields.size());
138            for (InstanceField instanceField : instanceFields) {
139                fieldNames.add(instanceField.name());
140            }
141            return fieldNames;
142        }
143    }
144}
145