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 Bonr </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
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
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
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
222
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
234
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
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
334 if (typeDesc.startsWith("[")) {
335 result = typeDesc;
336 } else {
337
338 if (typeDesc.startsWith("L") && typeDesc.endsWith(";")) {
339 result = typeDesc.substring(1, typeDesc.length() - 1);
340 } else {
341
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 }