View Javadoc
1   package mockit.asm.types;
2   
3   import edu.umd.cs.findbugs.annotations.NonNull;
4   
5   import java.lang.reflect.Constructor;
6   import java.lang.reflect.Method;
7   
8   import org.checkerframework.checker.index.qual.NonNegative;
9   
10  /**
11   * A Java field or method type. This class can be used to make it easier to manipulate type and method descriptors.
12   */
13  @SuppressWarnings("ClassReferencesSubclass")
14  public abstract class JavaType {
15      private static final JavaType[] NO_ARGS = {};
16  
17      /**
18       * The length of the internal name of this Java type.
19       */
20      @NonNegative
21      final int len;
22  
23      /**
24       * Constructs a Java type.
25       *
26       * @param len
27       *            the length of this descriptor.
28       */
29      JavaType(@NonNegative int len) {
30          this.len = len;
31      }
32  
33      /**
34       * Returns the Java type corresponding to the given type descriptor.
35       *
36       * @param typeDescriptor
37       *            a field or method type descriptor.
38       */
39      @NonNull
40      public static JavaType getType(@NonNull String typeDescriptor) {
41          return getType(typeDescriptor.toCharArray(), 0);
42      }
43  
44      /**
45       * Returns the Java types corresponding to the argument types of the given method descriptor.
46       */
47      @NonNull
48      public static JavaType[] getArgumentTypes(@NonNull String methodDescriptor) {
49          char[] buf = methodDescriptor.toCharArray();
50          int off = 1;
51          int size = 0;
52  
53          while (true) {
54              char c = buf[off];
55              off++;
56  
57              if (c == ')') {
58                  break;
59              }
60              if (c == 'L') {
61                  off = findNextTypeTerminatorCharacter(buf, off);
62                  size++;
63              } else if (c != '[') {
64                  size++;
65              }
66          }
67  
68          return getArgumentTypes(buf, size);
69      }
70  
71      @NonNegative
72      private static int findNextTypeTerminatorCharacter(@NonNull char[] desc, @NonNegative int i) {
73          while (desc[i++] != ';') {
74          }
75          return i;
76      }
77  
78      @NonNull
79      private static JavaType[] getArgumentTypes(@NonNull char[] buf, @NonNegative int argCount) {
80          if (argCount == 0) {
81              return NO_ARGS;
82          }
83  
84          JavaType[] argTypes = new JavaType[argCount];
85          int off = 1;
86  
87          for (int i = 0; buf[off] != ')'; i++) {
88              JavaType argType = getType(buf, off);
89              argTypes[i] = argType;
90              off += argType.len + (argType instanceof ObjectType ? 2 : 0);
91          }
92  
93          return argTypes;
94      }
95  
96      /**
97       * Returns the Java type corresponding to the return type of the given method descriptor.
98       */
99      @NonNull
100     public static JavaType getReturnType(@NonNull String methodDescriptor) {
101         char[] buf = methodDescriptor.toCharArray();
102         return getType(buf, methodDescriptor.indexOf(')') + 1);
103     }
104 
105     /**
106      * Computes the size of the arguments and of the return value of a method.
107      *
108      * @param desc
109      *            the descriptor of a method.
110      *
111      * @return the size of the arguments of the method (plus one for the implicit <code>this</code> argument),
112      *         <code>argSize</code>, and the size of its return value, <code>retSize</code>, packed into a single
113      *
114      *         <pre>{@code int i = (argSize << 2) | retSize }</pre>
115      *
116      *         (<code>argSize</code> is therefore equal to
117      *
118      *         <pre>{@code i >> 2 }</pre>
119      *
120      *         , and
121      *
122      *         <pre>{@code retSize }</pre>
123      *
124      *         to
125      *
126      *         <pre>
127      * &#64;{code i &amp; 0x03 }
128      *         </pre>
129      *
130      *         ).
131      */
132     public static int getArgumentsAndReturnSizes(@NonNull String desc) {
133         int argSize = 1;
134         int i = 1;
135 
136         while (true) {
137             char currentChar = desc.charAt(i);
138             i++;
139 
140             switch (currentChar) {
141                 case ')': {
142                     char nextChar = desc.charAt(i);
143                     return argSize << 2 | (nextChar == 'V' ? 0 : isDoubleSizePrimitiveType(nextChar) ? 2 : 1);
144                 }
145                 case 'L':
146                     i = findNextTypeTerminatorCharacter(desc, i);
147                     argSize++;
148                     break;
149                 case '[': {
150                     i = findStartOfArrayElementType(desc, i);
151                     char arrayElementType = desc.charAt(i);
152                     if (isDoubleSizePrimitiveType(arrayElementType)) {
153                         argSize--;
154                     }
155                     break;
156                 }
157                 default:
158                     if (isDoubleSizePrimitiveType(currentChar)) {
159                         argSize += 2;
160                     } else {
161                         argSize++;
162                     }
163                     break;
164             }
165         }
166     }
167 
168     private static boolean isDoubleSizePrimitiveType(char typeCode) {
169         return typeCode == 'D' || typeCode == 'J';
170     }
171 
172     @NonNegative
173     private static int findNextTypeTerminatorCharacter(@NonNull String desc, @NonNegative int i) {
174         while (desc.charAt(i++) != ';') {
175         }
176         return i;
177     }
178 
179     @NonNegative
180     private static int findStartOfArrayElementType(@NonNull String desc, @NonNegative int i) {
181         while (desc.charAt(i) == '[') {
182             i++;
183         }
184         return i;
185     }
186 
187     /**
188      * Returns the Java type corresponding to the given type descriptor. For method descriptors, <code>buf</code> is
189      * supposed to contain nothing more than the descriptor itself.
190      *
191      * @param buf
192      *            a buffer containing a type descriptor.
193      * @param off
194      *            the offset of this descriptor in the previous buffer.
195      */
196     @NonNull
197     static JavaType getType(@NonNull char[] buf, @NonNegative int off) {
198         PrimitiveType primitiveType = PrimitiveType.getPrimitiveType(buf[off]);
199 
200         if (primitiveType != null) {
201             return primitiveType;
202         }
203 
204         return ReferenceType.getReferenceType(buf, off);
205     }
206 
207     /**
208      * Returns the binary name of the class corresponding to this type. This method must not be used on method types.
209      */
210     @NonNull
211     public abstract String getClassName();
212 
213     // ------------------------------------------------------------------------
214     // Conversion to type descriptors
215     // ------------------------------------------------------------------------
216 
217     /**
218      * Returns the descriptor corresponding to this Java type.
219      */
220     @NonNull
221     public final String getDescriptor() {
222         StringBuilder buf = new StringBuilder();
223         getDescriptor(buf);
224         return buf.toString();
225     }
226 
227     /**
228      * Appends the descriptor corresponding to this Java type to the given string buffer.
229      *
230      * @param typeDesc
231      *            the string builder to which the descriptor must be appended
232      */
233     abstract void getDescriptor(@NonNull StringBuilder typeDesc);
234 
235     // -------------------------------------------------------------------------------------------------------
236     // Direct conversion from classes to type descriptors, and vice-versa, without intermediate JavaType objects
237     // -------------------------------------------------------------------------------------------------------
238 
239     /**
240      * Returns the internal name of the given class. The internal name of a class is its fully qualified name, as
241      * returned by Class.getName(), where '.' are replaced by '/'.
242      *
243      * @param aClass
244      *            an object or array class
245      */
246     @NonNull
247     public static String getInternalName(@NonNull Class<?> aClass) {
248         return aClass.getName().replace('.', '/');
249     }
250 
251     /**
252      * Returns the descriptor corresponding to the given constructor.
253      */
254     @NonNull
255     public static String getConstructorDescriptor(@NonNull Constructor<?> constructor) {
256         StringBuilder buf = getMemberDescriptor(constructor.getParameterTypes());
257         buf.append('V');
258         return buf.toString();
259     }
260 
261     @NonNull
262     private static StringBuilder getMemberDescriptor(@NonNull Class<?>[] parameterTypes) {
263         StringBuilder buf = new StringBuilder();
264         buf.append('(');
265 
266         for (Class<?> parameterType : parameterTypes) {
267             getDescriptor(buf, parameterType);
268         }
269 
270         buf.append(')');
271         return buf;
272     }
273 
274     /**
275      * Returns the descriptor corresponding to the given method.
276      */
277     @NonNull
278     public static String getMethodDescriptor(@NonNull Method method) {
279         StringBuilder buf = getMemberDescriptor(method.getParameterTypes());
280         getDescriptor(buf, method.getReturnType());
281         return buf.toString();
282     }
283 
284     /**
285      * Appends the descriptor of the given class to the given string builder.
286      */
287     private static void getDescriptor(@NonNull StringBuilder buf, @NonNull Class<?> aClass) {
288         Class<?> d = aClass;
289 
290         while (true) {
291             if (d.isPrimitive()) {
292                 char typeCode = PrimitiveType.getPrimitiveType(d).getTypeCode();
293                 buf.append(typeCode);
294                 return;
295             }
296             if (!d.isArray()) {
297                 ReferenceType.getDescriptor(buf, d);
298                 return;
299             }
300             buf.append('[');
301             d = d.getComponentType();
302         }
303     }
304 
305     // ------------------------------------------------------------------------
306     // Corresponding size and opcodes
307     // ------------------------------------------------------------------------
308 
309     /**
310      * Returns the size of values of this type. This method must not be used for method types.
311      *
312      * @return the size of values of this type, i.e., 2 for <code>long</code> and <code>double</code>, 0 for
313      *         <code>void</code> and 1 otherwise.
314      */
315     @NonNegative
316     public abstract int getSize();
317 
318     /**
319      * Returns a JVM instruction opcode adapted to this Java type. This method must not be used for method types.
320      *
321      * @param opcode
322      *            a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL,
323      *            IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
324      *
325      * @return an opcode that is similar to the given opcode, but adapted to this Java type. For example, if this type
326      *         is <code>float</code> and <code>opcode</code> is IRETURN, this method returns FRETURN.
327      */
328     public abstract int getOpcode(int opcode);
329 
330     public abstract int getLoadOpcode();
331 
332     public abstract int getConstOpcode();
333 
334     /**
335      * Returns a string representation of this type.
336      *
337      * @return the descriptor of this type.
338      */
339     @Override
340     public final String toString() {
341         return getDescriptor();
342     }
343 }