KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > bsf > smartValueObject > tools > ASMInstrumentor


1 package org.bsf.smartValueObject.tools;
2
3 import org.objectweb.asm.*;
4 import org.apache.commons.logging.Log;
5 import org.apache.commons.logging.LogFactory;
6 import org.bsf.smartValueObject.tools.Instrumentor;
7 import org.bsf.smartValueObject.Versionable;
8 import org.bsf.smartValueObject.Version;
9
10 import java.io.File JavaDoc;
11 import java.io.FileInputStream JavaDoc;
12 import java.io.InputStream JavaDoc;
13 import java.util.*;
14 import java.lang.reflect.Method JavaDoc;
15
16 /**
17  * ASM specific implementation of Instrumentor. This code is ugly (mainly
18  * due to ASM's low-levelness).
19  *
20  * @see org.bsf.smartValueObject.tools.Instrumentor
21  * @see org.bsf.smartValueObject.tools.JavaAssistInstrumentor
22  * @see <a HREF="http://asm.objectweb.org/">ASM homepage</a>
23  */

24 public class ASMInstrumentor implements Instrumentor {
25     private static Log log = LogFactory.getLog(ASMInstrumentor.class);
26     private ClassWriter cw;
27     private String JavaDoc className;
28
29     public void modifyClass(String JavaDoc name) throws InstrumentorException {
30         modifyClass(null, name);
31     }
32
33     public void modifyClass(String JavaDoc basedir, String JavaDoc name) throws InstrumentorException {
34         String JavaDoc file;
35         if (basedir != null) {
36             file = basedir + File.separator + name;
37         } else {
38             file = name;
39         }
40
41         className = name
42                 .substring(0, name.length() - 6)
43                 .replace(File.separatorChar, '/');
44
45         try {
46             InputStream JavaDoc is = new FileInputStream JavaDoc(file);
47             ClassReader cr = new ClassReader(is);
48             cw = new ClassWriter(true);
49             ClassVisitor cv = new SVOClassAdapter(className, cw);
50             cr.accept(cv, true);
51         } catch (Exception JavaDoc e) {
52             throw new InstrumentorException(e);
53         }
54
55     }
56
57     public byte[] getBytecode() throws InstrumentorException {
58         if (cw == null) {
59             throw new InstrumentorException();
60         }
61         return cw.toByteArray();
62     }
63
64     public Class JavaDoc defineClass() {
65        throw new UnsupportedOperationException JavaDoc();
66     }
67
68     /**
69      * Adapter to change class informations with ASM.
70      */

71     private class SVOClassAdapter extends ClassAdapter implements Constants {
72         private Set methods = new HashSet();
73         private boolean methodsCreated = false;
74         private String JavaDoc internalName;
75
76         public SVOClassAdapter(String JavaDoc className, ClassVisitor cv) {
77             super(cv);
78             this.internalName = className;
79         }
80
81         public void visit(int access, String JavaDoc name, String JavaDoc superName,
82                           String JavaDoc[] interfaces, String JavaDoc srcfile) {
83             log.debug("visit()");
84             String JavaDoc[] newInterfaces;
85             if (interfaces == null) {
86                 newInterfaces = new String JavaDoc[1];
87             } else {
88                 newInterfaces = new String JavaDoc[interfaces.length + 1];
89                 System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
90             }
91
92             newInterfaces[newInterfaces.length - 1] = VERSIONINTERFACE.replace('.', '/');
93
94             cv.visit(access, name, superName, newInterfaces, srcfile);
95             createVersionableField(cv);
96         }
97
98         public CodeVisitor visitMethod(int i, String JavaDoc s, String JavaDoc s1, String JavaDoc[] strings, Attribute attribute) {
99             log.debug("visitMethod(" + s + ")");
100             CodeVisitor mv = cv.visitMethod(i, s, s1, strings, attribute);
101             return mv == null ? null : new SVOCodeAdapter(s, mv, this);
102         }
103
104         public void visitInnerClass(String JavaDoc s, String JavaDoc s1, String JavaDoc s2, int i) {
105             log.debug("visitInnerClass()");
106             if (!methodsCreated)
107                 createTrapMethods();
108             cv.visitInnerClass(s, s1, s2, i);
109         }
110
111         public void visitAttribute(Attribute attribute) {
112             log.debug("visitAttribute()");
113             if (!methodsCreated)
114                 createTrapMethods();
115             cv.visitAttribute(attribute);
116         }
117
118         public void visitEnd() {
119             log.debug("visitEnd()");
120             if (!methodsCreated)
121                 createTrapMethods();
122             cv.visitEnd();
123         }
124
125         public void addMethod(MyMethod m) {
126             log.debug("addMethod(" + m + ")");
127             methods.add(m);
128         }
129
130         public String JavaDoc getInternalName() {
131             return internalName;
132         }
133
134         private void createTrapMethods() {
135             log.debug("createTrapMethods()");
136             for (Iterator it = methods.iterator(); it.hasNext(); ) {
137                 MyMethod method = (MyMethod) it.next();
138                 CodeVisitor codevisitor = cv.visitMethod(
139                         ACC_PRIVATE, /* access */
140                         method.getName(), /* name */
141                         "(" + method.getType() + ")V", /* descriptor */
142                         null, /* exceptions */
143                         null); /* attributes */
144
145                 createTrapMethod(method, codevisitor);
146             }
147
148             createVersionableMethods(Versionable.class, VERSIONFIELD);
149             methodsCreated = true;
150         }
151
152         private void createTrapMethod(MyMethod m, CodeVisitor cv) {
153             log.debug("createTrapMethod(" + m + ")");
154
155             if (m.hasSmartContainer()) {
156                 createContainerTrap(m, cv);
157                 return;
158             }
159
160             // check if field was changed (primitive types and java.lang.*)
161
if (m.getType().length() == 1 ||
162                 m.getTypeInternalName().startsWith("java/lang")) {
163                 Label fieldDifferent = new Label();
164                 Label fieldNull = new Label();
165
166                 // load field on operand stack
167
cv.visitVarInsn(ALOAD, 0);
168                 cv.visitFieldInsn(GETFIELD, getInternalName(), m.getField(), m.getType());
169
170                 if (m.getType().startsWith("L")) {
171                     // reference type, invoke equals if field != null
172
cv.visitInsn(DUP);
173                     cv.visitJumpInsn(IFNULL, fieldNull);
174                     cv.visitVarInsn(ALOAD, 1);
175                     cv.visitMethodInsn(INVOKEVIRTUAL, m.getTypeInternalName(), "equals", "(Ljava/lang/Object;)Z");
176                     cv.visitJumpInsn(IFEQ, fieldDifferent);
177                     // field is equal, return
178
} else if (m.getType().startsWith("D")) {
179                     cv.visitVarInsn(DLOAD, 1);
180                     cv.visitInsn(DCMPL);
181                     cv.visitJumpInsn(IFNE, fieldDifferent);
182                 } else if (m.getType().startsWith("F")) {
183                     cv.visitVarInsn(FLOAD, 1);
184                     cv.visitInsn(FCMPL);
185                     cv.visitJumpInsn(IFNE, fieldDifferent);
186                 } else if (m.getType().startsWith("J")) {
187                     cv.visitVarInsn(LLOAD, 1);
188                     cv.visitInsn(LCMP);
189                     cv.visitJumpInsn(IFNE, fieldDifferent);
190                 } else if (m.getType().startsWith("]")) {
191                     cv.visitJumpInsn(GOTO, fieldDifferent);
192                 } else {
193                     cv.visitVarInsn(ILOAD, 1);
194                     cv.visitJumpInsn(IF_ICMPNE, fieldDifferent);
195                 }
196
197                 cv.visitInsn(RETURN);
198
199                 // our field is null
200
cv.visitLabel(fieldNull);
201                 // so forget the field on the operand stack
202
cv.visitInsn(POP);
203                 // ... and check if the parameter is != null
204
cv.visitVarInsn(ALOAD, 1);
205                 cv.visitJumpInsn(IFNONNULL, fieldDifferent);
206
207                 cv.visitInsn(RETURN);
208
209                 cv.visitLabel(fieldDifferent);
210             }
211
212
213
214             cv.visitVarInsn(ALOAD, 0);
215             cv.visitFieldInsn(GETFIELD,
216                     getInternalName(),
217                     VERSIONFIELD,
218                     Type.getDescriptor(Versionable.class));
219
220             // call version.touch(String fieldname)
221
cv.visitLdcInsn(m.getField());
222             cv.visitMethodInsn(INVOKEINTERFACE,
223                     Type.getDescriptor(Versionable.class),
224                     VERSIONMETHOD, "(Ljava/lang/String;)V");
225
226             // set new field value
227
cv.visitVarInsn(ALOAD, 0);
228             String JavaDoc type = m.getType();
229             if (type.startsWith("L") || type.startsWith("["))
230                 cv.visitVarInsn(ALOAD, 1);
231             else if (type.startsWith("J"))
232                 cv.visitVarInsn(LLOAD, 1);
233             else if (type.startsWith("D"))
234                 cv.visitVarInsn(DLOAD, 1);
235             else if (type.startsWith("F"))
236                 cv.visitVarInsn(FLOAD, 1);
237             else
238                 cv.visitVarInsn(ILOAD, 1);
239
240             cv.visitFieldInsn(PUTFIELD,
241                     getInternalName(),
242                     m.getField(),
243                     m.getType());
244             cv.visitInsn(RETURN);
245             cv.visitMaxs(4,2);
246         }
247
248         private void createContainerTrap(MyMethod m, CodeVisitor cv) {
249             log.debug("createContainerTrap(" + m + ")");
250             String JavaDoc typeClassName = m.getTypeClassName();
251             String JavaDoc typeClassDesc = typeClassName.replace('.', '/');
252             String JavaDoc replacement = SMARTCONTAINERS.getProperty(typeClassName);
253
254             if (replacement == null) {
255                 log.debug("createContainerTrap: no replacement for " + typeClassName);
256                 return;
257             }
258
259             String JavaDoc replacementDesc = replacement.replace('.', '/');
260             String JavaDoc versionableDesc = VERSIONINTERFACE.replace('.', '/');
261
262             // create smart container
263
cv.visitVarInsn(ALOAD, 0);
264             cv.visitTypeInsn(NEW, replacementDesc);
265             cv.visitInsn(DUP);
266
267             // load parameter on stack
268
cv.visitVarInsn(ALOAD, 1);
269
270             // load versionable field on stack
271
cv.visitVarInsn(ALOAD, 0);
272             cv.visitFieldInsn(GETFIELD, getInternalName(), VERSIONFIELD, "L" + versionableDesc + ";");
273
274             // invoke contructor
275
cv.visitMethodInsn(INVOKESPECIAL,
276                                 replacementDesc,
277                                 "<init>",
278                                 "(L" + typeClassDesc + ";L" + versionableDesc + ";)V");
279
280             // store new object in field
281
cv.visitFieldInsn(PUTFIELD, getInternalName(), m.getField(), m.getType());
282             cv.visitInsn(RETURN);
283             cv.visitMaxs(4, 2);
284         }
285
286         private void createVersionableField(ClassVisitor cv) {
287             log.debug("createVersionableField()");
288             cv.visitField(
289                     ACC_PRIVATE, /* access */
290                     VERSIONFIELD, /* name */
291                     Type.getDescriptor(Versionable.class), /* descriptor */
292                     null, /* static value */
293                     null); /* attributes */
294         }
295
296         private void createVersionableMethods(Class JavaDoc clazz, String JavaDoc field) {
297             log.debug("createVersionableMethods(" + clazz + ", " + field);
298             java.lang.reflect.Method JavaDoc[] methods = clazz.getDeclaredMethods();
299             for (int i = 0; i < methods.length; i++) {
300                 Method JavaDoc method = methods[i];
301                 String JavaDoc name = method.getName();
302                 Class JavaDoc[] _exceptions = method.getExceptionTypes();
303                 String JavaDoc[] exceptions;
304                 if (_exceptions.length != 0) {
305                     exceptions = new String JavaDoc[_exceptions.length];
306                     for (int j = 0; j < exceptions.length; j++) {
307                         Class JavaDoc exception = _exceptions[j];
308                         exceptions[j] = Type.getDescriptor(exception);
309                     }
310                 } else {
311                     exceptions = null;
312                 }
313
314                 String JavaDoc desc = Type.getMethodDescriptor(method);
315
316                 // create method signature
317
CodeVisitor codevisitor = cv.visitMethod(ACC_PUBLIC,
318                     name, desc, exceptions, null);
319                 // load 'this'
320
codevisitor.visitVarInsn(ALOAD, 0);
321                 // get version field
322
codevisitor.visitFieldInsn(GETFIELD,
323                         getInternalName(),
324                         VERSIONFIELD,
325                         Type.getDescriptor(Versionable.class));
326
327                 // load parameters on operand stack
328
Class JavaDoc[] parameters = method.getParameterTypes();
329                 for (int j = 0; j < parameters.length; j++) {
330                     Class JavaDoc parameter = parameters[j];
331                     if (!parameter.isPrimitive()) {
332                         codevisitor.visitVarInsn(ALOAD, 1+j);
333                     } else if (parameter == Double.TYPE) {
334                         codevisitor.visitVarInsn(DLOAD, 1+j);
335                     } else if (parameter == Float.TYPE) {
336                         codevisitor.visitVarInsn(FLOAD, 1+j);
337                     } else if (parameter == Long.TYPE) {
338                         codevisitor.visitVarInsn(LLOAD, 1+j);
339                     } else {
340                         codevisitor.visitVarInsn(ILOAD, 1+j);
341                     }
342                 }
343
344                 // invoke method on version field
345
codevisitor.visitMethodInsn(INVOKEINTERFACE,
346                         Type.getDescriptor(Versionable.class),
347                         name,
348                         desc);
349
350                 // return result to caller
351
Class JavaDoc returnType = method.getReturnType();
352                 if (returnType == Void.TYPE) {
353                     codevisitor.visitInsn(RETURN);
354                 } else if (returnType.isPrimitive()) {
355                     if ( returnType == Double.TYPE) {
356                         codevisitor.visitInsn(DRETURN);
357                     } else if ( returnType == Float.TYPE) {
358                         codevisitor.visitInsn(FRETURN);
359                     } else if ( returnType == Long.TYPE ) {
360                         codevisitor.visitInsn(LRETURN);
361                     } else {
362                         codevisitor.visitInsn(IRETURN);
363                     }
364                 } else {
365                     codevisitor.visitInsn(ARETURN);
366                 }
367
368                 codevisitor.visitMaxs(1 + parameters.length, 1 + parameters.length);
369             }
370         }
371     }
372
373     /**
374      * Adapter to change the bytecode with ASM.
375      */

376     private class SVOCodeAdapter extends CodeAdapter implements Constants {
377         private SVOClassAdapter ca;
378         private boolean isConstructor;
379         private boolean isInitialized;
380         private String JavaDoc methodName;
381
382         public SVOCodeAdapter(String JavaDoc methodName, CodeVisitor cv, SVOClassAdapter ca) {
383             super(cv);
384             this.ca =ca;
385             this.methodName = methodName;
386             isConstructor = methodName.startsWith("<init>");
387             isInitialized = false;
388         }
389
390         public void visitFieldInsn(int i, String JavaDoc s, String JavaDoc s1, String JavaDoc s2) {
391             if (i == PUTFIELD) {
392                 log.debug("visitFieldInsn(" + i + "," + s + "," + s1 + "," + s2 + ")");
393                 ca.addMethod(new MyMethod(methodName(s1), s1, s2));
394                 log.debug("visitMethodInsn(INVOKEVIRTUAL, " + className + "," + methodName(s1) + ", (" + s2 + ")V" + ")");
395                 cv.visitMethodInsn(INVOKEVIRTUAL, className, methodName(s1), "(" + s2 + ")V");
396             } else {
397                 cv.visitFieldInsn(i, s, s1, s2);
398             }
399         }
400
401         public void visitMethodInsn(int i, String JavaDoc s, String JavaDoc s1, String JavaDoc s2) {
402             // initialize the version field as soon as possible
403
// right after invoking the baseclass constructor
404
if (isConstructor && i == INVOKESPECIAL && !isInitialized) {
405                 cv.visitMethodInsn(i, s, s1, s2);
406                 initVersionable();
407             } else {
408                 super.visitMethodInsn(i, s, s1, s2);
409             }
410         }
411
412         private String JavaDoc methodName(String JavaDoc field) {
413             return "write_" + field;
414         }
415
416         private void initVersionable() {
417             String JavaDoc versionDesc = Type.getDescriptor(Version.class);
418             String JavaDoc versionableDesc = Type.getDescriptor(Versionable.class);
419             String JavaDoc ownerDesc = ca.getInternalName();
420
421             cv.visitVarInsn(ALOAD, 0);
422             cv.visitTypeInsn(NEW, versionDesc);
423             cv.visitInsn(DUP);
424             cv.visitMethodInsn(INVOKESPECIAL, versionDesc, "<init>", "()V");
425             cv.visitFieldInsn(PUTFIELD,
426                     ownerDesc,
427                     VERSIONFIELD,
428                     versionableDesc);
429             isInitialized = true;
430         }
431     }
432
433     /**
434      * Representation of a method.
435      */

436     private static class MyMethod {
437         private String JavaDoc name, field, type;
438         public MyMethod(String JavaDoc name, String JavaDoc field, String JavaDoc type) {
439             this.name = name;
440             this.field = field;
441             this.type = type;
442         }
443
444         public String JavaDoc getName() { return name; }
445         public String JavaDoc getType() { return type; }
446         public String JavaDoc getField() { return field; }
447         public String JavaDoc toString() { return "void " + name + "(" + type + " " + field + ")"; }
448
449         public String JavaDoc getTypeInternalName() {
450             if (!type.startsWith("L")) {
451                 return null;
452             }
453             return type.substring(1, type.length()-1);
454         }
455
456         public String JavaDoc getTypeClassName() {
457             String JavaDoc internalName = getTypeInternalName();
458             if (internalName == null)
459                 return null;
460             else
461                 return internalName.replace('/', '.');
462         }
463
464         public boolean hasSmartContainer() {
465             String JavaDoc className = getTypeClassName();
466             if (className == null)
467                 return false;
468             else
469                 return (SMARTCONTAINERS.containsKey(className));
470         }
471
472         public boolean equals(Object JavaDoc o) {
473            if (o == null || ! (o instanceof MyMethod))
474              return false;
475
476            MyMethod m = (MyMethod) o;
477            return (m.getName().equals(this.name) &&
478                 m.getType().equals(this.type) &&
479                 m.getField().equals(this.field));
480         }
481
482         public int hashCode() {
483             return 42 +
484                     this.name.hashCode() +
485                     this.field.hashCode() +
486                     this.type.hashCode();
487         }
488     }
489 }
490
Popular Tags