Copyright (c) 2005 Contributors. All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution and is available at http://eclipse.org/legal/epl-v10.html Contributors: Alexandre Vasseur initial implementation
/******************************************************************************* * Copyright (c) 2005 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v1.0 * which accompanies this distribution and is available at * http://eclipse.org/legal/epl-v10.html * * Contributors: * Alexandre Vasseur initial implementation *******************************************************************************/
package org.aspectj.weaver.loadtime; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.aspectj.apache.bcel.Constants; import org.aspectj.apache.bcel.classfile.ConstantPool; import org.aspectj.apache.bcel.classfile.JavaClass; import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen; import org.aspectj.apache.bcel.classfile.annotation.ClassElementValue; import org.aspectj.apache.bcel.classfile.annotation.ElementValue; import org.aspectj.apache.bcel.classfile.annotation.NameValuePair; import org.aspectj.apache.bcel.classfile.annotation.SimpleElementValue; import org.aspectj.apache.bcel.generic.FieldGen; import org.aspectj.apache.bcel.generic.InstructionConstants; import org.aspectj.apache.bcel.generic.InstructionFactory; import org.aspectj.apache.bcel.generic.InstructionHandle; import org.aspectj.apache.bcel.generic.InstructionList; import org.aspectj.apache.bcel.generic.LocalVariableTag; import org.aspectj.apache.bcel.generic.ObjectType; import org.aspectj.apache.bcel.generic.Type; import org.aspectj.bridge.IMessage; import org.aspectj.bridge.Message; import org.aspectj.weaver.AjAttribute; import org.aspectj.weaver.AnnotationAJ; import org.aspectj.weaver.GeneratedReferenceTypeDelegate; import org.aspectj.weaver.ReferenceType; import org.aspectj.weaver.ResolvedMember; import org.aspectj.weaver.ResolvedType; import org.aspectj.weaver.UnresolvedType; import org.aspectj.weaver.World; import org.aspectj.weaver.bcel.BcelAnnotation; import org.aspectj.weaver.bcel.BcelPerClauseAspectAdder; import org.aspectj.weaver.bcel.BcelWorld; import org.aspectj.weaver.bcel.LazyClassGen; import org.aspectj.weaver.bcel.LazyMethodGen; import org.aspectj.weaver.loadtime.definition.Definition; import org.aspectj.weaver.loadtime.definition.Definition.AdviceKind; import org.aspectj.weaver.loadtime.definition.Definition.DeclareAnnotationKind; import org.aspectj.weaver.loadtime.definition.Definition.PointcutAndAdvice; import org.aspectj.weaver.patterns.BasicTokenSource; import org.aspectj.weaver.patterns.DeclareAnnotation; import org.aspectj.weaver.patterns.ISignaturePattern; import org.aspectj.weaver.patterns.ITokenSource; import org.aspectj.weaver.patterns.PatternParser; import org.aspectj.weaver.patterns.PerClause; import org.aspectj.weaver.patterns.TypePattern;
Generates bytecode for concrete-aspect.

The concrete aspect is generated annotation style aspect (so traditional Java constructs annotated with our AspectJ annotations). As it is built during aop.xml definitions registration we perform the type munging for perclause, ie. aspectOf() artifact directly, instead of waiting for it to go thru the weaver (that we are in the middle of configuring).

Author:Alexandre Vasseur, Andy Clement
/** * Generates bytecode for concrete-aspect. * <p> * The concrete aspect is generated annotation style aspect (so traditional Java constructs annotated with our AspectJ annotations). * As it is built during aop.xml definitions registration we perform the type munging for perclause, ie. aspectOf() artifact * directly, instead of waiting for it to go thru the weaver (that we are in the middle of configuring). * * @author Alexandre Vasseur * @author Andy Clement */
public class ConcreteAspectCodeGen { private final static String[] EMPTY_STRINGS = new String[0]; private final static Type[] EMPTY_TYPES = new Type[0];
Concrete aspect definition we build for
/** * Concrete aspect definition we build for */
private final Definition.ConcreteAspect concreteAspect;
World for which we build for
/** * World for which we build for */
private final World world;
Set to true when all is checks are verified
/** * Set to true when all is checks are verified */
private boolean isValid = false;
The parent aspect, not concretized
/** * The parent aspect, not concretized */
private ResolvedType parent;
Aspect perClause, used for direct munging of aspectOf artifacts
/** * Aspect perClause, used for direct munging of aspectOf artifacts */
private PerClause perclause;
Bytecode for the generated class
/** * Bytecode for the generated class */
private byte[] bytes;
Create a new generator for a concrete aspect
Params:
  • concreteAspect – the aspect definition
  • world – the related world (for type resolution, etc)
/** * Create a new generator for a concrete aspect * * @param concreteAspect the aspect definition * @param world the related world (for type resolution, etc) */
ConcreteAspectCodeGen(Definition.ConcreteAspect concreteAspect, World world) { this.concreteAspect = concreteAspect; this.world = world; }
Checks that concrete aspect is valid.
Returns:true if ok, false otherwise
/** * Checks that concrete aspect is valid. * * @return true if ok, false otherwise */
public boolean validate() { if (!(world instanceof BcelWorld)) { reportError("Internal error: world must be of type BcelWorld"); return false; } // name must be undefined so far // TODO only convert the name to signature once, probably earlier than this ResolvedType current = world.lookupBySignature(UnresolvedType.forName(concreteAspect.name).getSignature()); if (current != null && !current.isMissing()) { reportError("Attempt to concretize but chosen aspect name already defined: " + stringify()); return false; } if (concreteAspect.pointcutsAndAdvice.size() != 0) { isValid = true; return true; } if (concreteAspect.declareAnnotations.size()!=0) { isValid = true; return true; } // it can happen that extends is null, for precedence only declaration if (concreteAspect.extend == null && concreteAspect.precedence != null) { if (concreteAspect.pointcuts.isEmpty()) { isValid = true; // m_perClause = new PerSingleton(); parent = null; return true;// no need to checks more in that special case } else { reportError("Attempt to use nested pointcuts without extends clause: " + stringify()); return false; } } String parentAspectName = concreteAspect.extend; if (parentAspectName.indexOf("<") != -1) { // yikes, generic parent parent = world.resolve(UnresolvedType.forName(parentAspectName), true); if (parent.isMissing()) { reportError("Unable to resolve type reference: " + stringify()); return false; } if (parent.isParameterizedType()) { UnresolvedType[] typeParameters = parent.getTypeParameters(); for (int i = 0; i < typeParameters.length; i++) { UnresolvedType typeParameter = typeParameters[i]; if (typeParameter instanceof ResolvedType && ((ResolvedType) typeParameter).isMissing()) { reportError("Unablet to resolve type parameter '" + typeParameter.getName() + "' from " + stringify()); return false; } } } } else { parent = world.resolve(concreteAspect.extend, true); } // handle inner classes if (parent.isMissing()) { // fallback on inner class lookup mechanism String fixedName = concreteAspect.extend; int hasDot = fixedName.lastIndexOf('.'); while (hasDot > 0) { char[] fixedNameChars = fixedName.toCharArray(); fixedNameChars[hasDot] = '$'; fixedName = new String(fixedNameChars); hasDot = fixedName.lastIndexOf('.'); parent = world.resolve(UnresolvedType.forName(fixedName), true); if (!parent.isMissing()) { break; } } } if (parent.isMissing()) { reportError("Cannot find parent aspect for: " + stringify()); return false; } // extends must be abstract (allow for special case of Object where just using aspect for deows) if (!(parent.isAbstract() || parent.equals(ResolvedType.OBJECT))) { reportError("Attempt to concretize a non-abstract aspect: " + stringify()); return false; } // m_parent must be aspect (allow for special case of Object where just using aspect for deows) if (!(parent.isAspect() || parent.equals(ResolvedType.OBJECT))) { reportError("Attempt to concretize a non aspect: " + stringify()); return false; } // must have all abstractions defined List<String> elligibleAbstractions = new ArrayList<String>(); Collection<ResolvedMember> abstractMethods = getOutstandingAbstractMethods(parent); for (ResolvedMember method : abstractMethods) { if ("()V".equals(method.getSignature())) { String n = method.getName(); // Allow for the abstract pointcut being from a code style // aspect compiled with -1.5 (see test for 128744) if (n.startsWith("ajc$pointcut")) { n = n.substring(14); n = n.substring(0, n.indexOf("$")); elligibleAbstractions.add(n); } else if (hasPointcutAnnotation(method)) { elligibleAbstractions.add(method.getName()); } else { // error, an outstanding abstract method that can't be // concretized in XML reportError("Abstract method '" + method.toString() + "' cannot be concretized in XML: " + stringify()); return false; } } else { if (method.getName().startsWith("ajc$pointcut") || hasPointcutAnnotation(method)) { // it may be a pointcut but it doesn't meet the requirements for XML concretization reportError("Abstract method '" + method.toString() + "' cannot be concretized as a pointcut (illegal signature, must have no arguments, must return void): " + stringify()); return false; } else { // error, an outstanding abstract method that can't be // concretized in XML reportError("Abstract method '" + method.toString() + "' cannot be concretized in XML: " + stringify()); return false; } } } List<String> pointcutNames = new ArrayList<String>(); for (Definition.Pointcut abstractPc : concreteAspect.pointcuts) { pointcutNames.add(abstractPc.name); } for (String elligiblePc : elligibleAbstractions) { if (!pointcutNames.contains(elligiblePc)) { reportError("Abstract pointcut '" + elligiblePc + "' not configured: " + stringify()); return false; } } if (concreteAspect.perclause != null) { String perclauseString = concreteAspect.perclause; if (perclauseString.startsWith("persingleton")) { } else if (perclauseString.startsWith("percflow")) { } else if (perclauseString.startsWith("pertypewithin")) { } else if (perclauseString.startsWith("perthis")) { } else if (perclauseString.startsWith("pertarget")) { } else if (perclauseString.startsWith("percflowbelow")) { } else { reportError("Unrecognized per clause specified " + stringify()); return false; } } isValid = true; return isValid; } private Collection<ResolvedMember> getOutstandingAbstractMethods(ResolvedType type) { Map<String, ResolvedMember> collector = new HashMap<String, ResolvedMember>(); // let's get to the top of the hierarchy and then walk down ... // recording abstract methods then removing // them if they get defined further down the hierarchy getOutstandingAbstractMethodsHelper(type, collector); return collector.values(); } // We are trying to determine abstract methods left over at the bottom of a // hierarchy that have not been concretized. private void getOutstandingAbstractMethodsHelper(ResolvedType type, Map<String, ResolvedMember> collector) { if (type == null) { return; } // Get to the top if (!type.equals(ResolvedType.OBJECT)) { if (type.getSuperclass() != null) { getOutstandingAbstractMethodsHelper(type.getSuperclass(), collector); } } ResolvedMember[] rms = type.getDeclaredMethods(); if (rms != null) { for (int i = 0; i < rms.length; i++) { ResolvedMember member = rms[i]; String key = member.getName() + member.getSignature(); if (member.isAbstract()) { collector.put(key, member); } else { collector.remove(key); } } } }
Rebuild the XML snip that defines this concrete aspect, for log error purpose
Returns:string repr.
/** * Rebuild the XML snip that defines this concrete aspect, for log error purpose * * @return string repr. */
private String stringify() { StringBuffer sb = new StringBuffer("<concrete-aspect name='"); sb.append(concreteAspect.name); sb.append("' extends='"); sb.append(concreteAspect.extend); sb.append("' perclause='"); sb.append(concreteAspect.perclause); sb.append("'/> in aop.xml"); // TODO needs the extra state from the definition (concretized pointcuts and advice definitions) return sb.toString(); } private boolean hasPointcutAnnotation(ResolvedMember member) { AnnotationAJ[] as = member.getAnnotations(); if (as == null || as.length == 0) { return false; } for (int i = 0; i < as.length; i++) { if (as[i].getTypeSignature().equals("Lorg/aspectj/lang/annotation/Pointcut;")) { return true; } } return false; } public String getClassName() { return concreteAspect.name; }
Returns:the bytecode for the concrete aspect
/** * @return the bytecode for the concrete aspect */
public byte[] getBytes() { if (!isValid) { throw new RuntimeException("Must validate first"); } if (bytes != null) { return bytes; } PerClause.Kind perclauseKind = PerClause.SINGLETON; PerClause parentPerClause = (parent != null ? parent.getPerClause() : null); if (parentPerClause != null) { perclauseKind = parentPerClause.getKind(); } String perclauseString = null; if (concreteAspect.perclause != null) { perclauseString = concreteAspect.perclause; if (perclauseString.startsWith("persingleton")) { perclauseKind = PerClause.SINGLETON; } else if (perclauseString.startsWith("percflow")) { perclauseKind = PerClause.PERCFLOW; } else if (perclauseString.startsWith("pertypewithin")) { perclauseKind = PerClause.PERTYPEWITHIN; } else if (perclauseString.startsWith("perthis")) { perclauseKind = PerClause.PEROBJECT; } else if (perclauseString.startsWith("pertarget")) { perclauseKind = PerClause.PEROBJECT; } else if (perclauseString.startsWith("percflowbelow")) { perclauseKind = PerClause.PERCFLOW; } } // @Aspect //inherit clause from m_parent // @DeclarePrecedence("....") // if any // public class xxxName [extends xxxExtends] { // [@Pointcut(xxxExpression-n) // public void xxxName-n() {}] // } String parentName = "java/lang/Object"; if (parent != null) { if (parent.isParameterizedType()) { parentName = parent.getGenericType().getName().replace('.', '/'); } else { parentName = parent.getName().replace('.', '/'); } } // @Aspect public class ... // TODO AV - we could point to the aop.xml that defines it and use JSR-45 LazyClassGen cg = new LazyClassGen(concreteAspect.name.replace('.', '/'), parentName, null, Modifier.PUBLIC + Constants.ACC_SUPER, EMPTY_STRINGS, world); if (parent != null && parent.isParameterizedType()) { cg.setSuperClass(parent); } if (perclauseString == null) { AnnotationGen ag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Aspect"), Collections.<NameValuePair> emptyList(), true, cg.getConstantPool()); cg.addAnnotation(ag); } else { // List elems = new ArrayList(); List<NameValuePair> elems = new ArrayList<NameValuePair>(); elems.add(new NameValuePair("value", new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), perclauseString), cg.getConstantPool())); AnnotationGen ag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Aspect"), elems, true, cg.getConstantPool()); cg.addAnnotation(ag); } if (concreteAspect.precedence != null) { SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), concreteAspect.precedence); List<NameValuePair> elems = new ArrayList<NameValuePair>(); elems.add(new NameValuePair("value", svg, cg.getConstantPool())); AnnotationGen agprec = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/DeclarePrecedence"), elems, true, cg.getConstantPool()); cg.addAnnotation(agprec); } // default constructor LazyMethodGen init = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, "<init>", EMPTY_TYPES, EMPTY_STRINGS, cg); InstructionList cbody = init.getBody(); cbody.append(InstructionConstants.ALOAD_0); cbody.append(cg.getFactory().createInvoke(parentName, "<init>", Type.VOID, EMPTY_TYPES, Constants.INVOKESPECIAL)); cbody.append(InstructionConstants.RETURN); cg.addMethodGen(init); for (Iterator<Definition.Pointcut> it = concreteAspect.pointcuts.iterator(); it.hasNext();) { Definition.Pointcut abstractPc = (Definition.Pointcut) it.next(); // TODO AV - respect visibility instead of opening up as public? LazyMethodGen mg = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, abstractPc.name, EMPTY_TYPES, EMPTY_STRINGS, cg); SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), abstractPc.expression); List<NameValuePair> elems = new ArrayList<NameValuePair>(); elems.add(new NameValuePair("value", svg, cg.getConstantPool())); AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Pointcut"), elems, true, cg.getConstantPool()); AnnotationAJ max = new BcelAnnotation(mag, world); mg.addAnnotation(max); InstructionList body = mg.getBody(); body.append(InstructionConstants.RETURN); cg.addMethodGen(mg); } // Construct any defined declare error/warnings if (concreteAspect.deows.size() > 0) { int counter = 1; for (Definition.DeclareErrorOrWarning deow : concreteAspect.deows) { // Building this: // @DeclareWarning("call(* javax.sql..*(..)) && !within(org.xyz.daos..*)") // static final String aMessage = "Only DAOs should be calling JDBC."; FieldGen field = new FieldGen(Modifier.FINAL, ObjectType.STRING, "rule" + (counter++), cg.getConstantPool()); SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), deow.pointcut); List<NameValuePair> elems = new ArrayList<NameValuePair>(); elems.add(new NameValuePair("value", svg, cg.getConstantPool())); AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Declare" + (deow.isError ? "Error" : "Warning")), elems, true, cg.getConstantPool()); field.addAnnotation(mag); field.setValue(deow.message); cg.addField(field, null); } } if (concreteAspect.pointcutsAndAdvice.size() > 0) { int adviceCounter = 1; for (PointcutAndAdvice paa : concreteAspect.pointcutsAndAdvice) { generateAdviceMethod(paa, adviceCounter, cg); adviceCounter++; } } if (concreteAspect.declareAnnotations.size()>0) { int decCounter = 1; for (Definition.DeclareAnnotation da: concreteAspect.declareAnnotations) { generateDeclareAnnotation(da,decCounter++,cg); } } // handle the perClause ReferenceType rt = new ReferenceType(ResolvedType.forName(concreteAspect.name).getSignature(), world); GeneratedReferenceTypeDelegate grtd = new GeneratedReferenceTypeDelegate(rt); grtd.setSuperclass(parent); rt.setDelegate(grtd); BcelPerClauseAspectAdder perClauseMunger = new BcelPerClauseAspectAdder(rt, perclauseKind); perClauseMunger.forceMunge(cg, false); // TODO AV - unsafe cast // register the fresh new class into the world repository as it does not // exist on the classpath anywhere JavaClass jc = cg.getJavaClass((BcelWorld) world); ((BcelWorld) world).addSourceObjectType(jc, true); bytes = jc.getBytes(); return bytes; }
The DeclareAnnotation object encapsulates an method/field/type descriptor and an annotation. This uses a DeclareAnnotation object captured from the XML (something like '') and builds the same construct that would have existed if the code style variant was used. This involves creating a member upon which to hang the real annotation and then creating a classfile level attribute indicating a declare annotation is present (that includes the signature pattern and a pointer to the real member holding the annotation).
/** * The DeclareAnnotation object encapsulates an method/field/type descriptor and an annotation. This uses a DeclareAnnotation object * captured from the XML (something like '<declare-annotation field="* field1(..)" annotation="@Annot(a='a',fred=false,'abc')"/>') * and builds the same construct that would have existed if the code style variant was used. This involves creating a member upon * which to hang the real annotation and then creating a classfile level attribute indicating a declare annotation is present * (that includes the signature pattern and a pointer to the real member holding the annotation). * */
private void generateDeclareAnnotation(Definition.DeclareAnnotation da, int decCounter, LazyClassGen cg) { // Here is an example member from a code style declare annotation: //void ajc$declare_at_method_1(); // RuntimeInvisibleAnnotations: length = 0x6 // 00 01 00 1B 00 00 // RuntimeVisibleAnnotations: length = 0x15 // 00 01 00 1D 00 03 00 1E 73 00 1F 00 20 73 00 21 // 00 22 73 00 23 // org.aspectj.weaver.MethodDeclarationLineNumber: length = 0x8 // 00 00 00 02 00 00 00 16 // org.aspectj.weaver.AjSynthetic: length = 0x // // Code: // Stack=0, Locals=1, Args_size=1 // 0: return // and at the class level a Declare attribute: // org.aspectj.weaver.Declare: length = 0x51 // 05 00 00 00 03 01 00 05 40 41 6E 6E 6F 01 00 17 // 61 6A 63 24 64 65 63 6C 61 72 65 5F 61 74 5F 6D // 65 74 68 6F 64 5F 31 01 01 00 00 00 00 05 05 00 // 08 73 61 79 48 65 6C 6C 6F 00 01 04 00 00 00 00 // 07 00 00 00 27 00 00 00 34 00 00 00 16 00 00 00 // 3C AnnotationAJ constructedAnnotation = buildDeclareAnnotation_actualAnnotation(cg, da); if (constructedAnnotation==null) { return; // error occurred (and was reported), do not continue } String nameComponent = da.declareAnnotationKind.name().toLowerCase(); String declareName = new StringBuilder("ajc$declare_at_").append(nameComponent).append("_").append(decCounter).toString(); LazyMethodGen declareMethod = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, declareName, Type.NO_ARGS, EMPTY_STRINGS, cg); InstructionList declareMethodBody = declareMethod.getBody(); declareMethodBody.append(InstructionFactory.RETURN); declareMethod.addAnnotation(constructedAnnotation); DeclareAnnotation deca = null; ITokenSource tokenSource = BasicTokenSource.makeTokenSource(da.pattern,null); PatternParser pp = new PatternParser(tokenSource); if (da.declareAnnotationKind==DeclareAnnotationKind.Method || da.declareAnnotationKind==DeclareAnnotationKind.Field) { ISignaturePattern isp = (da.declareAnnotationKind==DeclareAnnotationKind.Method?pp.parseCompoundMethodOrConstructorSignaturePattern(true):pp.parseCompoundFieldSignaturePattern()); deca = new DeclareAnnotation(da.declareAnnotationKind==DeclareAnnotationKind.Method?DeclareAnnotation.AT_METHOD:DeclareAnnotation.AT_FIELD, isp); } else if (da.declareAnnotationKind==DeclareAnnotationKind.Type) { TypePattern tp = pp.parseTypePattern(); deca = new DeclareAnnotation(DeclareAnnotation.AT_TYPE,tp); } deca.setAnnotationMethod(declareName); deca.setAnnotationString(da.annotation); AjAttribute attribute = new AjAttribute.DeclareAttribute(deca); cg.addAttribute(attribute); cg.addMethodGen(declareMethod); }
Construct the annotation that the declare wants to add to the target.
/** * Construct the annotation that the declare wants to add to the target. */
private AnnotationAJ buildDeclareAnnotation_actualAnnotation(LazyClassGen cg, Definition.DeclareAnnotation da) { AnnotationGen anno = buildAnnotationFromString(cg.getConstantPool(),cg.getWorld(),da.annotation); if (anno==null) { return null; } else { AnnotationAJ bcelAnnotation = new BcelAnnotation(anno, world); return bcelAnnotation; } } // TODO support array values // TODO support annotation values
Build an AnnotationGen object based on a string, for example "@Foo(35,message='string')". Notice single quotes are fine for strings as they don't need special handling in the XML.
/** * Build an AnnotationGen object based on a string, for example "@Foo(35,message='string')". Notice single quotes are fine for * strings as they don't need special handling in the XML. */
private AnnotationGen buildAnnotationFromString(ConstantPool cp, World w, String annotationString) { int paren = annotationString.indexOf('('); if (paren==-1) { // the easy case, no values AnnotationGen aaj = buildBaseAnnotationType(cp,world,annotationString); return aaj; } else { // Discover the name and name/value pairs String name = annotationString.substring(0,paren); // break the rest into pieces based on the commas List<String> values = new ArrayList<String>(); int pos = paren+1; int depth = 0; int len = annotationString.length(); int start = pos; while (pos<len) { char ch = annotationString.charAt(pos); if (ch==')' && depth==0) { break; // reached the end } if (ch=='(' || ch=='[') { depth++; } else if (ch==')' || ch==']') { depth--; } if (ch==',' && depth==0) { // found a comma at the right level to end a name/value pair values.add(annotationString.substring(start,pos).trim()); start=pos+1; } pos++; } if (start != pos) { // there is a last bit to add values.add(annotationString.substring(start,pos).trim()); } AnnotationGen aaj = buildBaseAnnotationType(cp,world,name); if (aaj==null) { return null; // a check failed } String typename = aaj.getTypeName(); ResolvedType type = UnresolvedType.forName(typename).resolve(world); // shame it isn't retrievable from the anno ResolvedMember[] rms = type.getDeclaredMethods(); // Add the values for (String value: values) { int equalsIndex = value.indexOf("="); String key = "value"; if (value.charAt(0)!='\"' && equalsIndex!=-1) { key = value.substring(0,equalsIndex).trim(); value = value.substring(equalsIndex+1).trim(); } boolean keyIsOk = false; for (int m=0;m<rms.length;m++) { NameValuePair nvp = null; if (rms[m].getName().equals(key)) { // found it! keyIsOk=true; UnresolvedType rt = rms[m].getReturnType(); if (rt.isPrimitiveType()) { switch (rt.getSignature().charAt(0)) { case 'J': // long try { long longValue = Long.parseLong(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_LONG,cp,longValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a long"); return null; } break; case 'S': // short try { short shortValue = Short.parseShort(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_SHORT,cp,shortValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a short"); return null; } break; case 'F': // float try { float floatValue = Float.parseFloat(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_FLOAT,cp,floatValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a float"); return null; } break; case 'D': // double try { double doubleValue = Double.parseDouble(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_DOUBLE,cp,doubleValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a double"); return null; } break; case 'I': // integer try { int intValue = Integer.parseInt(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_INT,cp,intValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as an integer"); return null; } break; case 'B': // byte try { byte byteValue = Byte.parseByte(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_BYTE,cp,byteValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a byte"); return null; } break; case 'C': // char if (value.length()<2) { reportError("unable to interpret annotation value '"+value+"' as a char"); return null; } nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_CHAR,cp,value.charAt(1)),cp); break; case 'Z': // boolean try { boolean booleanValue = Boolean.parseBoolean(value); nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_BOOLEAN,cp,booleanValue),cp); } catch (NumberFormatException nfe) { reportError("unable to interpret annotation value '"+value+"' as a boolean"); return null; } break; default: reportError("not yet supporting XML setting of annotation values of type "+rt.getName()); return null; } } else if (UnresolvedType.JL_STRING.equals(rt)) { if (value.length()<2) { reportError("Invalid string value specified in annotation string: "+annotationString); return null; } value = value.substring(1,value.length()-1); // trim the quotes off nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.STRING,cp,value),cp); } else if (UnresolvedType.JL_CLASS.equals(rt)) { // format of class string: // Foo.class // java.lang.Foo.class if (value.length()<6) { reportError("Not a well formed class value for an annotation '"+value+"'"); return null; } String clazz = value.substring(0,value.length()-6); boolean qualified = clazz.indexOf(".")!=-1; if (!qualified) { // if not qualified, have to assume java.lang clazz = "java.lang."+clazz; } nvp = new NameValuePair(key,new ClassElementValue(new ObjectType(clazz),cp),cp); } } if (nvp!=null) { aaj.addElementNameValuePair(nvp); } } if (!keyIsOk) { reportError("annotation @"+typename+" does not have a value named "+key); return null; } } return aaj; } } private AnnotationGen buildBaseAnnotationType(ConstantPool cp,World world, String typename) { String annoname = typename; if (annoname.startsWith("@")) { annoname= annoname.substring(1); } ResolvedType annotationType = UnresolvedType.forName(annoname).resolve(world); if (!annotationType.isAnnotation()) { reportError("declare is not specifying an annotation type :"+typename); return null; } if (!annotationType.isAnnotationWithRuntimeRetention()) { reportError("declare is using an annotation type that does not have runtime retention: "+typename); return null; } List<NameValuePair> elems = new ArrayList<NameValuePair>(); return new AnnotationGen(new ObjectType(annoname), elems, true, cp); } /** * Construct the annotation that indicates this is a declare */
The PointcutAndAdvice object encapsulates an advice kind, a pointcut and names a Java method in a particular class. Generate an annotation style advice that has that pointcut whose implementation delegates to the Java method.
/** * The PointcutAndAdvice object encapsulates an advice kind, a pointcut and names a Java method in a particular class. Generate * an annotation style advice that has that pointcut whose implementation delegates to the Java method. */
private void generateAdviceMethod(PointcutAndAdvice paa, int adviceCounter, LazyClassGen cg) { // Check: Verify the class to be called does exist: ResolvedType delegateClass = world.resolve(UnresolvedType.forName(paa.adviceClass)); if (delegateClass.isMissing()) { reportError("Class to invoke cannot be found: '" + paa.adviceClass + "'"); return; } // Generate a name for this advice, includes advice kind plus a counter String adviceName = new StringBuilder("generated$").append(paa.adviceKind.toString().toLowerCase()).append("$advice$") .append(adviceCounter).toString(); // Build the annotation that encapsulates the pointcut AnnotationAJ aaj = buildAdviceAnnotation(cg, paa); // Chop up the supplied advice method string into its pieces. // Example: foo(JoinPoint jp, java.lang.String string) // JoinPoint and friends are recognized (so dont need fq package) String method = paa.adviceMethod; int paren = method.indexOf("("); String methodName = method.substring(0, paren); String signature = method.substring(paren); // Check: signature looks ok if (signature.charAt(0) != '(' || !signature.endsWith(")")) { reportError("Badly formatted parameter signature: '" + method + "'"); return; } // Extract parameter types and names List<Type> paramTypes = new ArrayList<Type>(); List<String> paramNames = new ArrayList<String>(); if (signature.charAt(1) != ')') { // there are parameters to convert into a signature StringBuilder convertedSignature = new StringBuilder("("); boolean paramsBroken = false; int pos = 1; while (pos < signature.length() && signature.charAt(pos) != ')' && !paramsBroken) { int nextChunkEndPos = signature.indexOf(',', pos); if (nextChunkEndPos == -1) { nextChunkEndPos = signature.indexOf(')', pos); } // chunk will be a type plus a space plus a name String nextChunk = signature.substring(pos, nextChunkEndPos).trim(); int space = nextChunk.indexOf(" "); ResolvedType resolvedParamType = null; if (space == -1) { // There is no parameter name, hopefully not needed! if (nextChunk.equals("JoinPoint")) { nextChunk = "org.aspectj.lang.JoinPoint"; } else if (nextChunk.equals("JoinPoint.StaticPart")) { nextChunk = "org.aspectj.lang.JoinPoint$StaticPart"; } else if (nextChunk.equals("ProceedingJoinPoint")) { nextChunk = "org.aspectj.lang.ProceedingJoinPoint"; } UnresolvedType unresolvedParamType = UnresolvedType.forName(nextChunk); resolvedParamType = world.resolve(unresolvedParamType); } else { String typename = nextChunk.substring(0, space); if (typename.equals("JoinPoint")) { typename = "org.aspectj.lang.JoinPoint"; } else if (typename.equals("JoinPoint.StaticPart")) { typename = "org.aspectj.lang.JoinPoint$StaticPart"; } else if (typename.equals("ProceedingJoinPoint")) { typename = "org.aspectj.lang.ProceedingJoinPoint"; } UnresolvedType unresolvedParamType = UnresolvedType.forName(typename); resolvedParamType = world.resolve(unresolvedParamType); String paramname = nextChunk.substring(space).trim(); paramNames.add(paramname); } if (resolvedParamType.isMissing()) { reportError("Cannot find type specified as parameter: '" + nextChunk + "' from signature '" + signature + "'"); paramsBroken = true; } paramTypes.add(Type.getType(resolvedParamType.getSignature())); convertedSignature.append(resolvedParamType.getSignature()); pos = nextChunkEndPos + 1; } convertedSignature.append(")"); signature = convertedSignature.toString(); if (paramsBroken) { return; } } Type returnType = Type.VOID; // If around advice we must find the actual delegate method and use its return type if (paa.adviceKind == AdviceKind.Around) { ResolvedMember[] methods = delegateClass.getDeclaredMethods(); ResolvedMember found = null; for (ResolvedMember candidate : methods) { if (candidate.getName().equals(methodName)) { UnresolvedType[] cparms = candidate.getParameterTypes(); if (cparms.length == paramTypes.size()) { boolean paramsMatch = true; for (int i = 0; i < cparms.length; i++) { if (!cparms[i].getSignature().equals(paramTypes.get(i).getSignature())) { paramsMatch = false; break; } } if (paramsMatch) { found = candidate; break; } } } } if (found != null) { returnType = Type.getType(found.getReturnType().getSignature()); } else { reportError("Unable to find method to invoke. In class: " + delegateClass.getName() + " cant find " + paa.adviceMethod); return; } } // Time to construct the method itself: LazyMethodGen advice = new LazyMethodGen(Modifier.PUBLIC, returnType, adviceName, paramTypes.toArray(new Type[paramTypes .size()]), EMPTY_STRINGS, cg); InstructionList adviceBody = advice.getBody(); // Generate code to load the parameters int pos = 1; // first slot after 'this' for (int i = 0; i < paramTypes.size(); i++) { adviceBody.append(InstructionFactory.createLoad(paramTypes.get(i), pos)); pos += paramTypes.get(i).getSize(); } // Generate the delegate call adviceBody.append(cg.getFactory().createInvoke(paa.adviceClass, methodName, signature + returnType.getSignature(), Constants.INVOKESTATIC)); // Generate the right return if (returnType == Type.VOID) { adviceBody.append(InstructionConstants.RETURN); } else { if (returnType.getSignature().length() < 2) { String sig = returnType.getSignature(); if (sig.equals("F")) { adviceBody.append(InstructionConstants.FRETURN); } else if (sig.equals("D")) { adviceBody.append(InstructionConstants.DRETURN); } else if (sig.equals("J")) { adviceBody.append(InstructionConstants.LRETURN); } else { adviceBody.append(InstructionConstants.IRETURN); } } else { adviceBody.append(InstructionConstants.ARETURN); } } // Add the annotation advice.addAnnotation(aaj); InstructionHandle start = adviceBody.getStart(); // Setup the local variable targeters so that the binding will work String sig = concreteAspect.name.replace('.', '/'); start.addTargeter(new LocalVariableTag("L" + sig + ";", "this", 0, start.getPosition())); if (paramNames.size() > 0) { for (int i = 0; i < paramNames.size(); i++) { start.addTargeter(new LocalVariableTag(paramTypes.get(i).getSignature(), paramNames.get(i), i + 1, start .getPosition())); } } // Record the new method in the class cg.addMethodGen(advice); }
For the given PointcutAndAdvice build the correct advice annotation.
/** * For the given PointcutAndAdvice build the correct advice annotation. */
private AnnotationAJ buildAdviceAnnotation(LazyClassGen cg, PointcutAndAdvice paa) { SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), paa.pointcut); List<NameValuePair> elems = new ArrayList<NameValuePair>(); elems.add(new NameValuePair("value", svg, cg.getConstantPool())); AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/" + paa.adviceKind.toString()), elems, true, cg.getConstantPool()); AnnotationAJ aaj = new BcelAnnotation(mag, world); return aaj; }
Error reporting
Params:
  • message –
/** * Error reporting * * @param message */
private void reportError(String message) { world.getMessageHandler().handleMessage(new Message(message, IMessage.ERROR, null, null)); } }