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