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 8of 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;
008:
009: import gnu.trove.TLongObjectHashMap;
010:
011: import java.util.ArrayList;
012: import java.util.HashSet;
013: import java.util.Iterator;
014: import java.util.List;
015: import java.util.Set;
016:
017: import org.codehaus.aspectwerkz.definition.SystemDefinition;
018: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
019: import org.codehaus.aspectwerkz.expression.ExpressionContext;
020: import org.codehaus.aspectwerkz.expression.PointcutType;
021: import org.codehaus.aspectwerkz.reflect.ClassInfo;
022: import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
023: import org.codehaus.aspectwerkz.transform.Context;
024: import org.codehaus.aspectwerkz.transform.WeavingStrategy;
025: import org.codehaus.aspectwerkz.transform.inlining.weaver.AddInterfaceVisitor;
026: import org.codehaus.aspectwerkz.transform.inlining.weaver.AddMixinMethodsVisitor;
027: import org.codehaus.aspectwerkz.transform.inlining.weaver.AlreadyAddedMethodAdapter;
028: import org.codehaus.aspectwerkz.transform.inlining.weaver.ConstructorBodyVisitor;
029: import org.codehaus.aspectwerkz.transform.inlining.weaver.ConstructorCallVisitor;
030: import org.codehaus.aspectwerkz.transform.inlining.weaver.FieldSetFieldGetVisitor;
031: import org.codehaus.aspectwerkz.transform.inlining.weaver.HandlerVisitor;
032: import org.codehaus.aspectwerkz.transform.inlining.weaver.InstanceLevelAspectVisitor;
033: import org.codehaus.aspectwerkz.transform.inlining.weaver.JoinPointInitVisitor;
034: import org.codehaus.aspectwerkz.transform.inlining.weaver.LabelToLineNumberVisitor;
035: import org.codehaus.aspectwerkz.transform.inlining.weaver.MethodCallVisitor;
036: import org.codehaus.aspectwerkz.transform.inlining.weaver.MethodExecutionVisitor;
037: import org.codehaus.aspectwerkz.transform.inlining.weaver.StaticInitializationVisitor;
038: import org.codehaus.aspectwerkz.transform.inlining.weaver.SerialVersionUidVisitor;
039: import org.codehaus.aspectwerkz.transform.inlining.weaver.AddWrapperVisitor;
040: import org.objectweb.asm.ClassReader;
041: import org.objectweb.asm.ClassVisitor;
042: import org.objectweb.asm.ClassWriter;
043: import org.objectweb.asm.attrs.Attributes;
044:
045: /**
046: * A weaving strategy implementing a weaving scheme based on statical compilation, and no reflection.
047: *
048: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
049: * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
050: */
051: public class InliningWeavingStrategy implements WeavingStrategy {
052:
053: /**
054: * Performs the weaving of the target class.
055: *
056: * @param className
057: * @param context
058: */
059: public void transform(String className, final Context context) {
060: try {
061: final byte[] bytecode = context.getInitialBytecode();
062: final ClassLoader loader = context.getLoader();
063:
064: ClassInfo classInfo = AsmClassInfo.getClassInfo(bytecode,
065: loader);
066:
067: // skip Java reflect proxies for which we cannot get the resource as a stream
068: // which leads to warnings when using annotation matching
069: // Note: we use an heuristic assuming JDK proxy are classes named "$..."
070: // to avoid to call getSuperClass everytime
071: if (classInfo.getName().startsWith("$")
072: && classInfo.getSuperclass().getName().equals(
073: "java.lang.reflect.Proxy")) {
074: context
075: .setCurrentBytecode(context
076: .getInitialBytecode());
077: return;
078: }
079:
080: //TODO:FIXME match on (within, null, classInfo) should be equivalent to those ones.
081: final Set definitions = context.getDefinitions();
082: final ExpressionContext[] ctxs = new ExpressionContext[] {
083: new ExpressionContext(PointcutType.EXECUTION,
084: classInfo, classInfo),
085: new ExpressionContext(PointcutType.CALL, null,
086: classInfo),
087: new ExpressionContext(PointcutType.GET, null,
088: classInfo),
089: new ExpressionContext(PointcutType.SET, null,
090: classInfo),
091: new ExpressionContext(PointcutType.HANDLER, null,
092: classInfo),
093: new ExpressionContext(
094: PointcutType.STATIC_INITIALIZATION,
095: classInfo, classInfo),
096: new ExpressionContext(PointcutType.WITHIN,
097: classInfo, classInfo) };
098:
099: if (classFilter(definitions, ctxs, classInfo)) {
100: return;
101: }
102:
103: // build the ClassInfo from the bytecode to avoid loading it from the loader resource stream later
104: // to support stub weaving
105: //AsmClassInfo.getClassInfo(bytecode, loader);
106:
107: // compute CALL + GET/SET early matching results to avoid registering useless visitors
108: final boolean filterForCall = classFilterFor(definitions,
109: new ExpressionContext[] {
110: new ExpressionContext(PointcutType.CALL,
111: null, classInfo),
112: new ExpressionContext(PointcutType.WITHIN,
113: classInfo, classInfo) });//FIXME - within make match all
114: final boolean filterForGetSet = classFilterFor(definitions,
115: new ExpressionContext[] {
116: new ExpressionContext(PointcutType.GET,
117: null, classInfo),
118: new ExpressionContext(PointcutType.SET,
119: null, classInfo),
120: new ExpressionContext(PointcutType.WITHIN,
121: classInfo, classInfo) });//FIXME - within make match all
122: final boolean filterForHandler = classFilterFor(
123: definitions, new ExpressionContext[] {
124: new ExpressionContext(PointcutType.HANDLER,
125: null, classInfo),
126: new ExpressionContext(PointcutType.WITHIN,
127: classInfo, classInfo) });//FIXME - within make match all
128:
129: // note: for staticinitialization we do an exact match right there
130: boolean filterForStaticinitialization = !classInfo
131: .hasStaticInitializer()
132: || classFilterFor(
133: definitions,
134: new ExpressionContext[] { new ExpressionContext(
135: PointcutType.STATIC_INITIALIZATION,
136: classInfo.staticInitializer(),
137: classInfo) });
138: if (!filterForStaticinitialization) {
139: filterForStaticinitialization = !hasPointcut(
140: definitions, new ExpressionContext(
141: PointcutType.STATIC_INITIALIZATION,
142: classInfo.staticInitializer(),
143: classInfo));
144: }
145:
146: // prepare ctor call jp
147: final ClassReader crLookahead = new ClassReader(bytecode);
148: TLongObjectHashMap newInvocationsByCallerMemberHash = null;
149: if (!filterForCall) {
150: newInvocationsByCallerMemberHash = new TLongObjectHashMap();
151: crLookahead
152: .accept(
153: new ConstructorCallVisitor.LookaheadNewDupInvokeSpecialInstructionClassAdapter(
154: newInvocationsByCallerMemberHash),
155: true);
156: }
157:
158: // prepare handler jp, by gathering ALL catch blocks and their exception type
159: List catchLabels = new ArrayList();
160: if (!filterForHandler) {
161: final ClassReader crLookahead2 = new ClassReader(
162: bytecode);
163: final ClassWriter cw2 = AsmHelper.newClassWriter(true);
164:
165: HandlerVisitor.LookaheadCatchLabelsClassAdapter lookForCatches = new HandlerVisitor.LookaheadCatchLabelsClassAdapter(
166: cw2, loader, classInfo, context, catchLabels);
167: // we must visit exactly as we will do further on with debug info (that produces extra labels)
168: crLookahead2.accept(lookForCatches, Attributes
169: .getDefaultAttributes(), false);
170: }
171:
172: // gather wrapper methods to support multi-weaving
173: // skip annotations visit and debug info by using the lookahead read-only classreader
174: Set addedMethods = new HashSet();
175: crLookahead.accept(new AlreadyAddedMethodAdapter(
176: addedMethods), true);
177:
178: // -- Phase 1 -- type change
179: final ClassWriter writerPhase1 = AsmHelper
180: .newClassWriter(true);
181: final ClassReader readerPhase1 = new ClassReader(bytecode);
182: ClassVisitor reversedChainPhase1 = writerPhase1;
183: reversedChainPhase1 = new AddMixinMethodsVisitor(
184: reversedChainPhase1, classInfo, context,
185: addedMethods);
186: reversedChainPhase1 = new AddInterfaceVisitor(
187: reversedChainPhase1, classInfo, context);
188: readerPhase1.accept(reversedChainPhase1, Attributes
189: .getDefaultAttributes(), false);
190: final byte[] bytesPhase1 = writerPhase1.toByteArray();
191:
192: // update the class info
193: classInfo = AsmClassInfo.newClassInfo(bytesPhase1, loader);
194:
195: // -- Phase 2 -- advices
196: final ClassWriter writerPhase2 = AsmHelper
197: .newClassWriter(true);
198: final ClassReader readerPhase2 = new ClassReader(
199: bytesPhase1);
200: ClassVisitor reversedChainPhase2 = writerPhase2;
201: reversedChainPhase2 = new InstanceLevelAspectVisitor(
202: reversedChainPhase2, classInfo, context);
203: reversedChainPhase2 = new MethodExecutionVisitor(
204: reversedChainPhase2, classInfo, context,
205: addedMethods);
206: reversedChainPhase2 = new ConstructorBodyVisitor(
207: reversedChainPhase2, classInfo, context,
208: addedMethods);
209: if (!filterForStaticinitialization) {
210: reversedChainPhase2 = new StaticInitializationVisitor(
211: reversedChainPhase2, context, addedMethods);
212: }
213: reversedChainPhase2 = new HandlerVisitor(
214: reversedChainPhase2, context, catchLabels);
215: if (!filterForCall) {
216: reversedChainPhase2 = new MethodCallVisitor(
217: reversedChainPhase2, loader, classInfo, context);
218: reversedChainPhase2 = new ConstructorCallVisitor(
219: reversedChainPhase2, loader, classInfo,
220: context, newInvocationsByCallerMemberHash);
221: }
222: if (!filterForGetSet) {
223: reversedChainPhase2 = new FieldSetFieldGetVisitor(
224: reversedChainPhase2, loader, classInfo, context);
225: }
226: reversedChainPhase2 = new LabelToLineNumberVisitor(
227: reversedChainPhase2, context);
228: readerPhase2.accept(reversedChainPhase2, Attributes
229: .getDefaultAttributes(), false);
230: final byte[] bytesPhase2 = writerPhase2.toByteArray();
231:
232: context.setCurrentBytecode(bytesPhase2);
233:
234: // -- Phase 3 -- serialUID and JoinPoint initialization
235: if (context.isAdvised()) {
236: final ClassWriter writerPhase3 = AsmHelper
237: .newClassWriter(true);
238: ClassReader readerPhase3 = new ClassReader(bytesPhase2);
239: ClassVisitor reversedChainPhase3 = writerPhase3;
240: reversedChainPhase3 = new SerialVersionUidVisitor.Add(
241: reversedChainPhase3, context, classInfo);
242: reversedChainPhase3 = new AddWrapperVisitor(
243: reversedChainPhase3, context, addedMethods);
244: reversedChainPhase3 = new JoinPointInitVisitor(
245: reversedChainPhase3, context);
246: readerPhase3.accept(reversedChainPhase3, Attributes
247: .getDefaultAttributes(), false);
248: final byte[] bytesPhase3 = writerPhase3.toByteArray();
249:
250: context.setCurrentBytecode(bytesPhase3);
251: }
252:
253: // TODO: INNER CLASS OR NOT?
254: // loop over emitted jp and flag them as inner classes
255: // for (Iterator iterator = ((ContextImpl) context).getEmittedInlinedJoinPoint().iterator(); iterator.hasNext();) {
256: // String joinPointClassName = ((ContextImpl.EmittedJoinPoint) iterator.next()).joinPointClassName;
257: // int innerIndex = joinPointClassName.lastIndexOf('$');
258: // cw.visitInnerClass(joinPointClassName,
259: // joinPointClassName.substring(0, innerIndex),
260: // joinPointClassName.substring(innerIndex + 1, joinPointClassName.length()),
261: // Constants.ACC_PUBLIC + Constants.ACC_STATIC);
262: // }
263:
264: // // resolve line numbers - debug only
265: // List ejp = ((ContextImpl)context).getEmittedJoinPoints();
266: // for (Iterator iterator = ejp.iterator(); iterator.hasNext();) {
267: // EmittedJoinPoint emittedJoinPoint = (EmittedJoinPoint) iterator.next();
268: // emittedJoinPoint.resolveLineNumber(context);
269: // System.out.println(emittedJoinPoint.toString());
270: // }
271:
272: // NOTE: remove when in release time or in debugging trouble (;-) - Alex)
273: // FAKE multiweaving - which is a requirement
274: // Object multi = context.getMetaData("FAKE");
275: // if (multi == null) {
276: // context.addMetaData("FAKE", "FAKE");
277: // transform(className, context);
278: // }
279:
280: } catch (Throwable t) {
281: t.printStackTrace();
282: throw new WrappedRuntimeException(t);
283: }
284: }
285:
286: /**
287: * Creates a new transformation context.
288: *
289: * @param name
290: * @param bytecode
291: * @param loader
292: * @return
293: */
294: public Context newContext(final String name, final byte[] bytecode,
295: final ClassLoader loader) {
296: return new ContextImpl(name, bytecode, loader);
297: }
298:
299: /**
300: * Filters out the classes that are not eligible for transformation.
301: *
302: * @param definitions the definitions
303: * @param ctxs an array with the contexts
304: * @param classInfo the class to filter
305: * @return boolean true if the class should be filtered out
306: */
307: private static boolean classFilter(final Set definitions,
308: final ExpressionContext[] ctxs, final ClassInfo classInfo) {
309: if (classInfo.isInterface()) {
310: return true;
311: }
312: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
313: if (classFilter((SystemDefinition) defs.next(), ctxs,
314: classInfo)) {
315: continue;
316: } else {
317: return false;
318: }
319: }
320: return true;
321: }
322:
323: /**
324: * Filters out the classes that are not eligible for transformation.
325: *
326: * @param definition the definition
327: * @param ctxs an array with the contexts
328: * @param classInfo the class to filter
329: * @return boolean true if the class should be filtered out
330: * @TODO: when a class had execution pointcut that were removed it must be unweaved, thus not filtered out How to
331: * handle that? cache lookup? or custom class level attribute ?
332: */
333: private static boolean classFilter(
334: final SystemDefinition definition,
335: final ExpressionContext[] ctxs, final ClassInfo classInfo) {
336: if (classInfo.isInterface()) {
337: return true;
338: }
339: String className = classInfo.getName();
340: if (definition.inExcludePackage(className)) {
341: return true;
342: }
343: if (!definition.inIncludePackage(className)) {
344: return true;
345: }
346: if (definition.isAdvised(ctxs)) {
347: return false;
348: }
349: if (definition.hasMixin(ctxs)) {
350: return false;
351: }
352: if (definition.hasIntroducedInterface(ctxs)) {
353: return false;
354: }
355: if (definition.inPreparePackage(className)) {
356: return false;
357: }
358: return true;
359: }
360:
361: private static boolean classFilterFor(final Set definitions,
362: final ExpressionContext[] ctxs) {
363: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
364: if (classFilterFor((SystemDefinition) defs.next(), ctxs)) {
365: continue;
366: } else {
367: return false;
368: }
369: }
370: return true;
371: }
372:
373: private static boolean classFilterFor(
374: final SystemDefinition definition,
375: final ExpressionContext[] ctxs) {
376: if (definition.isAdvised(ctxs)) {
377: return false;
378: }
379: return true;
380: }
381:
382: private static boolean hasPointcut(final Set definitions,
383: final ExpressionContext ctx) {
384: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
385: if (hasPointcut((SystemDefinition) defs.next(), ctx)) {
386: return true;
387: } else {
388: continue;
389: }
390: }
391: return false;
392: }
393:
394: private static boolean hasPointcut(
395: final SystemDefinition definition,
396: final ExpressionContext ctx) {
397: if (definition.hasPointcut(ctx)) {
398: return true;
399: }
400: return false;
401: }
402: }
|