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.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              // populate with fields already present for mixins from previous weaving
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              // add fields and method for (not already there) mixins
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             // check for method clashes
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             // add the <clinit> method
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 }