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.delegation;
9   
10  import gnu.trove.TObjectIntHashMap;
11  import org.codehaus.aspectwerkz.definition.SystemDefinition;
12  import org.codehaus.aspectwerkz.expression.ExpressionContext;
13  import org.codehaus.aspectwerkz.expression.PointcutType;
14  import org.codehaus.aspectwerkz.reflect.ClassInfo;
15  import org.codehaus.aspectwerkz.reflect.MethodInfo;
16  import org.codehaus.aspectwerkz.reflect.impl.javassist.JavassistClassInfo;
17  import org.codehaus.aspectwerkz.reflect.impl.javassist.JavassistMethodInfo;
18  import org.codehaus.aspectwerkz.transform.Context;
19  import org.codehaus.aspectwerkz.transform.TransformationUtil;
20  import org.codehaus.aspectwerkz.transform.Transformer;
21  import org.codehaus.aspectwerkz.transform.TransformationConstants;
22  import org.codehaus.aspectwerkz.transform.TransformationConstants;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import javassist.CannotCompileException;
31  import javassist.CtClass;
32  import javassist.CtMethod;
33  import javassist.CtNewMethod;
34  import javassist.Modifier;
35  import javassist.NotFoundException;
36  
37  /***
38   * Advises method EXECUTION join points.
39   * 
40   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
41   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
42   */
43  public class MethodExecutionTransformer implements Transformer {
44  
45      /***
46       * Makes the member method transformations.
47       * 
48       * @param context the transformation context
49       * @param klass the class set.
50       */
51      public void transform(final Context context, final Klass klass) throws Exception {
52          List definitions = context.getDefinitions();
53  
54          //m_joinPointIndex =
55          // TransformationUtil.getJoinPointIndex(klass.getCtClass()); // TODO not
56          // thread safe
57  
58          final CtClass ctClass = klass.getCtClass();
59          ClassInfo classInfo = JavassistClassInfo.getClassInfo(ctClass, context.getLoader());
60          
61          if (classFilter(definitions, new ExpressionContext(PointcutType.EXECUTION, classInfo, classInfo), ctClass)) {
62              return;
63          }
64          final CtMethod[] methods = ctClass.getDeclaredMethods();
65  
66          // Compute the method sequence number no matter the method is advised to
67          // support multi weaving.
68          // Javassist.getDeclaredMethods() does not always return methods in the
69          // same order so we have
70          // to sort the method list before computation.
71          // @TODO: filter init/clinit/prefixed methods
72          final List sortedMethods = Arrays.asList(methods);
73          Collections.sort(sortedMethods, JavassistMethodComparator.getInstance());
74          final TObjectIntHashMap methodSequences = new TObjectIntHashMap();
75          final List sortedMethodTuples = new ArrayList(sortedMethods.size());
76          for (Iterator methodsIt = sortedMethods.iterator(); methodsIt.hasNext();) {
77              CtMethod method = (CtMethod) methodsIt.next();
78              MethodInfo methodInfo = JavassistMethodInfo.getMethodInfo(method, context.getLoader());
79              int sequence = 1;
80              if (methodSequences.containsKey(method.getName())) {
81                  sequence = methodSequences.get(method.getName());
82                  methodSequences.remove(method.getName());
83                  sequence++;
84              }
85              methodSequences.put(method.getName(), sequence);
86              ExpressionContext ctx = new ExpressionContext(PointcutType.EXECUTION, methodInfo, methodInfo);
87              MethodSequenceTuple tuple = new MethodSequenceTuple(method, sequence);
88              int status = methodFilter(definitions, ctx, method);
89              tuple.setStatus(status);
90  
91              // @TODO filter out "skip" status
92              sortedMethodTuples.add(tuple);
93          }
94          final List wrapperMethods = new ArrayList();
95          boolean isClassAdvised = false;
96          for (Iterator i = sortedMethodTuples.iterator(); i.hasNext();) {
97              MethodSequenceTuple tuple = (MethodSequenceTuple) i.next();
98              if (tuple.getStatus() != STATUS_HAS_POINTCUT) {
99                  continue;
100             }
101             CtMethod method = tuple.getMethod();
102             final int methodSequence = tuple.getSequence();
103             final int methodHash = JavassistHelper.calculateHash(method);
104 
105             // there was no empty method already
106             final String prefixedMethodName = TransformationUtil.getPrefixedOriginalMethodName(
107                 method.getName(),
108                 methodSequence,
109                 ctClass.getName().replace('/', '.'));
110             if (JavassistHelper.hasMethod(ctClass, prefixedMethodName)) {
111                 CtMethod wrapperMethod = ctClass.getDeclaredMethod(prefixedMethodName);
112                 if (JavassistHelper.isAnnotatedEmpty(wrapperMethod)) {
113                     // create the non empty wrapper to access its body
114                     CtMethod nonEmptyWrapper = createWrapperMethod(ctClass, method, methodHash, klass);
115                     wrapperMethod.setBody(method, null);
116                     method.setBody(nonEmptyWrapper, null);
117                     JavassistHelper.setAnnotatedNotEmpty(wrapperMethod);
118                     isClassAdvised = true;
119                 } else {
120                     // multi weaving
121                     continue;
122                 }
123             } else {
124                 // new execution pointcut
125                 CtMethod wrapperMethod = createWrapperMethod(ctClass, method, methodHash, klass);
126                 wrapperMethods.add(wrapperMethod);
127                 addPrefixToMethod(ctClass, method, methodSequence);
128                 isClassAdvised = true;
129             }
130         }
131         if (isClassAdvised) {
132             context.markAsAdvised();
133 
134             // add the wrapper methods
135             for (Iterator it2 = wrapperMethods.iterator(); it2.hasNext();) {
136                 ctClass.addMethod((CtMethod) it2.next());
137             }
138         }
139 
140         // handles pointcut unweaving
141         // looping on the original methods is enough since we will look for
142         // method with no pc
143         // thus that have not been changed in the previous transformation steps
144         for (Iterator i = sortedMethodTuples.iterator(); i.hasNext();) {
145             MethodSequenceTuple tuple = (MethodSequenceTuple) i.next();
146 
147             //System.out.println(" tuple " + tuple.getAdvice().getName() + " :
148             // " + tuple.getStatus());
149             if (tuple.getStatus() != STATUS_HAS_NO_POINTCUT) {
150                 continue;
151             }
152             CtMethod method = tuple.getMethod();
153 
154             //System.out.println("FOUND NO PC = " + method.getName());
155             final String prefixedMethodName = TransformationUtil.getPrefixedOriginalMethodName(method.getName(), tuple
156                     .getSequence(), ctClass.getName().replace('/', '.'));
157 
158             // do we have a wrapper method, which is NOT marked empty
159             if (JavassistHelper.hasMethod(ctClass, prefixedMethodName)) {
160                 CtMethod wrapperMethod = ctClass.getDeclaredMethod(prefixedMethodName);
161                 if (JavassistHelper.isAnnotatedNotEmpty(wrapperMethod)) {
162                     //System.out.println("FOUND A real Wrapper but NO PC = " +
163                     // method.getName());
164                     CtMethod emptyWrapperMethod = JavassistHelper.createEmptyWrapperMethod(ctClass, method, tuple
165                             .getSequence());
166                     method.setBody(wrapperMethod, null);
167                     wrapperMethod.setBody(emptyWrapperMethod, null);
168                     JavassistHelper.setAnnotatedEmpty(wrapperMethod);
169                     context.markAsAdvised();
170                 }
171             }
172         }
173 
174         //}//end of def loop
175         //TransformationUtil.setJoinPointIndex(klass.getCtClass(),
176         // m_joinPointIndex);
177         klass.flushJoinPointIndex();
178     }
179 
180     /***
181      * Creates a wrapper method for the original method specified. This method has the same signature as the original
182      * method and catches the invocation for further processing by the framework before redirecting to the original
183      * method.
184      * 
185      * @param ctClass the ClassGen
186      * @param originalMethod the current method
187      * @param methodHash the method hash
188      * @return the wrapper method
189      */
190     private CtMethod createWrapperMethod(
191         final CtClass ctClass,
192         final CtMethod originalMethod,
193         final int methodHash,
194         final Klass klass) throws NotFoundException, CannotCompileException {
195         StringBuffer body = new StringBuffer();
196         StringBuffer callBody = new StringBuffer();
197         body.append('{');
198         callBody.append(TransformationConstants.JOIN_POINT_MANAGER_FIELD);
199         callBody.append('.');
200         callBody.append(TransformationConstants.PROCEED_WITH_EXECUTION_JOIN_POINT_METHOD);
201         callBody.append('(');
202         callBody.append(methodHash);
203         callBody.append(", ");
204         callBody.append(klass.getJoinPointIndex());
205         callBody.append(", args, ");
206         if (Modifier.isStatic(originalMethod.getModifiers())) {
207             callBody.append("nullObject");
208             body.append("Object nullObject = null;");
209         } else {
210             callBody.append("this");
211         }
212         callBody.append(',');
213         callBody.append(TransformationConstants.JOIN_POINT_TYPE_METHOD_EXECUTION);
214         callBody.append(");");
215         if (originalMethod.getParameterTypes().length > 0) {
216             body.append("Object[] args = $args; ");
217         } else {
218             body.append("Object[] args = null; ");
219         }
220         if (originalMethod.getReturnType() == CtClass.voidType) {
221             // special handling for void return type leads to cleaner bytecode
222             // generation with Javassist
223             body.append(callBody.toString()).append("}");
224         } else if (!originalMethod.getReturnType().isPrimitive()) {
225             body.append("return ($r)");
226             body.append(callBody.toString());
227             body.append("}");
228         } else {
229             String localResult = TransformationConstants.ASPECTWERKZ_PREFIX + "res";
230             body.append("Object ").append(localResult).append(" = ");
231             body.append(callBody.toString());
232             body.append("if (").append(localResult).append(" != null)");
233             body.append("return ($r) ").append(localResult).append("; else ");
234             body.append("return ");
235             body.append(JavassistHelper.getDefaultPrimitiveValue(originalMethod.getReturnType()));
236             body.append("; }");
237         }
238         CtMethod method;
239         if (Modifier.isStatic(originalMethod.getModifiers())) {
240             method = JavassistHelper.makeStatic(
241                 originalMethod.getReturnType(),
242                 originalMethod.getName(),
243                 originalMethod.getParameterTypes(),
244                 originalMethod.getExceptionTypes(),
245                 body.toString(),
246                 ctClass);
247         } else {
248             method = CtNewMethod.make(originalMethod.getReturnType(), originalMethod.getName(), originalMethod
249                     .getParameterTypes(), originalMethod.getExceptionTypes(), body.toString(), ctClass);
250             method.setModifiers(originalMethod.getModifiers());
251         }
252         JavassistHelper.copyCustomAttributes(method, originalMethod);
253         klass.incrementJoinPointIndex();
254         JavassistHelper.setAnnotatedNotEmpty(method);
255         return method;
256     }
257 
258     /***
259      * Adds a prefix to the original method. To make it callable only from within the framework itself.
260      * 
261      * @param cg class gen
262      * @param ctMethod the current method
263      * @param methodSequence the methods sequence number
264      */
265     private void addPrefixToMethod(final CtClass cg, final CtMethod ctMethod, final int methodSequence) {
266         // change the method access flags (should always be set to protected)
267         int accessFlags = ctMethod.getModifiers();
268         String prefixedMethodName = TransformationUtil.getPrefixedOriginalMethodName(ctMethod.getName(), methodSequence, cg
269                 .getName());
270         ctMethod.setName(prefixedMethodName);
271         ctMethod.setModifiers(accessFlags);
272     }
273 
274     /***
275      * Filters the classes to be transformed.
276      * 
277      * @param definitions the definitions
278      * @param ctx the context
279      * @param cg the class to filter
280      * @return boolean true if the method should be filtered away
281      */
282     private boolean classFilter(final List definitions, final ExpressionContext ctx, final CtClass cg) {
283         if (cg.isInterface()) {
284             return true;
285         }
286         for (Iterator defs = definitions.iterator(); defs.hasNext();) {
287             if (classFilter((SystemDefinition) defs.next(), ctx, cg)) {
288                 continue;
289             } else {
290                 return false;
291             }
292         }
293         return true;
294     }
295 
296     /***
297      * Filters the classes to be transformed. <p/>TODO: when a class had execution pointcut that were removed it must be
298      * unweaved, thus not filtered out How to handle that ? cache lookup ? or custom class level attribute ?
299      * 
300      * @param definition the definition
301      * @param ctx the context
302      * @param cg the class to filter
303      * @return boolean true if the method should be filtered away
304      */
305     public static boolean classFilter(final SystemDefinition definition, final ExpressionContext ctx, final CtClass cg) {
306         if (cg.isInterface()) {
307             return true;
308         }
309         String className = cg.getName().replace('/', '.');
310         if (definition.inExcludePackage(className)) {
311             return true;
312         }
313         if (!definition.inIncludePackage(className)) {
314             return true;
315         }
316         if (definition.isAdvised(ctx)) {
317             return false;
318         }
319         if (definition.inPreparePackage(className)) {
320             return false; //no early filtering for prepared Class to allow RuW
321         }
322         return true;
323     }
324 
325     /***
326      * Filters the methods to be transformed.
327      * 
328      * @param definitions
329      * @param ctx
330      * @param method
331      * @return
332      */
333     public static int methodFilter(final List definitions, final ExpressionContext ctx, final CtMethod method) {
334         if (Modifier.isAbstract(method.getModifiers())
335             || Modifier.isNative(method.getModifiers())
336             || method.getName().equals("<init>")
337             || method.getName().equals("<clinit>")
338             || method.getName().startsWith(TransformationConstants.ORIGINAL_METHOD_PREFIX)
339             || method.getName().equals(TransformationConstants.CLASS_LOOKUP_METHOD)) {
340             return STATUS_SKIP;
341         }
342         for (Iterator defs = definitions.iterator(); defs.hasNext();) {
343             if (((SystemDefinition) defs.next()).hasPointcut(ctx)) {
344                 return STATUS_HAS_POINTCUT;
345             } else {
346                 continue;
347             }
348         }
349         return STATUS_HAS_NO_POINTCUT;
350     }
351 }
352 
353 /***
354  * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
355  * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
356  */
357 
358 class MethodSequenceTuple {
359     private CtMethod m_method;
360 
361     private int m_sequence;
362 
363     private int m_status = MethodExecutionTransformer.STATUS_SKIP;
364 
365     public MethodSequenceTuple(CtMethod method, int sequence) {
366         m_method = method;
367         m_sequence = sequence;
368     }
369 
370     public CtMethod getMethod() {
371         return m_method;
372     }
373 
374     public int getSequence() {
375         return m_sequence;
376     }
377 
378     public void setStatus(int status) {
379         m_status = status;
380     }
381 
382     public int getStatus() {
383         return m_status;
384     }
385 
386     public String toString() {
387         return m_method.getName() + " : " + m_status;
388     }
389 }