View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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              // noinspection unchecked
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                  // noinspection TailRecursion
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                  // noinspection TailRecursion
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 }