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