beirtipol
beirtipol

Reputation: 863

Using a Method Annotation value to target a pointcut

I'm using AspectJ to weave in a custom PropertyChangeSupport engine for any class tagged with @BindableClass. It looks for methods tagged with @BindableMethod and intercepts the 'set' calls to fire off a chain of propertyChangeListeners. That all works ok, but I only want to intercept methods where it has the value @BindableMethod(type=Type.SET) as I'm using this annotation for features outside of PCS. I'm struggling a little with the syntax of the pointcut and would appreciate if someone could help me out.

I can hack it in by checking the annotation's value at the time I hunt for the field name, but I would prefer to have the aspectJ lookup do this for me. I think the key is in the 'pointcut' declaration below.

Apologies if I'm describing it badly, it has been a few years since I played with AspectJ. I found some answers around Annotation lookups, but not around the annotation values.

My Annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindableMethod {
    String fieldName();

    Type type() default Type.SET;

    public static enum Type {
        GET, SET;
    }
}

My Aspect:

public aspect PropertySupportAspect {
/**
 * Weave any class which is tagged with @BindableClass with NestedPropertyChangeSupport
 */
declare parents: @BindableClass * implements PropertySupport, IBindable;

NestedPropertyChangeSupport PropertySupport.support = new NestedPropertyChangeSupport(this);

public interface PropertySupport {
    public void addPropertyChangeListener(PropertyChangeListener listener);

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);

    public void removePropertyChangeListener(PropertyChangeListener listener);

    public boolean hasListeners(String propertyName);

    public void firePropertyChange(Object b, String property, Object oldval, Object newval);
}

public PropertyChangeSupport PropertySupport.changeSupport() {
    return support;
}

public void PropertySupport.addPropertyChangeListener(PropertyChangeListener listener) {
    support.addPropertyChangeListener(listener);
}

public void PropertySupport.addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    support.addPropertyChangeListener(propertyName, listener);
}

public void PropertySupport.removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    support.removePropertyChangeListener(propertyName, listener);
}

public void PropertySupport.removePropertyChangeListener(PropertyChangeListener listener) {
    support.removePropertyChangeListener(listener);
}

public boolean PropertySupport.hasListeners(String propertyName) {
    return support.hasListeners(propertyName);
}

pointcut callSetter(PropertySupport b) : 
    call( @BindableMethod * *(..) ) 
    && target( b );

void around(PropertySupport b) : callSetter( b )  
{
    Field propertyField = getField(thisJoinPointStaticPart.getSignature());
    try {
        propertyField.setAccessible(true);
        Object oldValue = propertyField.get(b);
        proceed(b);
        Object newValue = propertyField.get(b);
        ((PropertySupport) b).firePropertyChange(b, propertyField.getName(), oldValue, newValue);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private Field getField(Signature signature) {
    Field field = null;

    try {
        MethodSignature ms = (MethodSignature) signature;
        Method m = ms.getMethod();
        BindableMethod annotation = m.getAnnotation(BindableMethod.class);
        field = signature.getDeclaringType().getDeclaredField(annotation.fieldName());
    } catch (NoSuchFieldException nsfe) {
        nsfe.printStackTrace();
    }
    return field;

}

public void PropertySupport.firePropertyChange(Object b, String property, Object oldval, Object newval) {
    support.firePropertyChange(property, oldval, newval);

}

}

My Test Class:

@BindableClass
    private class Child {
        public static final String FLD_NAME = "name";
        private String name;

        @BindableMethod(fieldName = FLD_NAME)
        public void setName(String name) {
            this.name = name;
        }

        @BindableMethod(fieldName = FLD_NAME, type = Type.GET)
        public String getName() {
            return name;
        }
    }

Upvotes: 2

Views: 848

Answers (2)

Tokazio
Tokazio

Reputation: 530

pointcut callSetter(PropertySupport b, BindableMethod bm) : 
    call(@BindableMethod * *(..)) 
    && target(b)
    && @annotation(bm);

void around(PropertySupport b,BindableMethod bm) : callSetter( b,bm ){
    bm.type();
}

Upvotes: 1

kriegaex
kriegaex

Reputation: 67317

At the moment this is not possible, but there has been talk going on on the aspectj-users mailing list:

  • Someone described the same requirement here
  • and there was also talk about a possible syntax for it there.

You may want to catch up on the discussion and see when a core developer actually has time to extend AspectJ syntax appropriately.

Upvotes: 1

Related Questions