1
2
3
4
5 package mockit.internal.reflection;
6
7 import static java.lang.reflect.Modifier.isStatic;
8
9 import static mockit.internal.util.Utilities.ensureThatMemberIsAccessible;
10
11 import edu.umd.cs.findbugs.annotations.NonNull;
12 import edu.umd.cs.findbugs.annotations.Nullable;
13
14 import java.lang.reflect.Field;
15 import java.lang.reflect.Type;
16
17 import mockit.internal.util.AutoBoxing;
18 import mockit.internal.util.Utilities;
19
20 public final class FieldReflection {
21 private FieldReflection() {
22 }
23
24 @Nullable
25 public static <T> T getFieldValue(@NonNull Field field, @Nullable Object targetObject) {
26 ensureThatMemberIsAccessible(field);
27
28 if (targetObject != null && !field.getDeclaringClass().isInstance(targetObject)) {
29 Field outerInstanceField = getDeclaredField(targetObject.getClass(), "this$0", true);
30 targetObject = getFieldValue(outerInstanceField, targetObject);
31 }
32
33 try {
34
35 return (T) field.get(targetObject);
36 } catch (IllegalAccessException e) {
37 throw new RuntimeException(e);
38 }
39 }
40
41 @Nullable
42 public static <T> T getField(@NonNull Class<?> theClass, @NonNull String fieldName, @Nullable Object targetObject) {
43 Field field = getDeclaredField(theClass, fieldName, targetObject != null);
44 return getFieldValue(field, targetObject);
45 }
46
47 @Nullable
48 public static <T> T getField(@NonNull Class<?> theClass, @NonNull Class<T> fieldType,
49 @Nullable Object targetObject) {
50 Field field = getDeclaredField(theClass, fieldType, targetObject != null, false);
51 return getFieldValue(field, targetObject);
52 }
53
54 public static void setField(@NonNull Class<?> theClass, @Nullable Object targetObject, @Nullable String fieldName,
55 @Nullable Object fieldValue) {
56 boolean instanceField = targetObject != null;
57 Field field;
58
59 if (fieldName != null) {
60 field = getDeclaredField(theClass, fieldName, instanceField);
61 } else if (fieldValue != null) {
62 field = getDeclaredField(theClass, fieldValue.getClass(), instanceField, true);
63 } else {
64 throw new IllegalArgumentException("Missing field value when setting field by type");
65 }
66
67 setFieldValue(field, targetObject, fieldValue);
68 }
69
70 @NonNull
71 public static Field getDeclaredField(@NonNull Class<?> theClass, @NonNull String fieldName, boolean instanceField) {
72 try {
73 return theClass.getDeclaredField(fieldName);
74 } catch (NoSuchFieldException ignore) {
75 Class<?> superClass = theClass.getSuperclass();
76
77 if (superClass != null && superClass != Object.class) {
78
79 return getDeclaredField(superClass, fieldName, instanceField);
80 }
81
82 String kind = instanceField ? "instance" : "static";
83 throw new IllegalArgumentException(
84 "No " + kind + " field of name \"" + fieldName + "\" found in " + theClass);
85 }
86 }
87
88 @NonNull
89 private static Field getDeclaredField(@NonNull Class<?> theClass, @NonNull Type desiredType, boolean instanceField,
90 boolean forAssignment) {
91 Field found = getDeclaredFieldInSingleClass(theClass, desiredType, instanceField, forAssignment);
92
93 if (found == null) {
94 Class<?> superClass = theClass.getSuperclass();
95
96 if (superClass != null && superClass != Object.class) {
97
98 return getDeclaredField(superClass, desiredType, instanceField, forAssignment);
99 }
100
101 StringBuilder errorMsg = new StringBuilder(instanceField ? "Instance" : "Static");
102 String typeName = getTypeName(desiredType);
103 errorMsg.append(" field of type ").append(typeName).append(" not found in ").append(theClass);
104 throw new IllegalArgumentException(errorMsg.toString());
105 }
106
107 return found;
108 }
109
110 @Nullable
111 private static Field getDeclaredFieldInSingleClass(@NonNull Class<?> theClass, @NonNull Type desiredType,
112 boolean instanceField, boolean forAssignment) {
113 Field found = null;
114
115 for (Field field : theClass.getDeclaredFields()) {
116 if (!field.isSynthetic()) {
117 Type fieldType = field.getGenericType();
118
119 if (instanceField != isStatic(field.getModifiers())
120 && isCompatibleFieldType(fieldType, desiredType, forAssignment)) {
121 if (found != null) {
122 String message = errorMessageForMoreThanOneFieldFound(desiredType, instanceField, forAssignment,
123 found, field);
124 throw new IllegalArgumentException(message);
125 }
126
127 found = field;
128 }
129 }
130 }
131
132 return found;
133 }
134
135 private static boolean isCompatibleFieldType(@NonNull Type fieldType, @NonNull Type desiredType,
136 boolean forAssignment) {
137 Class<?> fieldClass = Utilities.getClassType(fieldType);
138 Class<?> desiredClass = Utilities.getClassType(desiredType);
139
140 if (ParameterReflection.isSameTypeIgnoringAutoBoxing(desiredClass, fieldClass)) {
141 return true;
142 }
143
144 if (forAssignment) {
145 return fieldClass.isAssignableFrom(desiredClass);
146 }
147
148 return desiredClass.isAssignableFrom(fieldClass) || fieldClass.isAssignableFrom(desiredClass);
149 }
150
151 private static String errorMessageForMoreThanOneFieldFound(@NonNull Type desiredFieldType, boolean instanceField,
152 boolean forAssignment, @NonNull Field firstField, @NonNull Field secondField) {
153 return "More than one " + (instanceField ? "instance" : "static") + " field " + (forAssignment ? "to" : "from")
154 + " which a value of type " + getTypeName(desiredFieldType)
155 + (forAssignment ? " can be assigned" : " can be read") + " exists in "
156 + secondField.getDeclaringClass() + ": " + firstField.getName() + ", " + secondField.getName();
157 }
158
159 @NonNull
160 private static String getTypeName(@NonNull Type type) {
161 Class<?> classType = Utilities.getClassType(type);
162 Class<?> primitiveType = AutoBoxing.getPrimitiveType(classType);
163
164 if (primitiveType != null) {
165 return primitiveType + " or " + classType.getSimpleName();
166 }
167
168 String name = classType.getName();
169 return name.startsWith("java.lang.") ? name.substring(10) : name;
170 }
171
172 public static void setFieldValue(@NonNull Field field, @Nullable Object targetObject, @Nullable Object value) {
173 ensureThatMemberIsAccessible(field);
174
175 if (targetObject != null && !field.getDeclaringClass().isInstance(targetObject)) {
176 Field outerInstanceField = getDeclaredField(targetObject.getClass(), "this$0", true);
177 targetObject = getFieldValue(outerInstanceField, targetObject);
178 }
179
180 try {
181 field.set(targetObject, value);
182 } catch (IllegalAccessException e) {
183 throw new RuntimeException(e);
184 }
185 }
186 }