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
55
56
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
67
68
69
70
71
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
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
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
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
121 continue;
122 }
123 } else {
124
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
135 for (Iterator it2 = wrapperMethods.iterator(); it2.hasNext();) {
136 ctClass.addMethod((CtMethod) it2.next());
137 }
138 }
139
140
141
142
143
144 for (Iterator i = sortedMethodTuples.iterator(); i.hasNext();) {
145 MethodSequenceTuple tuple = (MethodSequenceTuple) i.next();
146
147
148
149 if (tuple.getStatus() != STATUS_HAS_NO_POINTCUT) {
150 continue;
151 }
152 CtMethod method = tuple.getMethod();
153
154
155 final String prefixedMethodName = TransformationUtil.getPrefixedOriginalMethodName(method.getName(), tuple
156 .getSequence(), ctClass.getName().replace('/', '.'));
157
158
159 if (JavassistHelper.hasMethod(ctClass, prefixedMethodName)) {
160 CtMethod wrapperMethod = ctClass.getDeclaredMethod(prefixedMethodName);
161 if (JavassistHelper.isAnnotatedNotEmpty(wrapperMethod)) {
162
163
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
175
176
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
222
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
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;
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 }