View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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              // noinspection unchecked
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                  // noinspection TailRecursion
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                  // noinspection TailRecursion
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 }