View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.transform;
9   
10  import org.codehaus.aspectwerkz.ContextClassLoader;
11  import org.codehaus.aspectwerkz.transform.TransformationConstants;
12  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
13  import org.objectweb.asm.ClassWriter;
14  import org.objectweb.asm.CodeVisitor;
15  import org.objectweb.asm.Constants;
16  import org.objectweb.asm.Type;
17  import org.objectweb.asm.Label;
18  import org.objectweb.asm.ClassReader;
19  
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.InvocationTargetException;
26  
27  /***
28   * Helper class with utility methods for the ASM library.
29   *
30   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
31   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
32   */
33  public class AsmHelper implements Constants, TransformationConstants {
34  
35      /***
36       * A boolean to check if we have a J2SE 5 support
37       */
38      public final static boolean IS_JAVA_5;
39      public static int JAVA_VERSION = V1_3;
40  
41      static {
42          Class annotation = null;
43          try {
44              annotation = Class.forName("java.lang.annotation.Annotation");
45              ClassReader cr = new ClassReader("java.lang.annotation.Annotation");
46              JAVA_VERSION = V1_5;
47          } catch (Throwable e) {
48              annotation = null;
49          }
50          if (annotation == null) {
51              IS_JAVA_5 = false;
52          } else {
53              IS_JAVA_5 = true;
54          }
55      }
56  
57      /***
58       * Factory method for ASM ClassWriter and J2SE 5 support
59       * See http://www.objectweb.org/wws/arc/asm/2004-08/msg00005.html
60       *
61       * @param computeMax
62       * @return
63       */
64      public static ClassWriter newClassWriter(boolean computeMax) {
65          return new ClassWriter(computeMax, true);
66      }
67  
68      /***
69       * Creates a constructor descriptor.
70       * <p/>
71       * Parts of code in this method is taken from the ASM codebase.
72       *
73       * @param constructor
74       * @return the descriptor
75       */
76      public static String getConstructorDescriptor(final Constructor constructor) {
77          Class[] parameters = constructor.getParameterTypes();
78          StringBuffer buf = new StringBuffer();
79          buf.append('(');
80          for (int i = 0; i < parameters.length; ++i) {
81              Class d = parameters[i];
82              while (true) {
83                  if (d.isPrimitive()) {
84                      char car;
85                      if (d == Integer.TYPE) {
86                          car = 'I';
87                      } else if (d == Void.TYPE) {
88                          car = 'V';
89                      } else if (d == Boolean.TYPE) {
90                          car = 'Z';
91                      } else if (d == Byte.TYPE) {
92                          car = 'B';
93                      } else if (d == Character.TYPE) {
94                          car = 'C';
95                      } else if (d == Short.TYPE) {
96                          car = 'S';
97                      } else if (d == Double.TYPE) {
98                          car = 'D';
99                      } else if (d == Float.TYPE) {
100                         car = 'F';
101                     } else /* if (d == Long.TYPE) */ {
102                         car = 'J';
103                     }
104                     buf.append(car);
105                     break;
106                 } else if (d.isArray()) {
107                     buf.append('[');
108                     d = d.getComponentType();
109                 } else {
110                     buf.append('L');
111                     String name = d.getName();
112                     int len = name.length();
113                     for (int i1 = 0; i1 < len; ++i1) {
114                         char car = name.charAt(i1);
115                         buf.append((car == '.') ? '/' : car);
116                     }
117                     buf.append(';');
118                     break;
119                 }
120             }
121         }
122         buf.append(")V");
123         return buf.toString();
124     }
125 
126     /***
127      * Creates a method descriptor.
128      * <p/>
129      * Parts of code in this method is taken from the ASM codebase.
130      *
131      * @param method
132      * @return the descriptor
133      */
134     public static String getMethodDescriptor(final Method method) {
135         Class[] parameters = method.getParameterTypes();
136         StringBuffer buf = new StringBuffer();
137         buf.append('(');
138         for (int i = 0; i < parameters.length; ++i) {
139             Class d = parameters[i];
140             while (true) {
141                 if (d.isPrimitive()) {
142                     char car;
143                     if (d == Integer.TYPE) {
144                         car = 'I';
145                     } else if (d == Void.TYPE) {
146                         car = 'V';
147                     } else if (d == Boolean.TYPE) {
148                         car = 'Z';
149                     } else if (d == Byte.TYPE) {
150                         car = 'B';
151                     } else if (d == Character.TYPE) {
152                         car = 'C';
153                     } else if (d == Short.TYPE) {
154                         car = 'S';
155                     } else if (d == Double.TYPE) {
156                         car = 'D';
157                     } else if (d == Float.TYPE) {
158                         car = 'F';
159                     } else /* if (d == Long.TYPE) */ {
160                         car = 'J';
161                     }
162                     buf.append(car);
163                     break;
164                 } else if (d.isArray()) {
165                     buf.append('[');
166                     d = d.getComponentType();
167                 } else {
168                     buf.append('L');
169                     String name = d.getName();
170                     int len = name.length();
171                     for (int i1 = 0; i1 < len; ++i1) {
172                         char car = name.charAt(i1);
173                         buf.append((car == '.') ? '/' : car);
174                     }
175                     buf.append(';');
176                     break;
177                 }
178             }
179         }
180         buf.append(")");
181         //FIXME handles return type
182         return buf.toString();
183     }
184 
185     /***
186      * Gets the argument types for a constructor. <p/>Parts of code in this method is taken from the ASM codebase.
187      *
188      * @param constructor
189      * @return the ASM argument types for the constructor
190      */
191     public static Type[] getArgumentTypes(final Constructor constructor) {
192         Class[] classes = constructor.getParameterTypes();
193         Type[] types = new Type[classes.length];
194         for (int i = classes.length - 1; i >= 0; --i) {
195             types[i] = Type.getType(classes[i]);
196         }
197         return types;
198     }
199 
200     /***
201      * Adds a class to a class loader and loads it.
202      *
203      * @param loader the class loader (if null the context class loader will be used)
204      * @param bytes  the bytes for the class
205      * @param name   the name of the class
206      * @return the class
207      */
208     public static Class loadClass(ClassLoader loader, final byte[] bytes, final String name) {
209         String className = name.replace('/', '.');
210         try {
211             if (loader == null) {
212                 loader = ContextClassLoader.getLoader();
213             }
214             Class klass = loader.loadClass(CLASS_LOADER_REFLECT_CLASS_NAME);
215             Method method = klass.getDeclaredMethod(
216                     DEFINE_CLASS_METHOD_NAME, new Class[]{
217                         String.class, byte[].class, int.class, int.class
218                     }
219             );
220 
221             // TODO: what if we don't have rights to set this method to
222             // accessible on this specific CL? Load it in System CL?
223             method.setAccessible(true);
224             Object[] args = new Object[]{
225                 className, bytes, new Integer(0), new Integer(bytes.length)
226             };
227             Class clazz = (Class) method.invoke(loader, args);
228 
229             method.setAccessible(false);
230             return clazz;
231 
232         } catch (InvocationTargetException e) {
233             // JIT failovering for Thread concurrency
234             // AW-222 (Tomcat and WLS were reported for AW-222)
235             if (e.getTargetException() instanceof LinkageError) {
236                 Class failoverJoinpointClass = loadClass(loader, className);
237                 if (failoverJoinpointClass != null) {
238                     return failoverJoinpointClass;
239                 }
240             }
241             throw new WrappedRuntimeException(e);
242         } catch (Exception e) {
243             throw new WrappedRuntimeException(e);
244         }
245     }
246 
247     /***
248      * Tries to load a class if unsuccessful returns null.
249      *
250      * @param loader the class loader
251      * @param name   the name of the class
252      * @return the class
253      */
254     public static Class loadClass(ClassLoader loader, final String name) {
255         String className = name.replace('/', '.');
256         try {
257             if (loader == null) {
258                 loader = ContextClassLoader.getLoader();
259             }
260             // use Class.forName since loader.loadClass leads to error on JBoss UCL
261             return Class.forName(className, false, loader);
262         } catch (Exception e) {
263             return null;
264         }
265     }
266 
267     /***
268      * Calculates the method hash. The computation MUST be the same as in ReflectHelper, thus we switch back the names
269      * to Java style. Note that for array type, Java.reflect is using "[Lpack.foo;" style unless primitive.
270      *
271      * @param name
272      * @param desc
273      * @return
274      */
275     public static int calculateMethodHash(final String name, final String desc) {
276         int hash = 17;
277         hash = (37 * hash) + name.replace('/', '.').hashCode();
278         Type[] argumentTypes = Type.getArgumentTypes(desc);
279         for (int i = 0; i < argumentTypes.length; i++) {
280             hash = (37 * hash)
281                    + AsmHelper.convertTypeDescToReflectDesc(argumentTypes[i].getDescriptor()).hashCode();
282         }
283         return hash;
284     }
285 
286     /***
287      * Calculates the constructor hash.
288      *
289      * @param desc
290      * @return
291      */
292     public static int calculateConstructorHash(final String desc) {
293         return AsmHelper.calculateMethodHash(INIT_METHOD_NAME, desc);
294     }
295 
296     /***
297      * Calculates the field hash.
298      *
299      * @param name
300      * @param desc
301      * @return
302      */
303     public static int calculateFieldHash(final String name, final String desc) {
304         int hash = 17;
305         hash = (37 * hash) + name.hashCode();
306         Type type = Type.getType(desc);
307         hash = (37 * hash) + AsmHelper.convertTypeDescToReflectDesc(type.getDescriptor()).hashCode();
308         return hash;
309     }
310 
311     /***
312      * Calculates the class hash.
313      *
314      * @param declaringType
315      * @return
316      */
317     public static int calculateClassHash(final String declaringType) {
318         return AsmHelper.convertTypeDescToReflectDesc(declaringType).hashCode();
319     }
320 
321     /***
322      * Converts an ASM type descriptor" (I, [I, [Ljava/lang/String;, Ljava/lang/String;) to a Java.reflect one (int, [I,
323      * [Ljava.lang.String;, java.lang.String)
324      *
325      * @param typeDesc
326      * @return the Java.reflect string representation
327      */
328     public static String convertTypeDescToReflectDesc(final String typeDesc) {
329         if (typeDesc == null) {
330             return null;
331         }
332         String result = null;
333         // change needed for array types only
334         if (typeDesc.startsWith("[")) {
335             result = typeDesc;
336         } else {
337             // support for single dimension type
338             if (typeDesc.startsWith("L") && typeDesc.endsWith(";")) {
339                 result = typeDesc.substring(1, typeDesc.length() - 1);
340             } else {
341                 // primitive type, single dimension
342                 if (typeDesc.equals("I")) {
343                     result = "int";
344                 } else if (typeDesc.equals("J")) {
345                     result = "long";
346                 } else if (typeDesc.equals("S")) {
347                     result = "short";
348                 } else if (typeDesc.equals("F")) {
349                     result = "float";
350                 } else if (typeDesc.equals("D")) {
351                     result = "double";
352                 } else if (typeDesc.equals("Z")) {
353                     result = "boolean";
354                 } else if (typeDesc.equals("C")) {
355                     result = "char";
356                 } else if (typeDesc.equals("B")) {
357                     result = "byte";
358                 } else {
359                     throw new RuntimeException("unknown primitive type " + typeDesc);
360                 }
361             }
362         }
363         return result.replace('/', '.');
364     }
365 
366     /***
367      * Converts a java reflect type desc to ASM type desc.
368      *
369      * @param desc
370      * @return
371      */
372     public static String convertReflectDescToTypeDesc(final String desc) {
373         if (desc == null) {
374             return null;
375         }
376         String typeDesc = desc;
377         int dimension = 0;
378         char[] arr = desc.toCharArray();
379         for (int i = 0; i < arr.length; i++) {
380             if (arr[i] == ']') {
381                 dimension++;
382             }
383         }
384         typeDesc = desc.substring(0, desc.length() - dimension * 2);
385         if (typeDesc.equals("int")) {
386             typeDesc = "I";
387         } else if (typeDesc.equals("short")) {
388             typeDesc = "S";
389         } else if (typeDesc.equals("long")) {
390             typeDesc = "J";
391         } else if (typeDesc.equals("float")) {
392             typeDesc = "F";
393         } else if (typeDesc.equals("double")) {
394             typeDesc = "D";
395         } else if (typeDesc.equals("byte")) {
396             typeDesc = "B";
397         } else if (typeDesc.equals("char")) {
398             typeDesc = "C";
399         } else if (typeDesc.equals("boolean")) {
400             typeDesc = "Z";
401         } else {
402             typeDesc = 'L' + typeDesc + ';';
403         }
404         for (int i = 0; i < dimension; i++) {
405             typeDesc = '[' + typeDesc;
406         }
407         return typeDesc.replace('.', '/');
408     }
409 
410     /***
411      * Creates and adds the correct parameter index for integer types.
412      *
413      * @param cv
414      * @param index
415      */
416     public static void loadIntegerConstant(final CodeVisitor cv, final int index) {
417         switch (index) {
418             case 0:
419                 cv.visitInsn(ICONST_0);
420                 break;
421             case 1:
422                 cv.visitInsn(ICONST_1);
423                 break;
424             case 2:
425                 cv.visitInsn(ICONST_2);
426                 break;
427             case 3:
428                 cv.visitInsn(ICONST_3);
429                 break;
430             case 4:
431                 cv.visitInsn(ICONST_4);
432                 break;
433             case 5:
434                 cv.visitInsn(ICONST_5);
435                 break;
436             default:
437                 cv.visitIntInsn(BIPUSH, index);
438                 break;
439         }
440     }
441 
442 }