001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.transform.inlining.weaver;
008:
009: import java.util.Iterator;
010: import java.util.Set;
011: import java.util.List;
012: import java.util.Map;
013: import java.util.HashMap;
014: import java.util.HashSet;
015:
016: import org.objectweb.asm.*;
017: import org.codehaus.aspectwerkz.transform.Context;
018: import org.codehaus.aspectwerkz.transform.TransformationConstants;
019: import org.codehaus.aspectwerkz.transform.TransformationUtil;
020: import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
021: import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
022: import org.codehaus.aspectwerkz.definition.SystemDefinition;
023: import org.codehaus.aspectwerkz.definition.MixinDefinition;
024: import org.codehaus.aspectwerkz.expression.ExpressionContext;
025: import org.codehaus.aspectwerkz.expression.PointcutType;
026: import org.codehaus.aspectwerkz.reflect.ClassInfo;
027: import org.codehaus.aspectwerkz.reflect.MethodInfo;
028: import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
029: import org.codehaus.aspectwerkz.reflect.FieldInfo;
030: import org.codehaus.aspectwerkz.DeploymentModel;
031: import org.codehaus.aspectwerkz.DeploymentModel;
032: import org.codehaus.aspectwerkz.exception.DefinitionException;
033:
034: /**
035: * Adds mixin methods and fields to hold mixin instances to the target class.
036: *
037: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
038: */
039: public class AddMixinMethodsVisitor extends ClassAdapter implements
040: TransformationConstants {
041:
042: private final ContextImpl m_ctx;
043: private String m_declaringTypeName;
044: private final ClassInfo m_classInfo;
045: private final Set m_addedMethods;
046: private ExpressionContext m_expressionContext;
047: private boolean m_hasClinit = false;
048: private Map m_mixinFields;
049: private boolean m_isAdvised = false;
050:
051: /**
052: * Creates a new class adapter.
053: *
054: * @param cv
055: * @param classInfo
056: * @param ctx
057: * @param addedMethods
058: */
059: public AddMixinMethodsVisitor(final ClassVisitor cv,
060: final ClassInfo classInfo, final Context ctx,
061: final Set addedMethods) {
062: super (cv);
063: m_classInfo = classInfo;
064: m_ctx = (ContextImpl) ctx;
065: m_addedMethods = addedMethods;
066: m_expressionContext = new ExpressionContext(
067: PointcutType.WITHIN, m_classInfo, m_classInfo);
068: }
069:
070: /**
071: * Visits the class.
072: *
073: * @param access
074: * @param name
075: * @param superName
076: * @param interfaces
077: * @param sourceFile
078: */
079: public void visit(final int version, final int access,
080: final String name, final String super Name,
081: final String[] interfaces, final String sourceFile) {
082: ExpressionContext ctx = new ExpressionContext(
083: PointcutType.WITHIN, m_classInfo, m_classInfo);
084: if (!classFilter(m_classInfo, ctx, m_ctx.getDefinitions())) {
085: m_declaringTypeName = name;
086: m_mixinFields = new HashMap();
087:
088: // populate with fields already present for mixins from previous weaving
089: for (int i = 0; i < m_classInfo.getFields().length; i++) {
090: FieldInfo fieldInfo = m_classInfo.getFields()[i];
091: if (fieldInfo.getName().startsWith(MIXIN_FIELD_NAME)) {
092: m_mixinFields.put(fieldInfo.getType(), fieldInfo);
093: }
094: }
095:
096: // add fields and method for (not already there) mixins
097: addMixinMembers();
098: }
099: super .visit(version, access, name, super Name, interfaces,
100: sourceFile);
101: }
102:
103: /**
104: * Adds mixin fields and methods to the target class.
105: */
106: private void addMixinMembers() {
107: int index = 0;
108: for (Iterator it = m_ctx.getDefinitions().iterator(); it
109: .hasNext();) {
110: List mixinDefs = ((SystemDefinition) it.next())
111: .getMixinDefinitions(m_expressionContext);
112:
113: // check for method clashes
114: Set interfaceSet = new HashSet();
115: for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
116: interfaceSet.addAll(((MixinDefinition) it2.next())
117: .getInterfaceClassNames());
118: }
119: if (ClassInfoHelper.hasMethodClash(interfaceSet, m_ctx
120: .getLoader())) {
121: return;
122: }
123:
124: for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
125: final MixinDefinition mixinDef = (MixinDefinition) it2
126: .next();
127: final ClassInfo mixinImpl = mixinDef.getMixinImpl();
128: final DeploymentModel deploymentModel = mixinDef
129: .getDeploymentModel();
130:
131: if (m_mixinFields.containsKey(mixinImpl)) {
132: continue;
133: }
134: final MixinFieldInfo fieldInfo = new MixinFieldInfo();
135: fieldInfo.fieldName = MIXIN_FIELD_NAME + index;
136: fieldInfo.mixinClassInfo = mixinImpl;
137:
138: addMixinField(fieldInfo, deploymentModel, mixinDef);
139: addMixinMethods(fieldInfo, mixinDef);
140:
141: index++;
142: m_isAdvised = true;
143: }
144: }
145: }
146:
147: /**
148: * Appends mixin instantiation to the clinit method and/or init method.
149: *
150: * @param access
151: * @param name
152: * @param desc
153: * @param exceptions
154: * @param attrs
155: * @return
156: */
157: public CodeVisitor visitMethod(final int access, final String name,
158: final String desc, final String[] exceptions,
159: final Attribute attrs) {
160: if (m_isAdvised) {
161: if (name.equals(CLINIT_METHOD_NAME)) {
162: m_hasClinit = true;
163: CodeVisitor mv = new PrependToClinitMethodCodeAdapter(
164: cv.visitMethod(access, name, desc, exceptions,
165: attrs));
166: mv.visitMaxs(0, 0);
167: return mv;
168: } else if (name.equals(INIT_METHOD_NAME)) {
169: CodeVisitor mv = new AppendToInitMethodCodeAdapter(cv
170: .visitMethod(access, name, desc, exceptions,
171: attrs));
172: mv.visitMaxs(0, 0);
173: return mv;
174: }
175: }
176: return super .visitMethod(access, name, desc, exceptions, attrs);
177: }
178:
179: /**
180: * Creates a new clinit method and adds mixin instantiation if it does not exist.
181: */
182: public void visitEnd() {
183: if (m_isAdvised && !m_hasClinit) {
184: // add the <clinit> method
185: CodeVisitor mv = cv.visitMethod(ACC_STATIC,
186: CLINIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE,
187: null, null);
188: for (Iterator i4 = m_mixinFields.values().iterator(); i4
189: .hasNext();) {
190: MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
191: if (fieldInfo.isStatic) {
192: initializeStaticMixinField(mv, fieldInfo);
193: }
194: }
195:
196: mv.visitInsn(RETURN);
197: mv.visitMaxs(0, 0);
198: }
199: super .visitEnd();
200: }
201:
202: /**
203: * Initializes a static mixin field.
204: *
205: * @param mv
206: * @param fieldInfo
207: */
208: private void initializeStaticMixinField(final CodeVisitor mv,
209: final MixinFieldInfo fieldInfo) {
210: mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/',
211: '.'));
212: if (fieldInfo.isPerJVM) {
213: mv.visitFieldInsn(GETSTATIC, m_declaringTypeName,
214: TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
215: mv.visitMethodInsn(INVOKEVIRTUAL, CLASS_CLASS,
216: GETCLASSLOADER_METHOD_NAME,
217: CLASS_CLASS_GETCLASSLOADER_METHOD_SIGNATURE);
218: mv.visitMethodInsn(INVOKESTATIC, MIXINS_CLASS_NAME,
219: MIXIN_OF_METHOD_NAME,
220: MIXIN_OF_METHOD_PER_JVM_SIGNATURE);
221: } else {
222: mv.visitFieldInsn(GETSTATIC, m_declaringTypeName,
223: TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
224: mv.visitMethodInsn(INVOKESTATIC, MIXINS_CLASS_NAME,
225: MIXIN_OF_METHOD_NAME,
226: MIXIN_OF_METHOD_PER_CLASS_SIGNATURE);
227: }
228: mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName()
229: .replace('.', '/'));
230: mv.visitFieldInsn(PUTSTATIC, m_declaringTypeName,
231: fieldInfo.fieldName, fieldInfo.mixinClassInfo
232: .getSignature());
233: }
234:
235: /**
236: * Initializes a member mixin field.
237: *
238: * @param mv
239: * @param fieldInfo
240: */
241: private void initializeMemberMixinField(final CodeVisitor mv,
242: final MixinFieldInfo fieldInfo) {
243: mv.visitVarInsn(ALOAD, 0);
244: mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/',
245: '.'));
246: mv.visitVarInsn(ALOAD, 0);
247: mv.visitMethodInsn(INVOKESTATIC, MIXINS_CLASS_NAME,
248: MIXIN_OF_METHOD_NAME,
249: MIXIN_OF_METHOD_PER_INSTANCE_SIGNATURE);
250: mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName()
251: .replace('.', '/'));
252: mv.visitFieldInsn(PUTFIELD, m_declaringTypeName,
253: fieldInfo.fieldName, fieldInfo.mixinClassInfo
254: .getSignature());
255: }
256:
257: /**
258: * Adds the mixin field to the target class.
259: *
260: * @param fieldInfo
261: * @param deploymentModel
262: * @param mixinDef
263: */
264: private void addMixinField(final MixinFieldInfo fieldInfo,
265: final DeploymentModel deploymentModel,
266: final MixinDefinition mixinDef) {
267: final String signature = fieldInfo.mixinClassInfo
268: .getSignature();
269: int modifiers = 0;
270: if (deploymentModel.equals(DeploymentModel.PER_CLASS)
271: || deploymentModel.equals(DeploymentModel.PER_JVM)) {
272: fieldInfo.isStatic = true;
273: fieldInfo.isPerJVM = deploymentModel
274: .equals(DeploymentModel.PER_JVM);
275: modifiers = ACC_PRIVATE + ACC_FINAL + ACC_STATIC
276: + ACC_SYNTHETIC;
277: } else if (deploymentModel.equals(DeploymentModel.PER_INSTANCE)) {
278: fieldInfo.isStatic = false;
279: modifiers = ACC_PRIVATE + ACC_FINAL + ACC_SYNTHETIC;
280: } else {
281: throw new DefinitionException("deployment model ["
282: + mixinDef.getDeploymentModel() + "] for mixin ["
283: + mixinDef.getMixinImpl().getName()
284: + "] is not supported");
285:
286: }
287: if (mixinDef.isTransient()) {
288: modifiers += ACC_TRANSIENT;
289: }
290: cv.visitField(modifiers, fieldInfo.fieldName, signature, null,
291: null);
292: m_mixinFields.put(mixinDef.getMixinImpl(), fieldInfo);
293: }
294:
295: /**
296: * Adds the mixin methods to the target class.
297: *
298: * @param fieldInfo
299: * @param mixinDef
300: */
301: private void addMixinMethods(final MixinFieldInfo fieldInfo,
302: final MixinDefinition mixinDef) {
303: for (Iterator it3 = mixinDef.getMethodsToIntroduce().iterator(); it3
304: .hasNext();) {
305: MethodInfo methodInfo = (MethodInfo) it3.next();
306: final String methodName = methodInfo.getName();
307: final String methodSignature = methodInfo.getSignature();
308:
309: if (m_addedMethods.contains(AlreadyAddedMethodVisitor
310: .getMethodKey(methodName, methodSignature))) {
311: continue;
312: }
313:
314: CodeVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC,
315: methodName, methodSignature, null, null);
316: if (fieldInfo.isStatic) {
317: mv.visitFieldInsn(GETSTATIC, m_declaringTypeName,
318: fieldInfo.fieldName, fieldInfo.mixinClassInfo
319: .getSignature());
320: } else {
321: mv.visitVarInsn(ALOAD, 0);
322: mv.visitFieldInsn(GETFIELD, m_declaringTypeName,
323: fieldInfo.fieldName, fieldInfo.mixinClassInfo
324: .getSignature());
325: }
326: AsmHelper.loadArgumentTypes(mv, Type
327: .getArgumentTypes(methodSignature), false);
328: mv.visitMethodInsn(INVOKEVIRTUAL, fieldInfo.mixinClassInfo
329: .getName().replace('.', '/'), methodName,
330: methodSignature);
331: AsmHelper.addReturnStatement(mv, Type
332: .getReturnType(methodSignature));
333: mv.visitMaxs(0, 0);
334: }
335: }
336:
337: /**
338: * Filters the classes to be transformed.
339: *
340: * @param classInfo the class to filter
341: * @param ctx the context
342: * @param definitions a set with the definitions
343: * @return boolean true if the method should be filtered away
344: */
345: public static boolean classFilter(final ClassInfo classInfo,
346: final ExpressionContext ctx, final Set definitions) {
347: for (Iterator it = definitions.iterator(); it.hasNext();) {
348: SystemDefinition systemDef = (SystemDefinition) it.next();
349: if (classInfo.isInterface()) {
350: return true;
351: }
352: String className = classInfo.getName().replace('/', '.');
353: if (systemDef.inExcludePackage(className)) {
354: return true;
355: }
356: if (!systemDef.inIncludePackage(className)) {
357: return true;
358: }
359: if (systemDef.hasMixin(ctx)) {
360: return false;
361: }
362: }
363: return true;
364: }
365:
366: /**
367: * Adds initialization of static mixin fields to the beginning of the clinit method.
368: *
369: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
370: */
371: public class PrependToClinitMethodCodeAdapter extends CodeAdapter {
372:
373: public PrependToClinitMethodCodeAdapter(final CodeVisitor ca) {
374: super (ca);
375: for (Iterator i4 = m_mixinFields.values().iterator(); i4
376: .hasNext();) {
377: MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
378: if (fieldInfo.isStatic) {
379: initializeStaticMixinField(ca, fieldInfo);
380: }
381: }
382: }
383: }
384:
385: /**
386: * Adds initialization of member mixin fields to end of the init method.
387: *
388: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
389: */
390: public class AppendToInitMethodCodeAdapter extends CodeAdapter {
391:
392: public AppendToInitMethodCodeAdapter(final CodeVisitor ca) {
393: super (ca);
394: }
395:
396: public void visitInsn(final int opcode) {
397: if (opcode == RETURN) {
398: for (Iterator i4 = m_mixinFields.values().iterator(); i4
399: .hasNext();) {
400: MixinFieldInfo fieldInfo = (MixinFieldInfo) i4
401: .next();
402: if (!fieldInfo.isStatic) {
403: initializeMemberMixinField(cv, fieldInfo);
404: }
405: }
406: }
407: super .visitInsn(opcode);
408: }
409: }
410:
411: private static class MixinFieldInfo {
412: private String fieldName;
413: private ClassInfo mixinClassInfo;
414: private boolean isStatic;
415: private boolean isPerJVM = false;
416: }
417: }
|