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.inlining.weaver;
9
10 import java.util.Iterator;
11 import java.util.Set;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.HashMap;
15 import java.util.HashSet;
16
17 import org.objectweb.asm.*;
18 import org.codehaus.aspectwerkz.transform.Context;
19 import org.codehaus.aspectwerkz.transform.TransformationConstants;
20 import org.codehaus.aspectwerkz.transform.TransformationUtil;
21 import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
22 import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
23 import org.codehaus.aspectwerkz.definition.SystemDefinition;
24 import org.codehaus.aspectwerkz.definition.MixinDefinition;
25 import org.codehaus.aspectwerkz.expression.ExpressionContext;
26 import org.codehaus.aspectwerkz.expression.PointcutType;
27 import org.codehaus.aspectwerkz.reflect.ClassInfo;
28 import org.codehaus.aspectwerkz.reflect.MethodInfo;
29 import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
30 import org.codehaus.aspectwerkz.reflect.FieldInfo;
31 import org.codehaus.aspectwerkz.DeploymentModel;
32 import org.codehaus.aspectwerkz.DeploymentModel;
33 import org.codehaus.aspectwerkz.exception.DefinitionException;
34
35 /***
36 * Adds mixin methods and fields to hold mixin instances to the target class.
37 *
38 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
39 */
40 public class AddMixinMethodsVisitor extends ClassAdapter implements TransformationConstants {
41
42 private final ContextImpl m_ctx;
43 private String m_declaringTypeName;
44 private final ClassInfo m_classInfo;
45 private final Set m_addedMethods;
46 private ExpressionContext m_expressionContext;
47 private boolean m_hasClinit = false;
48 private Map m_mixinFields;
49 private boolean m_isAdvised = false;
50
51 /***
52 * Creates a new class adapter.
53 *
54 * @param cv
55 * @param classInfo
56 * @param ctx
57 * @param addedMethods
58 */
59 public AddMixinMethodsVisitor(final ClassVisitor cv,
60 final ClassInfo classInfo,
61 final Context ctx,
62 final Set addedMethods) {
63 super(cv);
64 m_classInfo = classInfo;
65 m_ctx = (ContextImpl) ctx;
66 m_addedMethods = addedMethods;
67 m_expressionContext = new ExpressionContext(PointcutType.WITHIN, m_classInfo, m_classInfo);
68 }
69
70 /***
71 * Visits the class.
72 *
73 * @param access
74 * @param name
75 * @param superName
76 * @param interfaces
77 * @param sourceFile
78 */
79 public void visit(final int version,
80 final int access,
81 final String name,
82 final String superName,
83 final String[] interfaces,
84 final String sourceFile) {
85 ExpressionContext ctx = new ExpressionContext(PointcutType.WITHIN, m_classInfo, m_classInfo);
86 if (!classFilter(m_classInfo, ctx, m_ctx.getDefinitions())) {
87 m_declaringTypeName = name;
88 m_mixinFields = new HashMap();
89
90
91 for (int i = 0; i < m_classInfo.getFields().length; i++) {
92 FieldInfo fieldInfo = m_classInfo.getFields()[i];
93 if (fieldInfo.getName().startsWith(MIXIN_FIELD_NAME)) {
94 m_mixinFields.put(fieldInfo.getType(), fieldInfo);
95 }
96 }
97
98
99 addMixinMembers();
100 }
101 super.visit(version, access, name, superName, interfaces, sourceFile);
102 }
103
104 /***
105 * Adds mixin fields and methods to the target class.
106 */
107 private void addMixinMembers() {
108 int index = 0;
109 for (Iterator it = m_ctx.getDefinitions().iterator(); it.hasNext();) {
110 List mixinDefs = ((SystemDefinition) it.next()).getMixinDefinitions(m_expressionContext);
111
112
113 Set interfaceSet = new HashSet();
114 for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
115 interfaceSet.addAll(((MixinDefinition) it2.next()).getInterfaceClassNames());
116 }
117 if (ClassInfoHelper.hasMethodClash(interfaceSet, m_ctx.getLoader())) {
118 return;
119 }
120
121 for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
122 final MixinDefinition mixinDef = (MixinDefinition) it2.next();
123 final ClassInfo mixinImpl = mixinDef.getMixinImpl();
124 final DeploymentModel deploymentModel = mixinDef.getDeploymentModel();
125
126 if (m_mixinFields.containsKey(mixinImpl)) {
127 continue;
128 }
129 final MixinFieldInfo fieldInfo = new MixinFieldInfo();
130 fieldInfo.fieldName = MIXIN_FIELD_NAME + index;
131 fieldInfo.mixinClassInfo = mixinImpl;
132
133 addMixinField(fieldInfo, deploymentModel, mixinDef);
134 addMixinMethods(fieldInfo, mixinDef);
135
136 index++;
137 m_isAdvised = true;
138 }
139 }
140 }
141
142 /***
143 * Appends mixin instantiation to the clinit method and/or init method.
144 *
145 * @param access
146 * @param name
147 * @param desc
148 * @param exceptions
149 * @param attrs
150 * @return
151 */
152 public CodeVisitor visitMethod(final int access,
153 final String name,
154 final String desc,
155 final String[] exceptions,
156 final Attribute attrs) {
157 if (m_isAdvised) {
158 if (name.equals(CLINIT_METHOD_NAME)) {
159 m_hasClinit = true;
160 CodeVisitor mv = new PrependToClinitMethodCodeAdapter(
161 cv.visitMethod(access, name, desc, exceptions, attrs)
162 );
163 mv.visitMaxs(0, 0);
164 return mv;
165 } else if (name.equals(INIT_METHOD_NAME)) {
166 CodeVisitor mv = new AppendToInitMethodCodeAdapter(
167 cv.visitMethod(access, name, desc, exceptions, attrs)
168 );
169 mv.visitMaxs(0, 0);
170 return mv;
171 }
172 }
173 return super.visitMethod(access, name, desc, exceptions, attrs);
174 }
175
176 /***
177 * Creates a new clinit method and adds mixin instantiation if it does not exist.
178 */
179 public void visitEnd() {
180 if (m_isAdvised && !m_hasClinit) {
181
182 CodeVisitor mv = cv.visitMethod(
183 ACC_STATIC, CLINIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE, null, null
184 );
185 for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
186 MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
187 if (fieldInfo.isStatic) {
188 initializeStaticMixinField(mv, fieldInfo);
189 }
190 }
191
192 mv.visitInsn(RETURN);
193 mv.visitMaxs(0, 0);
194 }
195 super.visitEnd();
196 }
197
198 /***
199 * Initializes a static mixin field.
200 *
201 * @param mv
202 * @param fieldInfo
203 */
204 private void initializeStaticMixinField(final CodeVisitor mv, final MixinFieldInfo fieldInfo) {
205 mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/', '.'));
206 if (fieldInfo.isPerJVM) {
207 mv.visitFieldInsn(
208 GETSTATIC,
209 m_declaringTypeName,
210 TARGET_CLASS_FIELD_NAME,
211 CLASS_CLASS_SIGNATURE
212 );
213 mv.visitMethodInsn(
214 INVOKEVIRTUAL,
215 CLASS_CLASS,
216 GETCLASSLOADER_METHOD_NAME,
217 CLASS_CLASS_GETCLASSLOADER_METHOD_SIGNATURE
218 );
219 mv.visitMethodInsn(
220 INVOKESTATIC,
221 MIXINS_CLASS_NAME,
222 MIXIN_OF_METHOD_NAME,
223 MIXIN_OF_METHOD_PER_JVM_SIGNATURE
224 );
225 } else {
226 mv.visitFieldInsn(
227 GETSTATIC,
228 m_declaringTypeName,
229 TARGET_CLASS_FIELD_NAME,
230 CLASS_CLASS_SIGNATURE
231 );
232 mv.visitMethodInsn(
233 INVOKESTATIC,
234 MIXINS_CLASS_NAME,
235 MIXIN_OF_METHOD_NAME,
236 MIXIN_OF_METHOD_PER_CLASS_SIGNATURE
237 );
238 }
239 mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName().replace('.', '/'));
240 mv.visitFieldInsn(
241 PUTSTATIC,
242 m_declaringTypeName,
243 fieldInfo.fieldName,
244 fieldInfo.mixinClassInfo.getSignature()
245 );
246 }
247
248 /***
249 * Initializes a member mixin field.
250 *
251 * @param mv
252 * @param fieldInfo
253 */
254 private void initializeMemberMixinField(final CodeVisitor mv, final MixinFieldInfo fieldInfo) {
255 mv.visitVarInsn(ALOAD, 0);
256 mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/', '.'));
257 mv.visitVarInsn(ALOAD, 0);
258 mv.visitMethodInsn(
259 INVOKESTATIC,
260 MIXINS_CLASS_NAME,
261 MIXIN_OF_METHOD_NAME,
262 MIXIN_OF_METHOD_PER_INSTANCE_SIGNATURE
263 );
264 mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName().replace('.', '/'));
265 mv.visitFieldInsn(
266 PUTFIELD,
267 m_declaringTypeName,
268 fieldInfo.fieldName,
269 fieldInfo.mixinClassInfo.getSignature()
270 );
271 }
272
273 /***
274 * Adds the mixin field to the target class.
275 *
276 * @param fieldInfo
277 * @param deploymentModel
278 * @param mixinDef
279 */
280 private void addMixinField(final MixinFieldInfo fieldInfo,
281 final DeploymentModel deploymentModel,
282 final MixinDefinition mixinDef) {
283 final String signature = fieldInfo.mixinClassInfo.getSignature();
284 int modifiers = 0;
285 if (deploymentModel.equals(DeploymentModel.PER_CLASS) || deploymentModel.equals(DeploymentModel.PER_JVM)) {
286 fieldInfo.isStatic = true;
287 fieldInfo.isPerJVM = deploymentModel.equals(DeploymentModel.PER_JVM);
288 modifiers = ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC;
289 } else if (deploymentModel.equals(DeploymentModel.PER_INSTANCE)) {
290 fieldInfo.isStatic = false;
291 modifiers = ACC_PRIVATE + ACC_FINAL + ACC_SYNTHETIC;
292 } else {
293 throw new DefinitionException(
294 "deployment model [" + mixinDef.getDeploymentModel() +
295 "] for mixin [" + mixinDef.getMixinImpl().getName() +
296 "] is not supported"
297 );
298
299 }
300 if (mixinDef.isTransient()) {
301 modifiers += ACC_TRANSIENT;
302 }
303 cv.visitField(modifiers, fieldInfo.fieldName, signature, null, null);
304 m_mixinFields.put(mixinDef.getMixinImpl(), fieldInfo);
305 }
306
307 /***
308 * Adds the mixin methods to the target class.
309 *
310 * @param fieldInfo
311 * @param mixinDef
312 */
313 private void addMixinMethods(final MixinFieldInfo fieldInfo, final MixinDefinition mixinDef) {
314 for (Iterator it3 = mixinDef.getMethodsToIntroduce().iterator(); it3.hasNext();) {
315 MethodInfo methodInfo = (MethodInfo) it3.next();
316 final String methodName = methodInfo.getName();
317 final String methodSignature = methodInfo.getSignature();
318
319 if (m_addedMethods.contains(AlreadyAddedMethodVisitor.getMethodKey(methodName, methodSignature))) {
320 continue;
321 }
322
323 CodeVisitor mv = cv.visitMethod(
324 ACC_PUBLIC + ACC_SYNTHETIC,
325 methodName,
326 methodSignature,
327 null,
328 null
329 );
330 if (fieldInfo.isStatic) {
331 mv.visitFieldInsn(
332 GETSTATIC,
333 m_declaringTypeName,
334 fieldInfo.fieldName,
335 fieldInfo.mixinClassInfo.getSignature()
336 );
337 } else {
338 mv.visitVarInsn(ALOAD, 0);
339 mv.visitFieldInsn(
340 GETFIELD,
341 m_declaringTypeName,
342 fieldInfo.fieldName,
343 fieldInfo.mixinClassInfo.getSignature()
344 );
345 }
346 AsmHelper.loadArgumentTypes(mv, Type.getArgumentTypes(methodSignature), false);
347 mv.visitMethodInsn(
348 INVOKEVIRTUAL,
349 fieldInfo.mixinClassInfo.getName().replace('.', '/'),
350 methodName,
351 methodSignature
352 );
353 AsmHelper.addReturnStatement(mv, Type.getReturnType(methodSignature));
354 mv.visitMaxs(0, 0);
355 }
356 }
357
358 /***
359 * Filters the classes to be transformed.
360 *
361 * @param classInfo the class to filter
362 * @param ctx the context
363 * @param definitions a set with the definitions
364 * @return boolean true if the method should be filtered away
365 */
366 public static boolean classFilter(final ClassInfo classInfo,
367 final ExpressionContext ctx,
368 final Set definitions) {
369 for (Iterator it = definitions.iterator(); it.hasNext();) {
370 SystemDefinition systemDef = (SystemDefinition) it.next();
371 if (classInfo.isInterface()) {
372 return true;
373 }
374 String className = classInfo.getName().replace('/', '.');
375 if (systemDef.inExcludePackage(className)) {
376 return true;
377 }
378 if (!systemDef.inIncludePackage(className)) {
379 return true;
380 }
381 if (systemDef.hasMixin(ctx)) {
382 return false;
383 }
384 }
385 return true;
386 }
387
388 /***
389 * Adds initialization of static mixin fields to the beginning of the clinit method.
390 *
391 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
392 */
393 public class PrependToClinitMethodCodeAdapter extends CodeAdapter {
394
395 public PrependToClinitMethodCodeAdapter(final CodeVisitor ca) {
396 super(ca);
397 for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
398 MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
399 if (fieldInfo.isStatic) {
400 initializeStaticMixinField(ca, fieldInfo);
401 }
402 }
403 }
404 }
405
406 /***
407 * Adds initialization of member mixin fields to end of the init method.
408 *
409 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
410 */
411 public class AppendToInitMethodCodeAdapter extends CodeAdapter {
412
413 public AppendToInitMethodCodeAdapter(final CodeVisitor ca) {
414 super(ca);
415 }
416
417 public void visitInsn(final int opcode) {
418 if (opcode == RETURN) {
419 for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
420 MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
421 if (!fieldInfo.isStatic) {
422 initializeMemberMixinField(cv, fieldInfo);
423 }
424 }
425 }
426 super.visitInsn(opcode);
427 }
428 }
429
430 private static class MixinFieldInfo {
431 private String fieldName;
432 private ClassInfo mixinClassInfo;
433 private boolean isStatic;
434 private boolean isPerJVM = false;
435 }
436 }