KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > aop > aspectj > AspectJAdviceParameterNameDiscoverer


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.aop.aspectj;
18
19 import java.lang.reflect.Constructor JavaDoc;
20 import java.lang.reflect.Method JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Set JavaDoc;
26
27 import org.aspectj.lang.JoinPoint;
28 import org.aspectj.lang.ProceedingJoinPoint;
29 import org.aspectj.weaver.tools.PointcutParser;
30 import org.aspectj.weaver.tools.PointcutPrimitive;
31
32 import org.springframework.core.ParameterNameDiscoverer;
33 import org.springframework.util.ClassUtils;
34 import org.springframework.util.StringUtils;
35
36 /**
37  * {@link ParameterNameDiscoverer} implementation that tries to deduce parameter names
38  * for an advice method from the pointcut expression, returning, and throwing clauses.
39  * If an unambiguous interpretation is not available, it returns <code>null</code>.
40  *
41  * <p>This class interprets arguments in the following way:
42  * <ol>
43  * <li>If the first parameter of the method is of type {@link JoinPoint}
44  * or {@link ProceedingJoinPoint}, it is assumed to be for passing
45  * <code>thisJoinPoint</code> to the advice, and the parameter name will
46  * be assigned the value <code>"thisJoinPoint"</code>.</li>
47  * <li>If the first parameter of the method is of type
48  * <code>JoinPoint.StaticPart</code>, it is assumed to be for passing
49  * <code>"thisJoinPointStaticPart"</code> to the advice, and the parameter name
50  * will be assigned the value <code>"thisJoinPointStaticPart"</code>.</li>
51  * <li>If a {@link #setThrowingName(String) throwingName} has been set, and
52  * there are no unbound arguments of type <code>Throwable+</code>, then an
53  * {@link IllegalArgumentException} is raised. If there is more than one
54  * unbound argument of type <code>Throwable+</code>, then an
55  * {@link AmbiguousBindingException} is raised. If there is exactly one
56  * unbound argument of type <code>Throwable+</code>, then the corresponding
57  * parameter name is assigned the value &lt;throwingName&gt;.</li>
58  * <li>If there remain unbound arguments, then the pointcut expression is
59  * examined. Let <code>a</code> be the number of annotation-based pointcut
60  * expressions (&#64;annotation, &#64;this, &#64;target, &#64;args,
61  * &#64;within, &#64;withincode) that are used in binding form. Usage in
62  * binding form has itself to be deduced: if the expression inside the
63  * pointcut is a single string literal that meets Java variable name
64  * conventions it is assumed to be a variable name. If <code>a</code> is
65  * zero we proceed to the next stage. If <code>a</code> &gt; 1 then an
66  * <code>AmbiguousBindingException</code> is raised. If <code>a</code> == 1,
67  * and there are no unbound arguments of type <code>Annotation+</code>,
68  * then an <code>IllegalArgumentException</code> is raised. if there is
69  * exactly one such argument, then the corresponding parameter name is
70  * assigned the value from the pointcut expression.</li>
71  * <li>If a returningName has been set, and there are no unbound arguments
72  * then an <code>IllegalArgumentException</code> is raised. If there is
73  * more than one unbound argument then an
74  * <code>AmbiguousBindingException</code> is raised. If there is exactly
75  * one unbound argument then the corresponding parameter name is assigned
76  * the value &lt;returningName&gt;.</li>
77  * <li>If there remain unbound arguments, then the pointcut expression is
78  * examined once more for <code>this</code>, <code>target</code>, and
79  * <code>args</code> pointcut expressions used in the binding form (binding
80  * forms are deduced as described for the annotation based pointcuts). If
81  * there remains more than one unbound argument of a primitive type (which
82  * can only be bound in <code>args</code>) then an
83  * <code>AmbiguousBindingException</code> is raised. If there is exactly
84  * one argument of a primitive type, then if exactly one <code>args</code>
85  * bound variable was found, we assign the corresponding parameter name
86  * the variable name. If there were no <code>args</code> bound variables
87  * found an <code>IllegalStateException</code> is raised. If there are
88  * multiple <code>args</code> bound variables, an
89  * <code>AmbiguousBindingException</code> is raised. At this point, if
90  * there remains more than one unbound argument we raise an
91  * <code>AmbiguousBindingException</code>. If there are no unbound arguments
92  * remaining, we are done. If there is exactly one unbound argument
93  * remaining, and only one candidate variable name unbound from
94  * <code>this</code>, <code>target</code>, or <code>args</code>, it is
95  * assigned as the corresponding parameter name. If there are multiple
96  * possibilities, an <code>AmbiguousBindingException</code> is raised.</li>
97  * </ol>
98  *
99  * <p>The behavior on raising an <code>IllegalArgumentException</code> or
100  * <code>AmbiguousBindingException</code> is configurable to allow this discoverer
101  * to be used as part of a chain-of-responsibility. By default the condition will
102  * be logged and the <code>getParameterNames(..)</code> method will simply return
103  * <code>null</code>. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
104  * property is set to <code>true</code>, the conditions will be thrown as
105  * <code>IllegalArgumentException</code> and <code>AmbiguousBindingException</code>,
106  * respectively.
107  *
108  * <p>Was that perfectly clear? ;)
109  *
110  * <p>Short version: If an unambiguous binding can be deduced, then it is.
111  * If the advice requirements cannot possibly be satisfied, then <code>null</code>
112  * is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
113  * property to <code>true</code>, descriptive exceptions will be thrown instead of
114  * returning <code>null</code> in the case that the parameter names cannot be discovered.
115  *
116  * @author Adrian Colyer
117  * @since 2.0
118  */

119 public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer {
120
121     private static final String JavaDoc ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation";
122
123     private static final String JavaDoc THIS_JOIN_POINT = "thisJoinPoint";
124     private static final String JavaDoc THIS_JOIN_POINT_STATIC_PART = "thisJoinPointStaticPart";
125
126     // Steps in the binding algorithm...
127
private static final int STEP_JOIN_POINT_BINDING = 1;
128     private static final int STEP_THROWING_BINDING = 2;
129     private static final int STEP_ANNOTATION_BINDING = 3;
130     private static final int STEP_RETURNING_BINDING = 4;
131     private static final int STEP_PRIMITIVE_ARGS_BINDING = 5;
132     private static final int STEP_THIS_TARGET_ARGS_BINDING = 6;
133     private static final int STEP_REFERENCE_PCUT_BINDING = 7;
134     private static final int STEP_FINISHED = 8;
135
136     private static final Set JavaDoc singleValuedAnnotationPcds = new HashSet JavaDoc();
137     private static final Set JavaDoc nonReferencePointcutTokens = new HashSet JavaDoc();
138
139     private static Class JavaDoc annotationClass;
140
141
142     static {
143         singleValuedAnnotationPcds.add("@this");
144         singleValuedAnnotationPcds.add("@target");
145         singleValuedAnnotationPcds.add("@within");
146         singleValuedAnnotationPcds.add("@withincode");
147         singleValuedAnnotationPcds.add("@annotation");
148
149         Set JavaDoc pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives();
150         for (Iterator JavaDoc iterator = pointcutPrimitives.iterator(); iterator.hasNext();) {
151             PointcutPrimitive primitive = (PointcutPrimitive) iterator.next();
152             nonReferencePointcutTokens.add(primitive.getName());
153         }
154         nonReferencePointcutTokens.add("&&");
155         nonReferencePointcutTokens.add("!");
156         nonReferencePointcutTokens.add("||");
157         nonReferencePointcutTokens.add("and");
158         nonReferencePointcutTokens.add("or");
159         nonReferencePointcutTokens.add("not");
160
161         try {
162             annotationClass = ClassUtils.forName(ANNOTATION_CLASS_NAME,
163                     AspectJAdviceParameterNameDiscoverer.class.getClassLoader());
164         }
165         catch (ClassNotFoundException JavaDoc ex) {
166             // Running on < JDK 1.5, this is OK...
167
annotationClass = null;
168         }
169     }
170
171
172     private boolean raiseExceptions;
173
174     /**
175      * If the advice is afterReturning, and binds the return value, this is the parameter name used.
176      */

177     private String JavaDoc returningName;
178
179     /**
180      * If the advice is afterThrowing, and binds the thrown value, this is the parameter name used.
181      */

182     private String JavaDoc throwingName;
183
184     /**
185      * The pointcut expression associated with the advice, as a simple String.
186      */

187     private String JavaDoc pointcutExpression;
188
189     private Class JavaDoc[] argumentTypes;
190
191     private String JavaDoc[] parameterNameBindings;
192
193     private int numberOfRemainingUnboundArguments;
194
195     private int algorithmicStep = STEP_JOIN_POINT_BINDING;
196
197
198     /**
199      * Create a new discoverer that attempts to discover parameter names
200      * from the given pointcut expression.
201      */

202     public AspectJAdviceParameterNameDiscoverer(String JavaDoc pointcutExpression) {
203         this.pointcutExpression = pointcutExpression;
204     }
205
206     /**
207      * Indicate whether {@link IllegalArgumentException} and {@link AmbiguousBindingException}
208      * must be thrown as appropriate in the case of failing to deduce advice parameter names.
209      * @param raiseExceptions <code>true</code> if exceptions are to be thrown
210      */

211     public void setRaiseExceptions(boolean raiseExceptions) {
212         this.raiseExceptions = raiseExceptions;
213     }
214
215     /**
216      * If <code>afterReturning</code> advice binds the return value, the
217      * returning variable name must be specified.
218      * @param returningName the name of the returning variable
219      */

220     public void setReturningName(String JavaDoc returningName) {
221         this.returningName = returningName;
222     }
223
224     /**
225      * If <code>afterThrowing</code> advice binds the thrown value, the
226      * throwing variable name must be specified.
227      * @param throwingName the name of the throwing variable
228      */

229     public void setThrowingName(String JavaDoc throwingName) {
230         this.throwingName = throwingName;
231     }
232
233     /**
234      * Deduce the parameter names for an advice method.
235      * <p>See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc}
236      * for this class for details of the algorithm used.
237      * @param method the target {@link Method}
238      * @return the parameter names
239      */

240     public String JavaDoc[] getParameterNames(Method JavaDoc method) {
241         this.argumentTypes = method.getParameterTypes();
242         this.numberOfRemainingUnboundArguments = this.argumentTypes.length;
243         this.parameterNameBindings = new String JavaDoc[this.numberOfRemainingUnboundArguments];
244         this.algorithmicStep = STEP_JOIN_POINT_BINDING;
245
246         int minimumNumberUnboundArgs = 0;
247         if (this.returningName != null) {
248             minimumNumberUnboundArgs++;
249         }
250         if (this.throwingName != null) {
251             minimumNumberUnboundArgs++;
252         }
253         if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) {
254             throw new IllegalStateException JavaDoc(
255                     "Not enough arguments in method to satisfy binding of returning and throwing variables");
256         }
257
258         try {
259             while ((this.numberOfRemainingUnboundArguments > 0) && (this.algorithmicStep < STEP_FINISHED)) {
260                 switch (this.algorithmicStep++) {
261                     case STEP_JOIN_POINT_BINDING:
262                         if (!maybeBindThisJoinPoint()) {
263                             maybeBindThisJoinPointStaticPart();
264                         }
265                         break;
266                     case STEP_THROWING_BINDING:
267                         maybeBindThrowingVariable();
268                         break;
269                     case STEP_ANNOTATION_BINDING:
270                         maybeBindAnnotationsFromPointcutExpression();
271                         break;
272                     case STEP_RETURNING_BINDING:
273                         maybeBindReturningVariable();
274                         break;
275                     case STEP_PRIMITIVE_ARGS_BINDING:
276                         maybeBindPrimitiveArgsFromPointcutExpression();
277                         break;
278                     case STEP_THIS_TARGET_ARGS_BINDING:
279                         maybeBindThisOrTargetOrArgsFromPointcutExpression();
280                         break;
281                     case STEP_REFERENCE_PCUT_BINDING:
282                         maybeBindReferencePointcutParameter();
283                         break;
284                     default:
285                         throw new IllegalStateException JavaDoc("Unknown algorithmic step: " + (this.algorithmicStep - 1));
286                 }
287             }
288         }
289         catch (AmbiguousBindingException ambigEx) {
290             if (this.raiseExceptions) {
291                 throw ambigEx;
292             }
293             else {
294                 return null;
295             }
296         }
297         catch (IllegalArgumentException JavaDoc ex) {
298             if (this.raiseExceptions) {
299                 throw ex;
300             }
301             else {
302                 return null;
303             }
304         }
305
306         if (this.numberOfRemainingUnboundArguments == 0) {
307             return this.parameterNameBindings;
308         }
309         else {
310             if (this.raiseExceptions) {
311                 throw new IllegalStateException JavaDoc("Failed to bind all argument names: " +
312                         this.numberOfRemainingUnboundArguments + " argument(s) could not be bound");
313             }
314             else {
315                 // convention for failing is to return null, allowing participation in a chain of responsibility
316
return null;
317             }
318         }
319     }
320
321     /**
322      * An advice method can never be a constructor in Spring.
323      * @return <code>null</code>
324      * @throws UnsupportedOperationException if
325      * {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to <code>true</code>
326      */

327     public String JavaDoc[] getParameterNames(Constructor JavaDoc ctor) {
328         if (this.raiseExceptions) {
329             throw new UnsupportedOperationException JavaDoc("An advice method can never be a constructor");
330         }
331         else {
332             // we return null rather than throw an exception so that we behave well
333
// in a chain-of-responsibility.
334
return null;
335         }
336     }
337
338
339     private void bindParameterName(int index, String JavaDoc name) {
340         this.parameterNameBindings[index] = name;
341         this.numberOfRemainingUnboundArguments--;
342     }
343
344     /**
345      * If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as
346      * parameter name and return true, else return false.
347      */

348     private boolean maybeBindThisJoinPoint() {
349         if ((this.argumentTypes[0] == JoinPoint.class) || (this.argumentTypes[0] == ProceedingJoinPoint.class)) {
350             bindParameterName(0, THIS_JOIN_POINT);
351             return true;
352         }
353         else {
354             return false;
355         }
356     }
357
358     private void maybeBindThisJoinPointStaticPart() {
359         if (this.argumentTypes[0] == JoinPoint.StaticPart.class) {
360             bindParameterName(0, THIS_JOIN_POINT_STATIC_PART);
361         }
362     }
363
364     /**
365      * If a throwing name was specified and there is exactly one choice remaining
366      * (argument that is a subtype of Throwable) then bind it.
367      */

368     private void maybeBindThrowingVariable() {
369         if (this.throwingName == null) {
370             return;
371         }
372
373         // So there is binding work to do...
374
int throwableIndex = -1;
375         for (int i = 0; i < this.argumentTypes.length; i++) {
376             if (isUnbound(i) && isSubtypeOf(Throwable JavaDoc.class, i)) {
377                 if (throwableIndex == -1) {
378                     throwableIndex = i;
379                 }
380                 else {
381                     // Second candidate we've found - ambiguous binding
382
throw new AmbiguousBindingException("Binding of throwing parameter '" +
383                             this.throwingName + "' is ambiguous: could be bound to argument " +
384                             throwableIndex + " or argument " + i);
385                 }
386             }
387         }
388
389         if (throwableIndex == -1) {
390             throw new IllegalStateException JavaDoc("Binding of throwing parameter '" + this.throwingName
391                     + "' could not be completed as no available arguments are a subtype of Throwable");
392         }
393         else {
394             bindParameterName(throwableIndex, this.throwingName);
395         }
396     }
397
398     /**
399      * If a returning variable was specified and there is only one choice remaining, bind it.
400      */

401     private void maybeBindReturningVariable() {
402         if (this.numberOfRemainingUnboundArguments == 0) {
403             throw new IllegalStateException JavaDoc(
404                     "Algorithm assumes that there must be at least one unbound parameter on entry to this method");
405         }
406
407         if (this.returningName != null) {
408             if (this.numberOfRemainingUnboundArguments > 1) {
409                 throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
410                         "' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates.");
411             }
412
413             // We're all set... find the unbound parameter, and bind it.
414
for (int i = 0; i < this.parameterNameBindings.length; i++) {
415                 if (this.parameterNameBindings[i] == null) {
416                     bindParameterName(i, this.returningName);
417                     break;
418                 }
419             }
420         }
421     }
422
423
424     /**
425      * Parse the string pointcut expression looking for:
426      * &#64;this, &#64;target, &#64;args, &#64;within, &#64;withincode, &#64;annotation.
427      * If we find one of these pointcut expressions, try and extract a candidate variable
428      * name (or variable names, in the case of args).
429      * <p>Some more support from AspectJ in doing this exercise would be nice... :)
430      */

431     private void maybeBindAnnotationsFromPointcutExpression() {
432         List JavaDoc varNames = new ArrayList JavaDoc();
433         String JavaDoc[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
434         for (int i = 0; i < tokens.length; i++) {
435             String JavaDoc toMatch = tokens[i];
436             int firstParenIndex = toMatch.indexOf("(");
437             if (firstParenIndex != -1) {
438                 toMatch = toMatch.substring(0, firstParenIndex);
439             }
440             if (singleValuedAnnotationPcds.contains(toMatch)) {
441                 PointcutBody body = getPointcutBody(tokens, i);
442                 i += body.numTokensConsumed;
443                 String JavaDoc varName = maybeExtractVariableName(body.text);
444                 if (varName != null) {
445                     varNames.add(varName);
446                 }
447             }
448             else if (tokens[i].startsWith("@args(") || tokens[i].equals("@args")) {
449                 PointcutBody body = getPointcutBody(tokens, i);
450                 i += body.numTokensConsumed;
451                 maybeExtractVariableNamesFromArgs(body.text, varNames);
452             }
453         }
454
455         bindAnnotationsFromVarNames(varNames);
456     }
457
458     /**
459      * Match the given list of extracted variable names to argument slots.
460      */

461     private void bindAnnotationsFromVarNames(List JavaDoc varNames) {
462         if (!varNames.isEmpty()) {
463             // we have work to do...
464
int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
465             if (numAnnotationSlots > 1) {
466                 throw new AmbiguousBindingException("Found " + varNames.size() +
467                         " potential annotation variable(s), and " +
468                         numAnnotationSlots + " potential argument slots");
469             }
470             else if (numAnnotationSlots == 1) {
471                 if (varNames.size() == 1) {
472                     // it's a match
473
findAndBind(annotationClass, (String JavaDoc) varNames.get(0));
474                 }
475                 else {
476                     // multiple candidate vars, but only one slot
477
throw new IllegalArgumentException JavaDoc("Found " + varNames.size() +
478                             " candidate annotation binding variables" +
479                             " but only one potential argument binding slot");
480                 }
481             }
482             else {
483                 // no slots so presume those candidate vars were actually type names
484
}
485         }
486     }
487
488     /*
489      * If the token starts meets Java identifier conventions, it's in.
490      */

491     private String JavaDoc maybeExtractVariableName(String JavaDoc candidateToken) {
492         if (candidateToken == null || candidateToken.equals("")) {
493             return null;
494         }
495         if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) &&
496                 Character.isLowerCase(candidateToken.charAt(0))) {
497             char[] tokenChars = candidateToken.toCharArray();
498             for (int i = 0; i < tokenChars.length; i++) {
499                 if (!Character.isJavaIdentifierPart(tokenChars[i])) {
500                     return null;
501                 }
502             }
503             return candidateToken;
504         }
505         else {
506             return null;
507         }
508     }
509
510     /**
511      * Given an args pointcut body (could be <code>args</code> or <code>at_args</code>),
512      * add any candidate variable names to the given list.
513      */

514     private void maybeExtractVariableNamesFromArgs(String JavaDoc argsSpec, List JavaDoc varNames) {
515         if (argsSpec == null) {
516             return;
517         }
518
519         String JavaDoc[] tokens = StringUtils.tokenizeToStringArray(argsSpec, ",");
520         for (int i = 0; i < tokens.length; i++) {
521             tokens[i] = StringUtils.trimWhitespace(tokens[i]);
522             String JavaDoc varName = maybeExtractVariableName(tokens[i]);
523             if (varName != null) {
524                 varNames.add(varName);
525             }
526         }
527     }
528
529     /**
530      * Parse the string pointcut expression looking for this(), target() and args() expressions.
531      * If we find one, try and extract a candidate variable name and bind it.
532      */

533     private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
534         if (this.numberOfRemainingUnboundArguments > 1) {
535             throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
536                     + " unbound args at this(),target(),args() binding stage, with no way to determine between them");
537         }
538
539         List JavaDoc varNames = new ArrayList JavaDoc();
540         String JavaDoc[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
541         for (int i = 0; i < tokens.length; i++) {
542             if (tokens[i].equals("this") ||
543                     tokens[i].startsWith("this(") ||
544                     tokens[i].equals("target") ||
545                     tokens[i].startsWith("target(")) {
546                 PointcutBody body = getPointcutBody(tokens, i);
547                 i += body.numTokensConsumed;
548                 String JavaDoc varName = maybeExtractVariableName(body.text);
549                 if (varName != null) {
550                     varNames.add(varName);
551                 }
552             }
553             else if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {
554                 PointcutBody body = getPointcutBody(tokens, i);
555                 i += body.numTokensConsumed;
556                 List JavaDoc candidateVarNames = new ArrayList JavaDoc();
557                 maybeExtractVariableNamesFromArgs(body.text, candidateVarNames);
558                 // we may have found some var names that were bound in previous primitive args binding step,
559
// filter them out...
560
for (Iterator JavaDoc iter = candidateVarNames.iterator(); iter.hasNext();) {
561                     String JavaDoc varName = (String JavaDoc) iter.next();
562                     if (!alreadyBound(varName)) {
563                         varNames.add(varName);
564                     }
565                 }
566             }
567         }
568
569
570         if (varNames.size() > 1) {
571             throw new AmbiguousBindingException("Found " + varNames.size() +
572                     " candidate this(), target() or args() variables but only one unbound argument slot");
573         }
574         else if (varNames.size() == 1) {
575             for (int j = 0; j < this.parameterNameBindings.length; j++) {
576                 if (isUnbound(j)) {
577                     bindParameterName(j, (String JavaDoc) varNames.get(0));
578                     break;
579                 }
580             }
581         }
582         // else varNames.size must be 0 and we have nothing to bind.
583
}
584
585     private void maybeBindReferencePointcutParameter() {
586         if (this.numberOfRemainingUnboundArguments > 1) {
587             throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
588                     + " unbound args at reference pointcut binding stage, with no way to determine between them");
589         }
590
591         List JavaDoc varNames = new ArrayList JavaDoc();
592         String JavaDoc[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
593         for (int i = 0; i < tokens.length; i++) {
594             String JavaDoc toMatch = tokens[i];
595             if (toMatch.startsWith("!")) {
596                 toMatch = toMatch.substring(1);
597             }
598             int firstParenIndex = toMatch.indexOf("(");
599             if (firstParenIndex != -1) {
600                 toMatch = toMatch.substring(0, firstParenIndex);
601             }
602             else {
603                 if (tokens.length < i + 2) {
604                     // no "(" and nothing following
605
continue;
606                 }
607                 else {
608                     String JavaDoc nextToken = tokens[i + 1];
609                     if (nextToken.charAt(0) != '(') {
610                         // next token is not "(" either, can't be a pc...
611
continue;
612                     }
613                 }
614
615             }
616
617             // eat the body
618
PointcutBody body = getPointcutBody(tokens, i);
619             i += body.numTokensConsumed;
620
621             if (!nonReferencePointcutTokens.contains(toMatch)) {
622                 // then it could be a reference pointcut
623
String JavaDoc varName = maybeExtractVariableName(body.text);
624                 if (varName != null) {
625                     varNames.add(varName);
626                 }
627             }
628         }
629
630         if (varNames.size() > 1) {
631             throw new AmbiguousBindingException("Found " + varNames.size() +
632                     " candidate reference pointcut variables but only one unbound argument slot");
633         }
634         else if (varNames.size() == 1) {
635             for (int j = 0; j < this.parameterNameBindings.length; j++) {
636                 if (isUnbound(j)) {
637                     bindParameterName(j, (String JavaDoc) varNames.get(0));
638                     break;
639                 }
640             }
641         }
642         // else varNames.size must be 0 and we have nothing to bind.
643
}
644
645     /*
646      * We've found the start of a binding pointcut at the given index into the
647      * token array. Now we need to extract the pointcut body and return it.
648      */

649     private PointcutBody getPointcutBody(String JavaDoc[] tokens, int startIndex) {
650         int numTokensConsumed = 0;
651         String JavaDoc currentToken = tokens[startIndex];
652         int bodyStart = currentToken.indexOf('(');
653         if (currentToken.charAt(currentToken.length() - 1) == ')') {
654             // It's an all in one... get the text between the first (and the last)
655
return new PointcutBody(0, currentToken.substring(bodyStart + 1, currentToken.length() - 1));
656         }
657         else {
658             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
659             if (bodyStart >= 0 && bodyStart != (currentToken.length() - 1)) {
660                 sb.append(currentToken.substring(bodyStart + 1));
661                 sb.append(" ");
662             }
663             numTokensConsumed++;
664             int currentIndex = startIndex + numTokensConsumed;
665             while (currentIndex < tokens.length) {
666                 if (tokens[currentIndex].equals("(")) {
667                     currentIndex++;
668                     continue;
669                 }
670
671                 if (tokens[currentIndex].endsWith(")")) {
672                     sb.append(tokens[currentIndex].substring(0, tokens[currentIndex].length() - 1));
673                     return new PointcutBody(numTokensConsumed, sb.toString().trim());
674                 }
675
676                 String JavaDoc toAppend = tokens[currentIndex];
677                 if (toAppend.startsWith("(")) {
678                     toAppend = toAppend.substring(1);
679                 }
680                 sb.append(toAppend);
681                 sb.append(" ");
682                 currentIndex++;
683                 numTokensConsumed++;
684             }
685
686         }
687
688         // We looked and failed...
689
return new PointcutBody(numTokensConsumed, null);
690     }
691
692     /**
693      * Match up args against unbound arguments of primitive types
694      */

695     private void maybeBindPrimitiveArgsFromPointcutExpression() {
696         int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
697         if (numUnboundPrimitives > 1) {
698             throw new AmbiguousBindingException("Found '" + numUnboundPrimitives +
699                     "' unbound primitive arguments with no way to distinguish between them.");
700         }
701         if (numUnboundPrimitives == 1) {
702             // Look for arg variable and bind it if we find exactly one...
703
List JavaDoc varNames = new ArrayList JavaDoc();
704             String JavaDoc[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
705             for (int i = 0; i < tokens.length; i++) {
706                 if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {
707                     PointcutBody body = getPointcutBody(tokens, i);
708                     i += body.numTokensConsumed;
709                     maybeExtractVariableNamesFromArgs(body.text, varNames);
710                 }
711             }
712             if (varNames.size() > 1) {
713                 throw new AmbiguousBindingException("Found " + varNames.size() +
714                         " candidate variable names but only one candidate binding slot when matching primitive args");
715             }
716             else if (varNames.size() == 1) {
717                 // 1 primitive arg, and one candidate...
718
for (int i = 0; i < this.argumentTypes.length; i++) {
719                     if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) {
720                         bindParameterName(i, (String JavaDoc) varNames.get(0));
721                         break;
722                     }
723                 }
724             }
725         }
726     }
727
728     /*
729      * Return true if the parameter name binding for the given parameter
730      * index has not yet been assigned.
731      */

732     private boolean isUnbound(int i) {
733         return this.parameterNameBindings[i] == null;
734     }
735
736     private boolean alreadyBound(String JavaDoc varName) {
737         for (int i = 0; i < this.parameterNameBindings.length; i++) {
738             if (!isUnbound(i) && varName.equals(this.parameterNameBindings[i])) {
739                 return true;
740             }
741         }
742         return false;
743     }
744
745     /*
746      * Return <code>true</code> if the given argument type is a subclass
747      * of the given supertype.
748      */

749     private boolean isSubtypeOf(Class JavaDoc supertype, int argumentNumber) {
750         return supertype.isAssignableFrom(this.argumentTypes[argumentNumber]);
751     }
752
753     private int countNumberOfUnboundAnnotationArguments() {
754         if (annotationClass == null) {
755             // We're running on a JDK < 1.5
756
return 0;
757         }
758
759         int count = 0;
760         for (int i = 0; i < this.argumentTypes.length; i++) {
761             if (isUnbound(i) && isSubtypeOf(annotationClass, i)) {
762                 count++;
763             }
764         }
765         return count;
766     }
767
768     private int countNumberOfUnboundPrimitiveArguments() {
769         int count = 0;
770         for (int i = 0; i < this.argumentTypes.length; i++) {
771             if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) {
772                 count++;
773             }
774         }
775         return count;
776     }
777
778     /*
779      * Find the argument index with the given type, and bind the given
780      * <code>varName</code> in that position.
781      */

782     private void findAndBind(Class JavaDoc argumentType, String JavaDoc varName) {
783         for (int i = 0; i < this.argumentTypes.length; i++) {
784             if (isUnbound(i) && isSubtypeOf(argumentType, i)) {
785                 bindParameterName(i, varName);
786                 return;
787             }
788         }
789         throw new IllegalStateException JavaDoc("Expected to find an unbound argument of type '" +
790                 argumentType.getName() + "'");
791     }
792
793
794     /**
795      * Simple struct to hold the extracted text from a pointcut body, together
796      * with the number of tokens consumed in extracting it.
797      */

798     private static class PointcutBody {
799
800         private int numTokensConsumed;
801
802         private String JavaDoc text;
803
804         public PointcutBody(int tokens, String JavaDoc text) {
805             this.numTokensConsumed = tokens;
806             this.text = text;
807         }
808     }
809
810
811     /**
812      * Thrown in response to an ambiguous binding being detected when
813      * trying to resolve a method's parameter names.
814      */

815     public static class AmbiguousBindingException extends RuntimeException JavaDoc {
816
817         /**
818          * Construct a new AmbiguousBindingException with the specified message.
819          * @param msg the detail message
820          */

821         public AmbiguousBindingException(String JavaDoc msg) {
822             super(msg);
823         }
824     }
825
826 }
827
Popular Tags