From e8f7b99508f0e44f756c465d710d7536f34c3710 Mon Sep 17 00:00:00 2001 From: acolyer Date: Wed, 17 Aug 2005 16:33:50 +0000 Subject: pretty much a complete rewrite, this time matching the actual implementation! --- docs/adk15ProgGuideDB/generics.xml | 1709 +++++++++++++++--------------------- 1 file changed, 684 insertions(+), 1025 deletions(-) (limited to 'docs/adk15ProgGuideDB/generics.xml') diff --git a/docs/adk15ProgGuideDB/generics.xml b/docs/adk15ProgGuideDB/generics.xml index d109020f3..1514d7ded 100644 --- a/docs/adk15ProgGuideDB/generics.xml +++ b/docs/adk15ProgGuideDB/generics.xml @@ -313,916 +313,599 @@ - Matching generic and parameterized types in type patterns - - - The foundation of AspectJ's support for generic and parameterized types in aspect declarations is the extension of type - pattern matching to allow matching against generic and parameterized types. - - - - The type pattern "Foo" matches all types named Foo, whether they - be simple types, generic types, or parameterized types. So for example, Foo, - Foo<T>, and Foo<String>will all be matched. - - - - AspectJ 5 also extends the specification of type patterns to allow explicit matching of generic and parameterized - types by including one or more type parameter patterns inside angle braces (< >) immediately - after the type pattern. For example, List<String> - - - ' - - TypeParameterPatternList ::= TypeParameterPattern (',' TypeParameterPattern)* - - TypeParameterPattern ::= TypePattern | - '?' TypeBoundPattern? - - TypeBoundPattern ::= 'extends' TypePattern AdditionalBoundPatternList? | - 'super' TypePattern AdditionalBoundPatternList? - - AdditionalBoundPatternList ::= AdditionalBoundPattern AdditionalBoundPatternList | - AdditionalBoundPattern - - AdditionalBoundPattern ::= '&' TypePattern - - ]]> - - - A simple identifier (such as String) occuring in a type parameter list will be treated as a type name unless - a type variable of that name is in scope (declaring type variables is covered later). The type pattern List<E> - will result in an "invalid absolute type name" warning if no type E is in scope (declared in the default package, or - imported in the compilation unit) and no declaration of E as a type variable is in scope either. - - - Some simple examples of type patterns follow: - - - - - List<String> - - Matches the parameterized type List<String> - - - - - - List<? extends Number> - - Matches the parameterized type List<? extends Number> - - - - - - List<E> - - Outside of a scope in which E is defined as a type variable, this pattern matches the - parameterized type List<E>. If E is not - a type then an invalidAbsoluteTypeName xlint warning will be issued. - - In a scope in which - E is defined as a type variable, this pattern matches the generic type List<E>. - The type parameter name does not have to match the name used in the declaration of List, - but the bounds must match. This pattern also matches any parameterization of List - that satisfies the bounds of the type variable (for example, List<String>). - - - - - - - - The *, +, and .. wildcards may be used in type patterns - matching against generic and parameterized types (just as in any other type pattern). The + - wildcard matches all subtypes. Recalling the discussion on subtypes and supertypes in the previous section, note - that the pattern List<Number>+ will match List<Number> and - LinkedList<Number>, but not List<Double>. To match lists of - any number type use the pattern List<Number+> which will match - List<Number>, List<Double>, List<Float> - and so on. - - - - The generics wildcard ? is considered part of the signature of a parameterized type, and - is not used as an AspectJ wildcard in type matching. For example: - - - - - - List<*> - - Matches any generic or parameterized List type (List<String>, - List<Integer> and so on) with a single type parameter. - - - - - - List<?> - - Matches the parameterized type List<?> (and does - not match List<String>, - List<Integer> and so on) - - - - - - List<? extends Number+> - - Matches List<? extends Number>, List<? extends Double>, - and so on, but does not match List<Double>. - - - - - - - - - - Signature patterns + Matching generic and parameterized types in pointcut expressions - Now that we understand how to write type patterns that match generic and parameterized types, it is time to look at - how these can be utilized to match member declarations by using signature patterns. - - - To match members declared in generic types and making use of type variables defined in those types (for - example interface Foo<T> { public T doSomething(); } use a signature pattern of the form: - - .doSomething() - ]]> - - - This assumes a scope in which X is declared as a type variable. As with type patterns, the name - of the type variable does not have to match the name used in the member declaration, but the bounds must match. - For example, if the interface was declared as Foo<T extends Number> then the signature - pattern would be: X Foo<X extends Number>.doSomething(). - - - - - - T Util<T extends Number,S>.someFunction(List<S>) - - Matches the method someFunction in a generic type Util with - two type parameters, the first type parameter having an upper bound of Number. - - - - - - LinkedList<E>.new() - - Matches the no-argument constructor of the generic type LinkedList. - - - - - - - - Matching a field with a generic type works in the same way. For example: + The simplest way to work with generic and parameterized types in pointcut expressions and type patterns + is simply to use the raw type name. For example, the type pattern List will match + the generic type List<E> and any parameterization of that type + (List<String>, List<?>, List<? extends Number> and so on. This + ensures that pointcuts written in existing code that is not generics-aware will continue to work as + expected in AspectJ 5. It is also the recommended way to match against generic and parameterized types + in AspectJ 5 unless you explicitly wish to narrow matches to certain parameterizations of a generic type. + + Generic methods and constructors, and members defined in generic types, may use type variables + as part of their signature. For example: - .* + T first(List ts) { ... } + + /** instance generic method */ + T max(T t1, T t2) { ... } + + } + + public class G { + + // field with parameterized type + T myData; + + // method with parameterized return type + public List getAllDataItems() {...} + + } ]]> - - Matches a field of the type of type parameter T in any generic type with a single - unbounded type parameter (the pattern*<T>). The field may be of any name. - - To match a generic method the generic method type variable - declarations become part of the signature pattern. For example: + AspectJ 5 does not allow the use of type variables in pointcut expressions and type patterns. Instead, members that + use type parameters as part of their signature are matched by their erasure. Java 5 defines the + rules for determing the erasure of a type as follows. - - List *.favourites(List) - ]]> - - matches a generic method favourites declared in any type. To match a - static generic method simply include the static modifier in the type pattern. + + Let |T| represent the erasure of some type T. Then: - - - - Pointcuts - - - In this section we discuss how type patterns and signature patterns matching on generic and - parameterized types, methods, and constructors can be used in pointcut expressions. - We distinguish between pointcuts that match based on static type information, and pointcuts - that match based on runtime type information (this, target, args). - - - - First however we need to address the notion of type variables and scopes. There is a - convention in Java, but no requirement, that type variables are named with a single letter. - Likewise it is rare, but perfectly legal, to declare a type with a single character name. Given the - type pattern List<Strng>, is this a mis-spelling of the - parameterized type pattern List<String> or is it a generic type pattern - with one unbounded type variable Strng?. Alternatively, given the - type pattern List<E>, if the type E cannot be found, - is this a missing import statement or an implied type variable? There is no way for AspectJ - to disambiguate in these situations without an explicit declaration of type variable names. If - E is defined as a type variable, and Strng is not, then both - declarations can be correctly interpreted. - - - - Type Variables in Pointcut Expressions - - The type variables in scope for a pointcut primitive are declared in a type variable - list immediately following the pointcut desginator keyword. For example: - - (Foo *) - ]]> - - matches a get join point for a field with any name (*) and of - the generic type Foo<T>. - - In contrast, the pointcut - - .*) - ]]> - - matches a get join point for a field with any name and of the parameterized - type Foo<T>. If there is no type T in scope, an - "invalid absolute type name (T)" warning will be issued. - - - The type variables declaration following a pointcut designator permits only simple identifiers - (e.g. <S,T> and not <S extends Number>). - - - A type variable declaration list can appear following any pointcut designator except - for handler (Java 5 does - not permit a generic class to be a direct or indirect subtype of Throwable - - see JLS 8.1.2), the dynamic pointcuts this, target, args, if, cflow, cflowbelow, - and the annotation pointcut designators - (@args, @this, @within and so on). - - - - - Initialization and execution pointcuts - - - Recall that there is only ever one type for a generic type (e.g. List<E>) - regardless of how many different parameterizations of that type (e.g. - List<String>, List<Double>) are used within a - program. For join points that occur within a type, such as execution join points, it therefore only - makes sense to talk about execution join points for the generic type. Given the generic type - - - { + + The erasure of a parameterized type T<T1,...,Tn> is |T|. + For example, the erasure of List<String> is List. - T doSomething(T toSomeT) { - return T; - } - - } - ]]> - - - then - - - - - - execution<T>(T Foo<T>.doSomething(..)) - - matches the execution of the doSomething method in - Foo. - - - - - - execution(* Foo.doSomething(..)) - - also matches the execution of the doSomething method in - Foo. - - - - - - execution(T Foo.doSomething(..)) - - results in an "invalid absolute type name (T)" warning since T is - interpreted as a type, not a type variable. - - - - - - execution(String Foo<String>.doSomething(..)) - - results in a compilation error "no execution join points for parameterized type - Foo<String>, use a generic signature instead". - - - - - - - - Given the type declaration - - - { + The erasure of a nested type T.C is |T|.C. For example, + the erasure of the nested type Foo<T>.Bar is Foo.Bar. + + The erasure of an array type T[] is |T|[]. For example, + the erasure of List<String>[] is List[]. - N doSomething(N toSomeN) { - return N; - } + The erasure of a type variable is its leftmost bound. For example, the erasure of a + type variable P is Object, and the erasure of a type + variable N extends Number is Number. + + The erasure of every other type is the type itself + - } - ]]> - - - then - - - - - - execution<T>(T Bar<T>.doSomething(..)) - - does not match the execution of Bar.doSomething since - the bounds of the type parameter T in the pointcut expression do - not match the bounds of the type parameter N in the type declaration. - - - - - - execution<T>(T Bar<T extends Number>.doSomething(..)) - - matches the execution of the doSomething method in - Bar. - - - - - - execution<T extends Number>(T Bar<T>.doSomething(..)) - - results in a compilation error, since type variable bounds must be specified as part - of the declaring type pattern, and not in the type variable list. - - - - - - - - If a type implements a parameterized interface, then - execution join points exist and can be matched for the parameterized interface operations within - the implementing type. For example, given the pair of types: - - - { - T greatest(List ts); - } - - public class NumberOperations implements Greatest { - public Number greatest(List numbers) { - //... - } - } - ]]> - - - then - - - .*(..)) - ]]> - - - will match the execution of the greatest method declared in - NumberOperations. However, it does not - match the execution of greatest in the program below: - + + Applying these rules to the earlier examples, we find that the methods defined in Utils + can be matched by a signature pattern matching static Object Utils.first(List) and + Number Utils.max(Number, Number) respectively. The members of the generic type + G can be matched by a signature pattern matching Object G.myData and + public List G.getAllDataItems() respectively. + + + Restricting matching using parameterized types + + Pointcut matching can be further restricted to match only given parameterizations of parameter types (methods and constructors), return + types (methods) and field types (fields). This is achieved by specifying a parameterized type pattern at the appropriate point + in the signature pattern. For example, given the class Foo: { - T greatest(List ts); - } - - public class NumberOperations implements Greatest { - public N greatest(List numbers) { - //... - } - } - - // in some fragment of code... - NumberOperations numOps = new NumberOperations(); - numOps.greatest(numList); - ]]> - - Since there is only one generic type, NumberOperations, - which implements a generic interface. Either of the pointcut expressions - execution<T>(* Greatest<T>.*(..)) or - execution<T>(* Greatest<T extends Number>.*(..)) will - match the execution of greatest in this example. Recall from - chapter that a kinded pointcut primitive matches a join point if - it exactly matches one of the signatures of the join point. The signatures of the - execution join point for greatest in the example above are: - - - - - public N Greatest<N>.greatest(List<N>) - - from the declaration in the Greatest interface, and - - - - - - public N Greatest<N extends Number>.greatest(List<N>) - - from the additional bounds restriction of N in the - declaration of NumberOperations - - - - - - - - Join points for staticinitialization,initialization and - preinitialization - only ever exist on a generic type (an interface cannot define a constructor). The expression - initialization<T>(Foo<T>.new(..)) which match any initialization - join point for the generic type Foo<T>, and - staticinitialization<T>(Foo<T>) matches the static initialization - of that same type. - - - - The expression staticinitialization(List<String>) will result in a - compilation error: there is no static initialization join point for the parameterized type - List<String>. However, the expression - staticinitialization(List<String>+) is - legal, and will match the static initialization of any type that - implements List<String>. The expression - staticinitialization<T>(List<T>+) will match the static - initialization join point of any type that either extends or implements the generic - type List<T> or implements any parameterization of that - interface. - - - - - - Static scoping: within and withincode - - The within and withincode - pointcut designators both match the - execution of join points that occur within a type or a member of a type respectively. Therefore - the same considerations apply with respect to there only being one type for - a generic type regardless of how many parameterizations of that type are used in a program. - - - The within pointcut designator can never be used in conjunction - with a simple parameterized type. So - - - - - - within<T>(Foo<T>) - - matches all join points occurring within the generic type Foo<T>, - and - - - - - - within(Foo<String>) - - results in a compilation error since there is no concept of a join point within a - parameterized type, but - - - - - - within(Foo<String>+) - - matches any join point occurring within a type that - implements Foo<String>. - - - - - - - The withincode designator is likewise normally used with a - generic type, but can be used with a parameterized interface type to match join points - arising from code lexically within the implementation of the interface methods, in a type - that implements the parameterized interface. - - - - - - withincode<T>(* Foo<T>.*(..)) - - matches all join points arising from code lexically within a method of the - generic type Foo<T> - - - - - - withincode(* Foo<String>.*(..)) - - results in a compilation error if Foo is not an interface. If - Foo is an interface then it matches all join points arising from - code lexically within the implementation of the interface methods in a type that - implements Foo<String>. - - - - - - withincode(* Foo<String>+.*(..)) - - matches any join point occurring within a method of a type that - implements Foo<String>. - - - - - - - - - - Call, get and set pointcuts - - - At a call join point, the target of the call may be either a generic or - a parameterized type. The following short program demonstrates this: - + public class Foo { + + List myStrings; + List myFloats; + + public List getStrings() { return myStrings; } + public List getFloats() { return myFloats; } + + public void addStrings(List evenMoreStrings) { + myStrings.addAll(evenMoreStrings); + } + + } + ]]> - { + - public T timeFor; - - public Foo(T aCuppa) { - timeFor = aCuppa; // set-site A - } + Then a get join point for the field myStrings can be matched by the + pointcut get(List Foo.myStrings) and by the pointcut get(List<String> Foo.myStrings), + but not by the pointcut get(List<Number> *). - public void doThis(T t) { - doThat(t); // call-site A - } - - public void doThat(T t) { - return; - } - - } + A get join point for the field myFloats can be matched by the + pointcut get(List Foo.myFloats), the pointcut get(List<Float> *), + and the pointcut get(List<Number+> *). This last example shows how AspectJ type + patterns can be used to match type parameters types just like any other type. The pointcut + get(List<Double> *) does not match. - public class Main { - public static void main(String[] args) { - Foo foos = new Foo("tea"); - foos.doThis("b"); //call-site B - foos.doThat("c"); // call-site C - foos.timeFor = "a cuppa"; // set-site B + The execution of the methods getStrings and getFloats can be + matched by the pointcut expression execution(List get*(..)), and the pointcut + expression execution(List<*> get*(..)), but only getStrings + is matched by execution(List<String> get*(..)) and only getFloats + is matched by execution(List<Number+> get*(..)) + + A call to the method addStrings can be matched by the pointcut expression + call(* addStrings(List)) and by the expression call(* addStrings(List<String>)), + but not by the expression call(* addStrings(List<Number>)). + + + Remember that any type variable reference in a generic member is + always matched by its erasure. Thus given the following + example: + + { + + List foo(List - - - We have annotated the three method call sites as call-site A, call-site B, and call-site C. - Call-site A is situated within the generic type Foo<T> and - the target of the call is of type Foo<T>. Call-sites B and C have a - target of type public void Foo<String>. However, in all three cases - the declaring type of the method is Foo<T>. - A call pointcut expression matches based on the declaring type of a method - (where specified) so all three call-sites in the above program will give rise to call join points - matched by the pointcut call<T>(* Foo<T>.do*(T)), but - none of them will be matched by the pointcut - call(* Foo<String>.do*(String)). - - - Likewise both of the set join points in the above program (arising from the lines of - code annotated "set-site A" and "set-site B" are matched by the pointcut expression - set<T>(T Foo<T>.*), but neither of them are - matched by the pointcut expression set(String Foo<String>.*). - + + + The execution of foo can be matched by + execution(List foo(List)), + execution(List foo(List<String>>)), and + execution(* foo(List<String<))but + not by execution(List<Object> foo(List<String>>) + since the erasure of List<T> is List + and not List<Object>. + + + - - Specifying a parameterized type pattern in the declaring type pattern position of a call, get or set - pointcut expression results in a compilation error "declaring type cannot be parameterized". - The call, get, and set pointcut designators can be combined with - target to match based on the actual (possibly parameterized) target type of the receiver. - + + Generic wildcards and signature matching - A field's type may be either generic or parameterized, as illustrated in the example below. - - + When it comes to signature matching, a type parameterized using a generic wildcard is a distinct type. + For example, List<?> is a very different type to List<String>, + even though a variable of type List<String> can be assigned to a variable of + type List<?>. Given the methods: + + { - private F aField; // field with a generic type - private List; // field with a generic type - private List fieldNames; // field with a parameterized type - } - ]]> + class C { + + public void foo(List listOfSomeNumberType) {} - The get and set join points for a field declared with a generic type are - only matched by get and set pointcut expressions that specify a generic type pattern (or *) - for the field type. For example, get<T>(T Farmers<T>.aField) - matches a get of the field aField, whereas - get(String Farmers<String>.aField) never does (even when the - target object at the join point is of type Farmers<String>). - + public void bar(List listOfSomeType) {} - - A field with a parameterized type is matched by specifying a parameterized type - pattern (or *) for the field type. For example, set(List<String> Farmers.fieldName). - The expression set<T>(List<String> Farmers<T>.fieldNames) would - also match here (but would no longer match if the Farmers type were refactored - to be a plain (non-generic) type). - - - + public void goo(List listOfDoubles) {} + + } + ]]> + + + + - call<T>(* List<T>.*(..)) + execution(* C.*(List)) - matches any call to an operation defined in the generic type - List<E>. This includes calls where the target is of type List<String>, - List<Number>, List<? super Foo> and so on. + Matches an execution join point for any of the three methods. - get<T>(T *<T extends Account>.*) + execution(* C.*(List<? extends Number>)) - matches the get of any field defined in a generic type with one type parameter that has - an upper bound of Account. The field has the type of the type parameter, and - can be of any name. + matches only the + execution of foo, and not the execution + of goo since List<? extends Number> and + List<Double> are distinct types. - + - set(List<Account> Foo.*Account) + execution(* C.*(List<?>)) - matches the set of a field of type List<Account> declared in - type Foo where the field name ends with "Account". + matches only the execution of bar. - - + + - get(List<? extends Number> *) + execution(* C.*(List<? extends Object+>)) - matches any get of a field of type List<? extends Number>. - Does not match gets of fields of type List<Number> or - List<Double> as these are distinct types. + matches both the execution of foo and the execution of bar + since the upper bound of List<?> is implicitly Object. - + + + + + - - get(List<Number+> *) - - matches any get of a field of type List<Number> or - any subclass of Number. For example, List<Number>, - List<Double> List<Float>. - Does not match a get join point for a field of type List<? extends Number> - as this is a distinct type. - - - + + Treatment of bridge methods + + Under certain circumstances a Java 5 compiler is required to create bridge + methods that support the compilation of programs using raw types. Consider the types + + { + + public T foo(T someObject) { + return someObject; + } + + } - - call(* List<?>.*(..)) - - results in a compilation error, "declaring type cannot be parameterized (List<?>)". - - - + class SubGeneric extends Generic { + + public N foo(N someNumber) { + return someNumber; + } + + } + ]]> + + The class SubGeneric extends Generic + and overrides the method foo. Since the upper bound of the type variable + N in SubGeneric is different to the upper bound of + the type variable T in Generic, the method foo + in SubGeneric has a different erasure to the method foo + in Generic. This is an example of a case where a Java 5 compiler will create + a bridge method in SubGeneric. Although you never see it, + the bridge method will look something like this: + + + + + Bridge methods are synthetic artefacts generated as a result of a particular compilation strategy and + have no execution join points in AspectJ 5. So the pointcut execution(Object SubGeneric.foo(Object)) + does not match anything. (The pointcut execution(Object Generic.foo(Object)) matches the + execution of foo in both Generic and SubGeneric since + both are implementations of Generic.foo). + + + It is possible to call a bridge method as the following short + code snippet demonstrates. Such a call does result in a call join point for the call to + the method. + + + + + + + Runtime type matching with this(), target() and args() + + The this(), target(), and + args() pointcut expressions all match based on the runtime + type of their arguments. Because Java 5 implements generics using erasure, it is not + possible to ask at runtime whether an object is an instance of a given parameterization of a type + (only whether or not it is an instance of the erasure of that parameterized type). Therefore + AspectJ 5 does not support the use of parameterized types with the this() and + target() pointcuts. Parameterized types may however be used in conjunction with + args(). Consider the following class + - - + listOfStrings) {} + + public void bar(List listOfDoubles) {} + + public void goo(List listOfSomeNumberType) {} - - Handler + } + ]]> + + + + - - The Java Language Specification states that a generic class may not be a direct or indirect - subclass of Throwable. Therefore it is a compilation error to use a generic - or parameterized type pattern in a handler pointcut expression. - + + args(List) + + will match an execution or call join point for any of + these methods + + + + + + args(List<String>) + + will match an execution + or call join point for foo. + + + + + + args(List<Double>) + + matches an execution or call join point for bar, and may match + at an execution or call join point for goo since it is legitimate to pass an + object of type List<Double> to a method expecting a List<? extends Number>. + + + In this situation a runtime test would normally be applied to ascertain whether or not the argument + was indeed an instance of the required type. However, in the case of parameterized types such a test is not + possible and therefore AspectJ 5 considers this a match, but issues an unchecked warning. + For example, compiling the aspect A below with the class C produces the + compilation warning: "unchecked match of List<Double> with List<? extends Number> when argument is + an instance of List at join point method-execution(void C.goo(List<? extends Number>)) [Xlint:uncheckedArgument]"; + + + + + + + listOfDoubles) : execution(* C.*(..)) && args(listOfDoubles) { + for (Double d : listOfDoubles) { + // do something + } + } + + } + ]]> + + Like all Lint messages, the uncheckedArgument warning can be + configured in severity from the default warning level to error or even ignore if preferred. + In addition, AspectJ 5 offers the annotation @SuppressAjWarnings which is + the AspectJ equivalent of Java's @SuppressWarnings annotation. If the + advice is annotated with @SuppressWarnings then all + lint warnings issued during matching of pointcut associated with the advice will be + suppressed. To suppress just an uncheckedArgument warning, use the + annotation @SuppressWarnings("uncheckedArgument") as in the following + examples: + - - - - Runtime type matching: this, target and args - - - Java 5 generics are implemented using a technique known an erasure. - In particular, what gets "erased" is the ability to find out the parameterized runtime type - of an instance of a generic type. You can ask if something is an instanceof List, - but not if something is an instanceof List<String> - - - - The this, target and args pointcut designators all match - based on the runtime type of the appropriate object (this, target, or argument) at a join point. - To match any parameterization of a generic type, simply use the raw type (type variables are - not permitted with these designators). For example: - - - + - target(List) - - matches any call to an instance of List (including - List<String>, List<Number>, and so on. + @SuppressAjWarnings // will not see *any* lint warnings for this advice + before(List listOfDoubles) : execution(* C.*(..)) && args(listOfDoubles) { + for (Double d : listOfDoubles) { + // do something + } + } + + @SuppressAjWarnings("uncheckedArgument") // will not see *any* lint warnings for this advice + before(List listOfDoubles) : execution(* C.*(..)) && args(listOfDoubles) { + for (Double d : listOfDoubles) { + // do something + } + } + + } + ]]> + + + The safest way to deal with uncheckedArgument warnings however is to restrict the pointcut + to match only at those join points where the argument is guaranteed to match. This is achieved by combining + args with a call or execution signature matching + pointcut. In the following example the advice will match the execution of bar but not + of goo since the signature of goo is not matched by the execution pointcut + expression. + + + listOfDoubles) : execution(* C.*(List)) && args(listOfDoubles) { + for (Double d : listOfDoubles) { + // do something + } + } + + } + ]]> + + Generic wildcards can be used in args type patterns, and matching follows regular Java 5 assignability rules. For + example, args(List<?>) will match a list argument of any type, and + args(List<? extends Number>) will match an argument of type + List<Number>, List<Double>, List<Float> and so on. Where a match cannot be + fully statically determined, the compiler will once more issue an uncheckedArgument warning. - - - - args (List) - - matches any join point with a single argument that is an instance of - List. + Consider the following program: + + ls = new ArrayList(); + List ld = new ArrayList(); + c.foo("hi"); + c.foo(ls); + c.foo(ld); + } + + public void foo(Object anObject) {} + } + + aspect A { + before(List aListOfSomeNumberType) + : call(* foo(..)) && args(aListOfSomeNumberType) { + // process list... + } + } + ]]> + + + + From the signature of foo all we know is that the runtime argument will be an instance of + Object.Compiling this program gives the unchecked argument warning: + "unchecked match of List<? extends Number> with List when argument is + an instance of List at join point method-execution(void C.foo(Object)) [Xlint:uncheckedArgument]". + The advice will not execute at the call join point for c.foo("hi") since String + is not an instance of List. The advice will execute at the call join points + for c.foo(ls) and c.foo(ld) since in both cases the argument is an instance of + List. - - - - - - - To match specific parameterizations of a generic type, simply use the type that you require - the relevant object to be an instance of inside the pointcut expression. For example: - args(List<String>). - - - - Recall that runtime tests to determine whether an object is an instance of a parameterized - type are not possible due to erasure. Therefore AspectJ matching behaviour with - parameterized types for this, target and args is as follows. - - - For a parameterized type specified in an args pointcut expression: - - - If it can be statically determined that a given object will always be an instance - of the required type, then the pointcut expression matches. For example, given a method - with signature void foo(Set<BankAccount>) - and the pointcut expression - args(Set<BankAccount>) then any call or execution join point - for the method will be matched. - If it can be statically determined that a given object can never be an - instance of the required type, then the pointcut expression does not match. The - expression args(List<String>)will never match a call or - execution join point for a method taking a single argument of type List<Number> (it is not possible for - a type to implement two different parameterizations of the same interface). - If an object might be an instance of the required - type in some circumstances but not in others, then since it is not possible to perform - the runtime test, AspectJ deems the pointcut expression to match, but issues an - unchecked warning. This is analogous to the behaviour of the Java compiler when - converting between raw and parameterized types. Given a method that takes a single argument of type - List<? extends Number> and - pointcut expression args(List<Double>) then - the expression matches call and execution join points for the method, but with an unchecked warning. The warning can be suppressed - by annotating the associated advice with either @SuppressAjWarnings - or @SuppressAjWarnings("unchecked"). - - - - When using a parameterized type with the - this pointcut designator then a joinpoint is - matched if and only if at least one of the following conditions hold: - - - - - the runtime type of the this object extends or - implements the parameterized type. For example, an object with runtime type Foo, - defined as - class Foo implements List<String>, will match - this(List<String>). - - The parameterized "this" type is given using a generics wildcard in the pointcut - expression, and the bounds of - the generic runtime type of this are such that all valid parameterizations - are matched by the wildcard. For example, the pointcut expression - this(List<? extends Number>) will match a this - object of type class Foo<N extends Number> implements List<N>. - If some parameterization may be matched by the wildcard (for - example an object of type class Foo<N> implements List<N>) the pointcut - will match but with an unchecked warning. - - + + Combine a wildcard argument type with a signature pattern to avoid unchecked argument matches. In the example + below we use the signature pattern List<Number+> to match a call to any method taking + a List<Number>, List<Double>, List<Float> and so on. In addition the + signature pattern List<? extends Number+> can be used to match a call to a method + declared to take a List<? extends Number>, List<? extends Double> + and so on. Taken together, these restrict matching to only + those join points at which the argument is guaranteed to be an instance of List<? extends Number>. + + + aListOfSomeNumberType) + : (call(* foo(List)) || call(* foo(List))) + && args(aListOfSomeNumberType) { + // process list... + } + } + ]]> + + - - Using a parameterized type with the target pointcut designator is not supported in - the 1.5.0 release of AspectJ 5. If a future release of AspectJ supports this feature, the matching rules - will be the same as those described for args. The reason for the limitation is that - erasure makes it impossible in the general case to determine the parameterized target type at a call, get, or set - join point from Java bytecodes (only the raw type information is available). If a class file has been compiled - with variable debug information ("-g" or "-g:vars") then parameterized type information can - be recovered using a combination of data flow analysis and the local variable type table (potentially expensive). A more - efficient implementation would be possible for class files compiled from source by ajc. - + + Binding return values in after returning advice + + + After returning advice can be used to bind the return value from a matched join point. AspectJ 5 supports the use of + a parameterized type in the returning clause, with matching following the same rules as described for args. For + example, the following aspect matches the execution of any method returning a List, and makes + the returned list available to the body of the advice. + - - You've already seen some examples of using the generic wildcard ? - in parameterized type patterns. Since this, target and - args match using an instance of test, the generic wildcard can be useful in - specifying an acceptable range of parameterized types to match. When used in the binding - form, the same restrictions on operations permitted on the bound variable apply as when a - method declares a parameter with a wildcard type. For example, in the advice below, it is - a compilation error to attempt to add an element into the list aList. - + aList) : - execution(* org.xyz.Foo.*(..)) && args(aList) { - aList.add(5.0d); // Compilation error on this line - } + pointcut executionOfAnyMethodReturningAList() : execution(List *(..)); + + after() returning(List listOfSomeType) : executionOfAnyMethodReturningAList() { + for (Object element : listOfSomeType) { + // process element... + } + } + + } + ]]> + + + The pointcut uses the raw type pattern List, and hence it + matches methods returning any kind of list (List<String>, List<Double>, + and so on. We've chosen to bind the returned list as the parameterized type + List<?> in the advice since Java's type checking will now ensure + that we only perform safe operations on the list. + + Given the class + + foo(List listOfStrings) {...} + + public List bar(List listOfDoubles) {...} + + public List goo(List listOfSomeNumberType) {...} + + } ]]> - - - - Declaring pointcuts in generic classes + The advice in the aspect below will run after the execution of bar + and bind the return value. It will also run after the execution of goo and + bind the return value, but gives an uncheckedArgument warning during + compilation. It does not run after the execution of foo. + - - AspectJ permits pointcuts to be declared in classes as well as aspects. A pointcut defined - inside a generic class may not use the type variables of the class in the pointcut expression - (just as static members of a generic class may not use type variables). - For example: - + listOfDoubles) : execution(* C.*(..)) { + for(Double d : listOfDoubles) { + // process double... + } + } + + } + ]]> + + As with args you can guarantee that after returning advice only + executes on lists statically determinable to be of the right + type by specifying a return type pattern in the associated pointcut. The + @SuppressAjWarnings annotation can also be used if desired. + + - { - - ... - - // Not allowed - uses T in the pointcut expression - public pointcut fooOperationCall(T t) : - call(* Foo.*(T)) && args(t); + + Declaring pointcuts inside generic types + This language feature will not be supported until AspectJ 5 M4. - // permitted, but recommended to use an alternate variable name in the local - // type variable declaration - e.g. execution(...) - public pointcut fooExecution(Number n) : - execution(* Foo.*(T)) && args(n); - } - ]]> - - + Pointcuts can be declared in both classes and aspects. A pointcut declared in a generic + type may use the type variables of the type in which it is declared. All references to + a pointcut declared in a generic type from outside of that type must be via a parameterized type reference, + and not a raw type reference. + Consider the generic type Generic with a pointcut foo: + + + { + + /** + * matches the execution of any implementation of a method defined for T + */ + public pointcut foo() : execution(* T.*(..)); + + } + ]]> + + + + Such a pointcut must be refered to using a parameterized reference as shown + below. + + .foo() { + // ... + } + + // runs before the execution of any implementation of a method defined for YourClass + before() : Generic.foo() { + // ... + } + + // results in a compilation error - raw type reference + before() : Generic.foo() { } + + } + ]]> + + + Inter-type Declarations - AspectJ 5 allows type parameters to be used in inter-type declarations - either for declaring generic - methods and constructors, or for declaring members on generic types. The syntax for declaring generic - methods and constructors follows the regular AspectJ convention of simply qualifying the member name with - the target type. + AspectJ 5 supports the inter-type declaration of generic methods, and of members on + generic types. For generic methods, the syntax is exactly as for a regular method + declaration, with the addition of the target type specification: @@ -1273,18 +956,21 @@ String Foo.getName() {...} - Declares a getName method on behalf of the raw type Foo. It is + Declares a getName method on behalf of the type Foo. It is not possible to refer to the type parameters of Foo in such a declaration. - R Foo<Q, R>.getMagnitude() {...} + public R Foo<Q, R>.getMagnitude() {...} Declares a method getMagnitude on the generic class Foo. The method returns an instance of the type substituted for the second type parameter in an invocation - of Foo. + of Foo If Foo is declared as + Foo<T,N extends Number> {...} then this inter-type declaration is + equivalent to the declaration of a method public N getMagnitude() + within the body of Foo. @@ -1304,58 +990,18 @@ A parameterized type may not be the target of an inter-type declaration. This is because there is only one type (the generic type) regardless of how many different invocations (parameterizations) of that generic type are made in a program. Therefore it does not make sense to try and declare a member - on behalf of (say) Foo<String>, you can only declare members on the generic - type Foo<T>. - - - - If an inter-type member is declared inside a generic aspect, then the type parameter names from the - aspect declaration may be used in the signature specification of the inter-type declaration, but - not as type parameter names for a generic target type. In other words the example - that follows is legal: + on behalf of (say) Bar<String>, you can only declare members on the generic + type Bar<T>. - - { - - private T Foo.data; - - public T Foo.getData(T defaultValue) { - return (this.data != null ? data : defaultValue); - } - - } - ]]> - - Whereas the following example is not allowed and will report an error that a parameterized type may not be the - target of an inter-type declaration (since when the type parameter T in the aspect is subsituted with - say, String, then the target of the inter-type declaration becomes Goo<String>). - - - { - - private T Goo.data; - - public T Goo.getData(T defaultValue) { - return (this.data != null ? data : defaultValue); - } - - } - ]]> - - + Declare Parents Both generic and parameterized types can be used as the parent type in a declare parents statement (as long as the resulting type hierarchy would be well-formed in accordance with Java's sub-typing - rules). Generic types may also be used as the target type of a declare parents statement: - a type variable list follows the parents keyword in these cases to declare the - type variables in scope. - Some examples follow: + rules). Generic types may also be used as the target type of a declare parents statement. @@ -1370,24 +1016,6 @@ - - declare parents <T>: org.xyz..*<T> extends Base<T> - - All generic types declared in a package beginning with org.xyz and with a - single unbounded type parameter, extend the generic type Base<T>. - - - - - - declare parents <T>: org.xyz..*<T> extends Base<S> - - Results in a compilation error (unless S is a type) since S is - not bound in the type pattern. - - - - @@ -1399,7 +1027,9 @@ - Parameterized Aspects + Generic Aspects + + This feature will not be fully implemented until AspectJ5 M4. AspectJ 5 allows an abstract aspect to be declared as a generic type. Any concrete @@ -1455,83 +1085,105 @@ - - An exception to the rule that concrete aspects may not be generic is a pertypewithin aspect, which - may be declared with a single unbounded type parameter. This is discussed in the chapter on . The type parameter variables from a generic aspect declaration may be used in place of a type within any - member of the aspect. For example, we can declare a ParentChildRelationship aspect to + member of the aspect, except for within inter-type declarations. + For example, we can declare a ParentChildRelationship aspect to manage the bi-directional relationship between parent and child nodes as follows: { - - /** - * Parents contain a list of children - */ - private List P.children; - - /** - * Each child has a parent - */ - private P C.parent; - - /** - * Parents provide access to their children - */ - public List P.getChildren() { - return Collections.unmodifiableList(children); - } - - /** - * A child provides access to its parent - */ - public P C.getParent() { - return parent; + /** + * a generic aspect, we've used descriptive role names for the type variables + * (Parent and Child) but you could use anything of course + */ + public abstract aspect ParentChildRelationship { + + /** generic interface implemented by parents */ + interface ParentHasChildren{ + List getChildren(); + void addChild(C child); + void removeChild(C child); + } + + /** generic interface implemented by children */ + interface ChildHasParent

{ + P getParent(); + void setParent(P parent); + } + + /** ensure the parent type implements ParentHasChildren */ + declare parents: Parent implements ParentHasChildren; + + /** ensure the child type implements ChildHasParent */ + declare parents: Child implements ChildHasParent; + + // Inter-type declarations made on the *generic* interface types to provide + // default implementations. + + /** list of children maintained by parent */ + private List ParentHasChildren.children; + + /** reference to parent maintained by child */ + private P ChildHasParent

.parent; + + /** Default implementation of getChildren for the generic type ParentHasChildren */ + public List ParentHasChildren.getChildren() { + return Collections.unmodifiableList(children); + } + + /** Default implementation of getParent for the generic type ChildHasParent */ + public P ChildHasParent

.getParent() { + return parent; + } + + /** + * Default implementation of addChild, ensures that parent of child is + * also updated. + */ + public void ParentHasChildren.addChild(C child) { + if (child.parent != null) { + child.parent.removeChild(child); + } + children.add(child); + child.parent = this; + } + + /** + * Default implementation of removeChild, ensures that parent of + * child is also updated. + */ + public void ParentHasChildren.removeChild(C child) { + if (children.remove(child)) { + child.parent = null; } + } + + /** + * Default implementation of setParent for the generic type ChildHasParent. + * Ensures that this child is added to the children of the parent too. + */ + public void ChildHasParent

.setParent(P parent) { + parent.addChild(this); + } + + /** + * Matches at an addChild join point for the parent type P and child type C + */ + public pointcut addingChild(Parent p, Child c) : + execution(* Parent.addChild(Child)) && this(p) && args(c); - /** - * ensure bi-directional navigation on adding a child - */ - public void P.addChild(C child) { - if (child.parent != null) { - child.parent.removeChild(child); - } - children.add(child); - child.parent = this; - } - - /** - * ensure bi-directional navigation on removing a child - */ - public void P.removeChild(C child) { - if (children.remove(child)) { - child.parent = null; - } - } + /** + * Matches at a removeChild join point for the parent type P and child type C + */ + public pointcut removingChild(Parent p, Child c) : + execution(* Parent.removeChild(C)) && this(p) && args(c); - /** - * ensure bi-directional navigation on setting parent - */ - public void C.setParent(P parent) { - parent.addChild(this); - } - - public pointcut addingChild(P p, C c) : - execution(* P.addChild(C)) && this(p) && args(c); - - public pointcut removingChild(P p, C c) : - execution(* P.removeChild(C)) && this(p) && args(c); - } + } + ]]> - - Note in the above example how the type parameters P and C can be - used in inter-type declarations, pointcut expressions, and any other member of the aspect type. - - + The example aspect captures the protocol for managing a bi-directional parent-child relationship between any two types playing the role of parent and child. In a compiler implementation managing an abstract syntax @@ -1563,80 +1215,87 @@ - In a system managing files and folders, we could declare the concrete aspect: + In a system managing orders, we could declare the concrete aspect: { + public aspect OrderItemsInOrders extends ParentChildRelationship { } ]]> - As a result of this declaration, Folder gains members: + As a result of this declaration, Order gains members: - List<File> children - List<File> getChildren() - void addChild(File child) - void removeChild(File child) + List<OrderItem> children + List<OrderItem> getChildren() + void addChild(OrderItem child) + void removeChild(OrderItem child) - and File gains members: + and OrderItem gains members: - Folder parent - Folder getParent() - void setParent(Folder parent) + Order parent + Order getParent() + void setParent(Order parent) - When used in this way, the type parameters in a generic abstract aspect declare - roles, and the parameterization of the abstract aspect in the extends - clause binds types to those roles. This is a case where you may consider departing from the standard practice - of using a single letter to represent a type parameter, and instead use a role name. It makes no difference - to the compiler which option you choose of course. - - { - - /** - * Parents contain a list of children - */ - private List Parent.children; - - /** - * Each child has a parent - */ - private Parent Child.parent; - - /** - * Parents provide access to their children - */ - public List Parent.getChildren() { - return Collections.unmodifiableList(children); - } - - /** - * A child provides access to its parent - */ - public Parent Children.getParent() { - return parent; - } - - /** - * ensure bi-directional navigation on adding a child - */ - public void Parent.addChild(Child child) { - if (child.parent != null) { - child.parent.removeChild(child); - } - children.add(child); - child.parent = this; - } - ... - ]]> + A second example of an abstract aspect, this time for handling exceptions in a uniform + manner, is shown below: + + { + + /** + * method to be implemented by sub-aspects to handle thrown exceptions + */ + protected abstract void onException(T anException); + + /** + * to be defined by sub-aspects to specify the scope of exception handling + */ + protected abstract pointcut inExceptionHandlingScope(); + + /** + * soften T within the scope of the aspect + */ + declare soft: T : inExceptionHandlingScope(); + + /** + * bind an exception thrown in scope and pass it to the handler + */ + after() throwing (T anException) : inExceptionHandlingScope() { + onException(anException); + } + + } + ]]> + + Notice how the type variable T extends Throwable allows the + components of the aspect to be designed to work together in a type-safe manner. The + following concrete sub-aspect shows how the abstract aspect might be extended to + handle IOExceptions. + + { + + protected pointcut inExceptionHandlingScope() : + call(* doIO*(..)) && within(org.xyz..*); + + /** + * called whenever an IOException is thrown in scope. + */ + protected void onException(IOException ex) { + System.err.println("handled exception: " + ex.getMessage()); + throw new MyDomainException(ex); + } + } + ]]> + -- cgit v1.2.3