org.springframework.aop.aspectj
Class AspectJAdviceParameterNameDiscoverer

java.lang.Object
  extended by org.springframework.aop.aspectj.AspectJAdviceParameterNameDiscoverer
All Implemented Interfaces:
ParameterNameDiscoverer

public class AspectJAdviceParameterNameDiscoverer
extends java.lang.Object
implements ParameterNameDiscoverer

ParameterNameDiscoverer implementation that tries to deduce parameter names for an advice method from the pointcut expression, returning, and throwing clauses. If an unambiguous interpretation is not available, it returns null.

This class interprets arguments in the following way:

  1. If the first parameter of the method is of type JoinPoint or ProceedingJoinPoint, it is assumed to be for passing thisJoinPoint to the advice, and the parameter name will be assigned the value "thisJoinPoint".
  2. If the first parameter of the method is of type JoinPoint.StaticPart, it is assumed to be for passing "thisJoinPointStaticPart" to the advice, and the parameter name will be assigned the value "thisJoinPointStaticPart".
  3. If a throwingName has been set, and there are no unbound arguments of type Throwable+, then an IllegalArgumentException is raised. If there is more than one unbound argument of type Throwable+, then an AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException is raised. If there is exactly one unbound argument of type Throwable+, then the corresponding parameter name is assigned the value <throwingName>.
  4. If there remain unbound arguments, then the pointcut expression is examined. Let a be the number of annotation-based pointcut expressions (@annotation, @this, @target, @args, @within, @withincode) that are used in binding form. Usage in binding form has itself to be deduced: if the expression inside the pointcut is a single string literal that meets Java variable name conventions it is assumed to be a variable name. If a is zero we proceed to the next stage. If a > 1 then an AmbiguousBindingException is raised. If a == 1, and there are no unbound arguments of type Annotation+, then an IllegalArgumentException is raised. if there is exactly one such argument, then the corresponding parameter name is assigned the value from the pointcut expression.
  5. If a returningName has been set, and there are no unbound arguments then an IllegalArgumentException is raised. If there is more than one unbound argument then an AmbiguousBindingException is raised. If there is exactly one unbound argument then the corresponding parameter name is assigned the value <returningName>.
  6. If there remain unbound arguments, then the pointcut expression is examined once more for this, target, and args pointcut expressions used in the binding form (binding forms are deduced as described for the annotation based pointcuts). If there remains more than one unbound argument of a primitive type (which can only be bound in args) then an AmbiguousBindingException is raised. If there is exactly one argument of a primitive type, then if exactly one args bound variable was found, we assign the corresponding parameter name the variable name. If there were no args bound variables found an IllegalStateException is raised. If there are multiple args bound variables, an AmbiguousBindingException is raised. At this point, if there remains more than one unbound argument we raise an AmbiguousBindingException. If there are no unbound arguments remaining, we are done. If there is exactly one unbound argument remaining, and only one candidate variable name unbound from this, target, or args, it is assigned as the corresponding parameter name. If there are multiple possibilities, an AmbiguousBindingException is raised.

The behavior on raising an IllegalArgumentException or AmbiguousBindingException is configurable to allow this discoverer to be used as part of a chain-of-responsibility. By default the condition will be logged and the getParameterNames(..) method will simply return null. If the raiseExceptions property is set to true, the conditions will be thrown as IllegalArgumentException and AmbiguousBindingException, respectively.

Was that perfectly clear? ;)

Short version: If an unambiguous binding can be deduced, then it is. If the advice requirements cannot possibly be satisfied, then null is returned. By setting the raiseExceptions property to true, descriptive exceptions will be thrown instead of returning null in the case that the parameter names cannot be discovered.

Since:
2.0
Author:
Adrian Colyer

Nested Class Summary
static class AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException
          Thrown in response to an ambiguous binding being detected when trying to resolve a method's parameter names.
private static class AspectJAdviceParameterNameDiscoverer.PointcutBody
          Simple struct to hold the extracted text from a pointcut body, together with the number of tokens consumed in extracting it.
 
Field Summary
private  java.lang.Class[] argumentTypes
           
private static java.util.Set<java.lang.String> nonReferencePointcutTokens
           
private  int numberOfRemainingUnboundArguments
           
private  java.lang.String[] parameterNameBindings
           
private  java.lang.String pointcutExpression
          The pointcut expression associated with the advice, as a simple String.
private  boolean raiseExceptions
           
private  java.lang.String returningName
          If the advice is afterReturning, and binds the return value, this is the parameter name used.
private static java.util.Set<java.lang.String> singleValuedAnnotationPcds
           
private static int STEP_ANNOTATION_BINDING
           
private static int STEP_FINISHED
           
private static int STEP_JOIN_POINT_BINDING
           
private static int STEP_PRIMITIVE_ARGS_BINDING
           
private static int STEP_REFERENCE_PCUT_BINDING
           
private static int STEP_RETURNING_BINDING
           
private static int STEP_THIS_TARGET_ARGS_BINDING
           
private static int STEP_THROWING_BINDING
           
private static java.lang.String THIS_JOIN_POINT
           
private static java.lang.String THIS_JOIN_POINT_STATIC_PART
           
private  java.lang.String throwingName
          If the advice is afterThrowing, and binds the thrown value, this is the parameter name used.
 
Constructor Summary
AspectJAdviceParameterNameDiscoverer(java.lang.String pointcutExpression)
          Create a new discoverer that attempts to discover parameter names from the given pointcut expression.
 
Method Summary
private  boolean alreadyBound(java.lang.String varName)
           
private  void bindAnnotationsFromVarNames(java.util.List<java.lang.String> varNames)
          Match the given list of extracted variable names to argument slots.
private  void bindParameterName(int index, java.lang.String name)
           
private  int countNumberOfUnboundAnnotationArguments()
           
private  int countNumberOfUnboundPrimitiveArguments()
           
private  void findAndBind(java.lang.Class argumentType, java.lang.String varName)
           
 java.lang.String[] getParameterNames(java.lang.reflect.Constructor ctor)
          An advice method can never be a constructor in Spring.
 java.lang.String[] getParameterNames(java.lang.reflect.Method method)
          Deduce the parameter names for an advice method.
private  AspectJAdviceParameterNameDiscoverer.PointcutBody getPointcutBody(java.lang.String[] tokens, int startIndex)
           
private  boolean isSubtypeOf(java.lang.Class supertype, int argumentNumber)
           
private  boolean isUnbound(int i)
           
private  void maybeBindAnnotationsFromPointcutExpression()
          Parse the string pointcut expression looking for: @this, @target, @args, @within, @withincode, @annotation.
private  void maybeBindPrimitiveArgsFromPointcutExpression()
          Match up args against unbound arguments of primitive types
private  void maybeBindReferencePointcutParameter()
           
private  void maybeBindReturningVariable()
          If a returning variable was specified and there is only one choice remaining, bind it.
private  boolean maybeBindThisJoinPoint()
          If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as parameter name and return true, else return false.
private  void maybeBindThisJoinPointStaticPart()
           
private  void maybeBindThisOrTargetOrArgsFromPointcutExpression()
          Parse the string pointcut expression looking for this(), target() and args() expressions.
private  void maybeBindThrowingVariable()
          If a throwing name was specified and there is exactly one choice remaining (argument that is a subtype of Throwable) then bind it.
private  java.lang.String maybeExtractVariableName(java.lang.String candidateToken)
           
private  void maybeExtractVariableNamesFromArgs(java.lang.String argsSpec, java.util.List<java.lang.String> varNames)
          Given an args pointcut body (could be args or at_args), add any candidate variable names to the given list.
 void setRaiseExceptions(boolean raiseExceptions)
          Indicate whether IllegalArgumentException and AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException must be thrown as appropriate in the case of failing to deduce advice parameter names.
 void setReturningName(java.lang.String returningName)
          If afterReturning advice binds the return value, the returning variable name must be specified.
 void setThrowingName(java.lang.String throwingName)
          If afterThrowing advice binds the thrown value, the throwing variable name must be specified.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

THIS_JOIN_POINT

private static final java.lang.String THIS_JOIN_POINT
See Also:
Constant Field Values

THIS_JOIN_POINT_STATIC_PART

private static final java.lang.String THIS_JOIN_POINT_STATIC_PART
See Also:
Constant Field Values

STEP_JOIN_POINT_BINDING

private static final int STEP_JOIN_POINT_BINDING
See Also:
Constant Field Values

STEP_THROWING_BINDING

private static final int STEP_THROWING_BINDING
See Also:
Constant Field Values

STEP_ANNOTATION_BINDING

private static final int STEP_ANNOTATION_BINDING
See Also:
Constant Field Values

STEP_RETURNING_BINDING

private static final int STEP_RETURNING_BINDING
See Also:
Constant Field Values

STEP_PRIMITIVE_ARGS_BINDING

private static final int STEP_PRIMITIVE_ARGS_BINDING
See Also:
Constant Field Values

STEP_THIS_TARGET_ARGS_BINDING

private static final int STEP_THIS_TARGET_ARGS_BINDING
See Also:
Constant Field Values

STEP_REFERENCE_PCUT_BINDING

private static final int STEP_REFERENCE_PCUT_BINDING
See Also:
Constant Field Values

STEP_FINISHED

private static final int STEP_FINISHED
See Also:
Constant Field Values

singleValuedAnnotationPcds

private static final java.util.Set<java.lang.String> singleValuedAnnotationPcds

nonReferencePointcutTokens

private static final java.util.Set<java.lang.String> nonReferencePointcutTokens

raiseExceptions

private boolean raiseExceptions

returningName

private java.lang.String returningName
If the advice is afterReturning, and binds the return value, this is the parameter name used.


throwingName

private java.lang.String throwingName
If the advice is afterThrowing, and binds the thrown value, this is the parameter name used.


pointcutExpression

private java.lang.String pointcutExpression
The pointcut expression associated with the advice, as a simple String.


argumentTypes

private java.lang.Class[] argumentTypes

parameterNameBindings

private java.lang.String[] parameterNameBindings

numberOfRemainingUnboundArguments

private int numberOfRemainingUnboundArguments
Constructor Detail

AspectJAdviceParameterNameDiscoverer

public AspectJAdviceParameterNameDiscoverer(java.lang.String pointcutExpression)
Create a new discoverer that attempts to discover parameter names from the given pointcut expression.

Method Detail

setRaiseExceptions

public void setRaiseExceptions(boolean raiseExceptions)
Indicate whether IllegalArgumentException and AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException must be thrown as appropriate in the case of failing to deduce advice parameter names.

Parameters:
raiseExceptions - true if exceptions are to be thrown

setReturningName

public void setReturningName(java.lang.String returningName)
If afterReturning advice binds the return value, the returning variable name must be specified.

Parameters:
returningName - the name of the returning variable

setThrowingName

public void setThrowingName(java.lang.String throwingName)
If afterThrowing advice binds the thrown value, the throwing variable name must be specified.

Parameters:
throwingName - the name of the throwing variable

getParameterNames

public java.lang.String[] getParameterNames(java.lang.reflect.Method method)
Deduce the parameter names for an advice method.

See the class level javadoc for this class for details of the algorithm used.

Specified by:
getParameterNames in interface ParameterNameDiscoverer
Parameters:
method - the target Method
Returns:
the parameter names

getParameterNames

public java.lang.String[] getParameterNames(java.lang.reflect.Constructor ctor)
An advice method can never be a constructor in Spring.

Specified by:
getParameterNames in interface ParameterNameDiscoverer
Parameters:
ctor - constructor to find parameter names for
Returns:
null
Throws:
java.lang.UnsupportedOperationException - if raiseExceptions has been set to true

bindParameterName

private void bindParameterName(int index,
                               java.lang.String name)

maybeBindThisJoinPoint

private boolean maybeBindThisJoinPoint()
If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as parameter name and return true, else return false.


maybeBindThisJoinPointStaticPart

private void maybeBindThisJoinPointStaticPart()

maybeBindThrowingVariable

private void maybeBindThrowingVariable()
If a throwing name was specified and there is exactly one choice remaining (argument that is a subtype of Throwable) then bind it.


maybeBindReturningVariable

private void maybeBindReturningVariable()
If a returning variable was specified and there is only one choice remaining, bind it.


maybeBindAnnotationsFromPointcutExpression

private void maybeBindAnnotationsFromPointcutExpression()
Parse the string pointcut expression looking for: @this, @target, @args, @within, @withincode, @annotation. If we find one of these pointcut expressions, try and extract a candidate variable name (or variable names, in the case of args).

Some more support from AspectJ in doing this exercise would be nice... :)


bindAnnotationsFromVarNames

private void bindAnnotationsFromVarNames(java.util.List<java.lang.String> varNames)
Match the given list of extracted variable names to argument slots.


maybeExtractVariableName

private java.lang.String maybeExtractVariableName(java.lang.String candidateToken)

maybeExtractVariableNamesFromArgs

private void maybeExtractVariableNamesFromArgs(java.lang.String argsSpec,
                                               java.util.List<java.lang.String> varNames)
Given an args pointcut body (could be args or at_args), add any candidate variable names to the given list.


maybeBindThisOrTargetOrArgsFromPointcutExpression

private void maybeBindThisOrTargetOrArgsFromPointcutExpression()
Parse the string pointcut expression looking for this(), target() and args() expressions. If we find one, try and extract a candidate variable name and bind it.


maybeBindReferencePointcutParameter

private void maybeBindReferencePointcutParameter()

getPointcutBody

private AspectJAdviceParameterNameDiscoverer.PointcutBody getPointcutBody(java.lang.String[] tokens,
                                                                          int startIndex)

maybeBindPrimitiveArgsFromPointcutExpression

private void maybeBindPrimitiveArgsFromPointcutExpression()
Match up args against unbound arguments of primitive types


isUnbound

private boolean isUnbound(int i)

alreadyBound

private boolean alreadyBound(java.lang.String varName)

isSubtypeOf

private boolean isSubtypeOf(java.lang.Class supertype,
                            int argumentNumber)

countNumberOfUnboundAnnotationArguments

private int countNumberOfUnboundAnnotationArguments()

countNumberOfUnboundPrimitiveArguments

private int countNumberOfUnboundPrimitiveArguments()

findAndBind

private void findAndBind(java.lang.Class argumentType,
                         java.lang.String varName)