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