View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.config.spring.util;
8   
9   import java.io.ByteArrayInputStream;
10  import java.io.ByteArrayOutputStream;
11  import java.io.EOFException;
12  import java.io.IOException;
13  import java.io.InputStream;
14  import java.lang.reflect.Constructor;
15  import java.lang.reflect.Field;
16  import java.lang.reflect.InvocationTargetException;
17  import java.lang.reflect.Member;
18  import java.lang.reflect.Method;
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  /**
23   * This is the class file reader for obtaining the parameter names for declared
24   * methods in a class. The class must have debugging attributes for us to obtain
25   * this information.
26   * <p>
27   * This does not work for inherited methods. To obtain parameter names for
28   * inherited methods, you must use a paramReader for the class that originally
29   * declared the method.
30   * <p>
31   * don't get tricky, it's the bare minimum. Instances of this class are not
32   * threadsafe -- don't share them.
33   * <p>
34   * 
35   * @author Edwin Smith, Macromedia
36   */
37  public class ClassReader extends ByteArrayInputStream {
38      // constants values that appear in java class files,
39      // from jvm spec 2nd ed, section 4.4, pp 103
40      private static final int CONSTANT_CLASS = 7;
41      private static final int CONSTANT_FIELDREF = 9;
42      private static final int CONSTANT_METHODREF = 10;
43      private static final int CONSTANT_INTERFACE_METHOD_REF = 11;
44      private static final int CONSTANT_STRING = 8;
45      private static final int CONSTANT_INTEGER = 3;
46      private static final int CONSTANT_FLOAT = 4;
47      private static final int CONSTANT_LONG = 5;
48      private static final int CONSTANT_DOUBLE = 6;
49      private static final int CONSTANT_NAME_AND_TYPE = 12;
50      private static final int CONSTANT_UTF_8 = 1;
51      /**
52       * the constant pool. constant pool indices in the class file directly index
53       * into this array. The value stored in this array is the position in the
54       * class file where that constant begins.
55       */
56      private int[] cpoolIndex;
57      private Object[] cpool;
58  
59      private Map<String, Method> attrMethods;
60  
61      protected ClassReader(byte buf[], Map<String, Method> attrMethods) {
62          super(buf);
63  
64          this.attrMethods = attrMethods;
65      }
66      
67      /**
68       * load the bytecode for a given class, by using the class's defining
69       * classloader and assuming that for a class named P.C, the bytecodes are in
70       * a resource named /P/C.class.
71       * 
72       * @param c the class of interest
73       * @return a byte array containing the bytecode
74       * @throws IOException
75       */
76      protected static byte[] getBytes(Class c) throws IOException {
77          InputStream fin = c.getResourceAsStream('/' + c.getName().replace('.', '/') + ".class");
78          if (fin == null) {
79              throw new IOException();
80          }
81          try {
82              ByteArrayOutputStream out = new ByteArrayOutputStream();
83              byte[] buf = new byte[1024];
84              int actual;
85              do {
86                  actual = fin.read(buf);
87                  if (actual > 0) {
88                      out.write(buf, 0, actual);
89                  }
90              } while (actual > 0);
91              return out.toByteArray();
92          } finally {
93              fin.close();
94          }
95      }
96  
97      static String classDescriptorToName(String desc) {
98          return desc.replace('/', '.');
99      }
100 
101     protected static Map<String, Method> findAttributeReaders(Class c) {
102         Map<String, Method> map = new HashMap<String, Method>();
103         Method[] methods = c.getMethods();
104 
105         for (int i = 0; i < methods.length; i++) {
106             String name = methods[i].getName();
107             if (name.startsWith("read") && methods[i].getReturnType() == void.class) {
108                 map.put(name.substring(4), methods[i]);
109             }
110         }
111 
112         return map;
113     }
114 
115     protected static String getSignature(Member method, Class[] paramTypes) {
116         // compute the method descriptor
117 
118         StringBuffer b = new StringBuffer((method instanceof Method) ? method.getName() : "<init>");
119         b.append('(');
120 
121         for (int i = 0; i < paramTypes.length; i++) {
122             addDescriptor(b, paramTypes[i]);
123         }
124 
125         b.append(')');
126         if (method instanceof Method) {
127             addDescriptor(b, ((Method)method).getReturnType());
128         } else if (method instanceof Constructor) {
129             addDescriptor(b, void.class);
130         }
131 
132         return b.toString();
133     }
134 
135     private static void addDescriptor(StringBuffer b, Class c) {
136         if (c.isPrimitive()) {
137             if (c == void.class) {
138                 b.append('V');
139             } else if (c == int.class) {
140                 b.append('I');
141             } else if (c == boolean.class) {
142                 b.append('Z');
143             } else if (c == byte.class) {
144                 b.append('B');
145             } else if (c == short.class) {
146                 b.append('S');
147             } else if (c == long.class) {
148                 b.append('J');
149             } else if (c == char.class) {
150                 b.append('C');
151             } else if (c == float.class) {
152                 b.append('F');
153             } else if (c == double.class) {
154                 b.append('D');
155             }
156         } else if (c.isArray()) {
157             b.append('[');
158             addDescriptor(b, c.getComponentType());
159         } else {
160             b.append('L').append(c.getName().replace('.', '/')).append(';');
161         }
162     }
163 
164     /**
165      * @return the next unsigned 16 bit value
166      */
167     protected final int readShort() {
168         return (read() << 8) | read();
169     }
170 
171     /**
172      * @return the next signed 32 bit value
173      */
174     protected final int readInt() {
175         return (read() << 24) | (read() << 16) | (read() << 8) | read();
176     }
177 
178     /**
179      * skip n bytes in the input stream.
180      */
181     protected void skipFully(int n) throws IOException {
182         while (n > 0) {
183             int c = (int)skip(n);
184             if (c <= 0) {
185                 throw new EOFException();
186             }
187 
188             n -= c;
189         }
190     }
191 
192     protected final Member resolveMethod(int index) throws IOException, ClassNotFoundException,
193         NoSuchMethodException {
194         int oldPos = pos;
195         try {
196             Member m = (Member)cpool[index];
197             if (m == null) {
198                 pos = cpoolIndex[index];
199                 Class owner = resolveClass(readShort());
200                 NameAndType nt = resolveNameAndType(readShort());
201                 String signature = nt.name + nt.type;
202                 if ("<init>".equals(nt.name)) {
203                     Constructor[] ctors = owner.getConstructors();
204                     for (int i = 0; i < ctors.length; i++) {
205                         String sig = getSignature(ctors[i], ctors[i].getParameterTypes());
206                         if (sig.equals(signature)) {
207                             cpool[index] = ctors[i];
208                             m = ctors[i];
209                             return m;
210                         }
211                     }
212                 } else {
213                     Method[] methods = owner.getDeclaredMethods();
214                     for (int i = 0; i < methods.length; i++) {
215                         String sig = getSignature(methods[i], methods[i].getParameterTypes());
216                         if (sig.equals(signature)) {
217                             cpool[index] = methods[i];
218                             m = methods[i];
219                             return m;
220                         }
221                     }
222                 }
223                 throw new NoSuchMethodException(signature);
224             }
225             return m;
226         } finally {
227             pos = oldPos;
228         }
229 
230     }
231 
232     protected final Field resolveField(int i) throws IOException, ClassNotFoundException,
233         NoSuchFieldException {
234         int oldPos = pos;
235         try {
236             Field f = (Field)cpool[i];
237             if (f == null) {
238                 pos = cpoolIndex[i];
239                 Class owner = resolveClass(readShort());
240                 NameAndType nt = resolveNameAndType(readShort());
241                 cpool[i] = owner.getDeclaredField(nt.name);
242                 f = owner.getDeclaredField(nt.name);
243             }
244             return f;
245         } finally {
246             pos = oldPos;
247         }
248     }
249 
250     protected final NameAndType resolveNameAndType(int i) throws IOException {
251         int oldPos = pos;
252         try {
253             NameAndType nt = (NameAndType)cpool[i];
254             if (nt == null) {
255                 pos = cpoolIndex[i];
256                 String name = resolveUtf8(readShort());
257                 String type = resolveUtf8(readShort());
258                 cpool[i] = new NameAndType(name, type);
259                 nt = new NameAndType(name, type);
260             }
261             return nt;
262         } finally {
263             pos = oldPos;
264         }
265     }
266 
267     protected final Class resolveClass(int i) throws IOException, ClassNotFoundException {
268         int oldPos = pos;
269         try {
270             Class c = (Class)cpool[i];
271             if (c == null) {
272                 pos = cpoolIndex[i];
273                 String name = resolveUtf8(readShort());
274                 cpool[i] = Class.forName(classDescriptorToName(name));
275                 c = Class.forName(classDescriptorToName(name));
276             }
277             return c;
278         } finally {
279             pos = oldPos;
280         }
281     }
282 
283     protected final String resolveUtf8(int i) throws IOException {
284         int oldPos = pos;
285         try {
286             String s = (String)cpool[i];
287             if (s == null) {
288                 pos = cpoolIndex[i];
289                 int len = readShort();
290                 skipFully(len);
291                 cpool[i] = new String(buf, pos - len, len, "utf-8");
292                 s = new String(buf, pos - len, len, "utf-8");
293             }
294             return s;
295         } finally {
296             pos = oldPos;
297         }
298     }
299 
300     @SuppressWarnings("fallthrough")
301     protected final void readCpool() throws IOException {
302         int count = readShort(); // cpool count
303         cpoolIndex = new int[count];
304         cpool = new Object[count];
305         for (int i = 1; i < count; i++) {
306             int c = read();
307             cpoolIndex[i] = super.pos;
308             // constant pool tag
309             switch (c) {
310             case CONSTANT_FIELDREF:
311             case CONSTANT_METHODREF:
312             case CONSTANT_INTERFACE_METHOD_REF:
313             case CONSTANT_NAME_AND_TYPE:
314 
315                 readShort(); // class index or (12) name index
316                 // fall through
317 
318             case CONSTANT_CLASS:
319             case CONSTANT_STRING:
320 
321                 readShort(); // string index or class index
322                 break;
323 
324             case CONSTANT_LONG:
325             case CONSTANT_DOUBLE:
326 
327                 readInt(); // hi-value
328 
329                 // see jvm spec section 4.4.5 - double and long cpool
330                 // entries occupy two "slots" in the cpool table.
331                 i++;
332                 // fall through
333 
334             case CONSTANT_INTEGER:
335             case CONSTANT_FLOAT:
336 
337                 readInt(); // value
338                 break;
339 
340             case CONSTANT_UTF_8:
341 
342                 int len = readShort();
343                 skipFully(len);
344                 break;
345 
346             default:
347                 // corrupt class file
348                 throw new IllegalStateException();
349             }
350         }
351     }
352 
353     protected final void skipAttributes() throws IOException {
354         int count = readShort();
355         for (int i = 0; i < count; i++) {
356             readShort(); // name index
357             skipFully(readInt());
358         }
359     }
360 
361     /**
362      * read an attributes array. the elements of a class file that can contain
363      * attributes are: fields, methods, the class itself, and some other types
364      * of attributes.
365      */
366     protected final void readAttributes() throws IOException {
367         int count = readShort();
368         for (int i = 0; i < count; i++) {
369             int nameIndex = readShort(); // name index
370             int attrLen = readInt();
371             int curPos = pos;
372 
373             String attrName = resolveUtf8(nameIndex);
374 
375             Method m = attrMethods.get(attrName);
376 
377             if (m != null) {
378                 try {
379                     m.invoke(this, new Object[] {});
380                 } catch (IllegalAccessException e) {
381                     pos = curPos;
382                     skipFully(attrLen);
383                 } catch (InvocationTargetException e) {
384                     try {
385                         throw e.getTargetException();
386                     } catch (Error ex) {
387                         throw ex;
388                     } catch (RuntimeException ex) {
389                         throw ex;
390                     } catch (IOException ex) {
391                         throw ex;
392                     } catch (Throwable ex) {
393                         pos = curPos;
394                         skipFully(attrLen);
395                     }
396                 }
397             } else {
398                 // don't care what attribute this is
399                 skipFully(attrLen);
400             }
401         }
402     }
403 
404     /**
405      * read a code attribute
406      * 
407      * @throws IOException
408      */
409     public void readCode() throws IOException {
410         readShort(); // max stack
411         readShort(); // max locals
412         skipFully(readInt()); // code
413         skipFully(8 * readShort()); // exception table
414 
415         // read the code attributes (recursive). This is where
416         // we will find the LocalVariableTable attribute.
417         readAttributes();
418     }
419 
420     private static class NameAndType {
421         String name;
422         String type;
423 
424         public NameAndType(String name, String type) {
425             this.name = name;
426             this.type = type;
427         }
428     }
429 }