View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.expression;
9   
10  import org.codehaus.aspectwerkz.expression.ast.ASTRoot;
11  import org.codehaus.aspectwerkz.expression.ast.ASTPointcutReference;
12  import org.codehaus.aspectwerkz.expression.ast.ASTArgParameter;
13  import org.codehaus.aspectwerkz.expression.ast.ASTArgs;
14  import org.codehaus.aspectwerkz.expression.ast.ASTThis;
15  import org.codehaus.aspectwerkz.expression.ast.ASTTarget;
16  import org.codehaus.aspectwerkz.expression.ast.Node;
17  import org.codehaus.aspectwerkz.expression.ast.ASTCflow;
18  import org.codehaus.aspectwerkz.util.Strings;
19  import org.codehaus.aspectwerkz.exception.DefinitionException;
20  import org.codehaus.aspectwerkz.reflect.ClassInfo;
21  import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
22  import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
23  import org.codehaus.aspectwerkz.util.ContextClassLoader;
24  
25  import java.util.Iterator;
26  
27  import gnu.trove.TIntIntHashMap;
28  import gnu.trove.TObjectIntHashMap;
29  
30  /***
31   * A visitor to compute the args index of the target (matching) method/constructor which match the advice args. Note:
32   * extends the ExpressionVisitor. We should allow for optimization (all=TRUE) by assuming that args(..) does not depends
33   * of the matching context. The "(String a, String b):methodX && args(a,b) -OR- methodY && args(b,a)" expression should
34   * not be allowed then.
35   *
36   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
37   */
38  public class ArgsIndexVisitor extends ExpressionVisitor {
39  
40      /***
41       * Classloader used to perform type checks (for target / this bindings)
42       * A strong reference is enough since this visitor is not be referenced.
43       */
44      private ClassLoader m_classLoader;
45  
46      /***
47       * Update the given context with its runtime information (this, target, args).
48       * It should be called for each advice.
49       *
50       * @param expressionInfo
51       * @param context
52       */
53      public static void updateContextForRuntimeInformation(final ExpressionInfo expressionInfo,
54                                                            final ExpressionContext context,
55                                                            final ClassLoader loader) {
56          ArgsIndexVisitor visitor = new ArgsIndexVisitor(
57                  expressionInfo, expressionInfo.toString(),
58                  expressionInfo.getNamespace(),
59                  expressionInfo.getExpression().getASTRoot(),
60                  loader
61          );
62          visitor.match(context);
63      }
64  
65      private ArgsIndexVisitor(final ExpressionInfo expressionInfo,
66                               final String expression,
67                               final String namespace,
68                               final Node root,
69                               final ClassLoader loader) {
70          super(expressionInfo, expression, namespace, root);
71          m_classLoader = loader;
72      }
73  
74      //-- overrided methods to compute the args index mapping --//
75  
76      public Object visit(ASTPointcutReference node, Object data) {
77          // do the sub expression visit
78          ExpressionContext context = (ExpressionContext) data;
79          ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
80          ExpressionInfo expressionInfo = namespace.getExpressionInfo(node.getName());
81  
82          ArgsIndexVisitor referenced = new ArgsIndexVisitor(
83                  expressionInfo, expressionInfo.toString(),
84                  expressionInfo.getNamespace(),
85                  expressionInfo.getExpression().getASTRoot(),
86                  m_classLoader
87          );
88  
89          // keep track of the state we already had
90          String targetSoFar = context.m_targetBoundedName;
91          String thisSoFar = context.m_thisBoundedName;
92          boolean targetWithRuntimeCheckSoFar = context.m_targetWithRuntimeCheck;
93          TObjectIntHashMap exprIndexToTargetIndexSoFar = (TObjectIntHashMap) context.m_exprIndexToTargetIndex.clone();
94  
95          context.resetRuntimeState();
96          Boolean match = referenced.matchUndeterministic(context);
97  
98          // merge the state
99          if (context.m_targetBoundedName == null) {
100             context.m_targetBoundedName = targetSoFar;
101         } else if (targetSoFar != null) {
102             if (node.jjtGetNumChildren() == 1) {
103                 String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(0)).getTypePattern().getPattern();
104                 if (!targetSoFar.equals(referenceCallArg)) {
105                     throw new UnsupportedOperationException("should not occur");
106                 }
107             }
108         }
109         if (context.m_thisBoundedName == null) {
110             context.m_thisBoundedName = thisSoFar;
111         } else if (thisSoFar != null) {
112             if (node.jjtGetNumChildren() == 1) {
113                 String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(0)).getTypePattern().getPattern();
114                 if (!thisSoFar.equals(referenceCallArg)) {
115                     throw new UnsupportedOperationException("should not occur");
116                 }
117             }
118         }
119         if (!context.m_targetWithRuntimeCheck) {
120             // restore
121             context.m_targetWithRuntimeCheck = targetWithRuntimeCheckSoFar;
122         }
123         if (context.m_exprIndexToTargetIndex.isEmpty()) {
124             // restore
125             context.m_exprIndexToTargetIndex = exprIndexToTargetIndexSoFar;
126         } else if (!exprIndexToTargetIndexSoFar.isEmpty()) {
127             //should merge ?
128             throw new UnsupportedOperationException("should not occur");
129         }
130 
131 
132         // update the this and target bounded name from this last visit as well as args
133         TObjectIntHashMap exprToTargetArgIndexes = new TObjectIntHashMap();
134         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
135             String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(i)).getTypePattern().getPattern();
136             String referentArg = expressionInfo.getArgumentNameAtIndex(i);
137             if (referentArg.equals(context.m_targetBoundedName)) {
138                 context.m_targetBoundedName = referenceCallArg;
139                 assertIsInstanceOf(
140                         expressionInfo.getArgumentType(referentArg),
141                         m_expressionInfo.getArgumentType(referenceCallArg)
142                 );
143             } else if (referentArg.equals(context.m_thisBoundedName)) {
144                 context.m_thisBoundedName = referenceCallArg;
145                 assertIsInstanceOf(
146                         expressionInfo.getArgumentType(referentArg),
147                         m_expressionInfo.getArgumentType(referenceCallArg)
148                 );
149             } else {
150                 int adviceArgIndex = i;
151                 if (context.m_exprIndexToTargetIndex.containsKey(referentArg)) {
152                     int targetArgIndex = context.m_exprIndexToTargetIndex.get(referentArg);
153                     exprToTargetArgIndexes.put(referenceCallArg, targetArgIndex);
154                 }
155 
156             }
157         }
158         // merge with index found so far (inlined args() f.e.)
159         Object[] soFar = exprIndexToTargetIndexSoFar.keys();
160         for (int i = 0; i < soFar.length; i++) {
161             String name = (String) soFar[i];
162             if (!exprToTargetArgIndexes.containsKey(name)) {
163                 exprToTargetArgIndexes.put(name, exprIndexToTargetIndexSoFar.get(name));
164             }
165         }
166         context.m_exprIndexToTargetIndex = exprToTargetArgIndexes;
167         return match;
168     }
169 
170     public Object visit(ASTCflow node, Object data) {
171         // do the sub expression visit
172         ExpressionContext context = (ExpressionContext) data;
173         //ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
174         //ExpressionInfo expressionInfo = namespace.getExpressionInfo(node.getName());
175 
176         ExpressionInfo expressionInfo = new ExpressionInfo(
177                 node.jjtGetChild(0), m_namespace
178         );
179         expressionInfo.inheritPossibleArgumentFrom(m_expressionInfo);
180 
181         ArgsIndexVisitor referenced = new ArgsIndexVisitor(
182                 expressionInfo, "N/A",
183                 m_namespace,
184                 node.jjtGetChild(0),
185                 m_classLoader
186         );
187 
188         // keep track of the state we already had
189         String targetSoFar = context.m_targetBoundedName;
190         String thisSoFar = context.m_thisBoundedName;
191         boolean targetWithRuntimeCheckSoFar = context.m_targetWithRuntimeCheck;
192         TObjectIntHashMap exprIndexToTargetIndexSoFar = (TObjectIntHashMap) context.m_exprIndexToTargetIndex.clone();
193 
194         context.resetRuntimeState();
195         Boolean match = referenced.matchUndeterministic(context);
196 
197         // TODO FIX ME merge the state
198         if (context.m_targetBoundedName == null) {
199             context.m_targetBoundedName = targetSoFar;
200         } else if (targetSoFar != null) {
201             // cflow target
202         }
203         if (context.m_thisBoundedName == null) {
204             context.m_thisBoundedName = thisSoFar;
205         } else if (thisSoFar != null) {
206             // cflow this
207         }
208         if (!context.m_targetWithRuntimeCheck) {
209             // restore
210             context.m_targetWithRuntimeCheck = targetWithRuntimeCheckSoFar;
211         }
212         if (context.m_exprIndexToTargetIndex.isEmpty()) {
213             // restore
214             context.m_exprIndexToTargetIndex = exprIndexToTargetIndexSoFar;
215         } else if (!exprIndexToTargetIndexSoFar.isEmpty()) {
216             //should merge ?
217             for (int i = 0; i < exprIndexToTargetIndexSoFar.keys().length; i++) {
218                 Object o = exprIndexToTargetIndexSoFar.keys()[i];
219                 context.m_exprIndexToTargetIndex.put(o, exprIndexToTargetIndexSoFar.get(o));
220             }
221         }
222         return match;
223     }
224 
225     public Object visit(ASTArgs node, Object data) {
226         return super.visit(node, data);
227     }
228 
229     public Object visit(ASTArgParameter node, Object data) {
230         // do the visit
231         Boolean match = (Boolean) super.visit(node, data);
232 
233         // get the pointcut signature arg index of the arg we are visiting
234         int pointcutArgIndex = -1;
235         if (node.getTypePattern().getPattern().indexOf(".") < 0) {
236             pointcutArgIndex = m_expressionInfo.getArgumentIndex(node.getTypePattern().getPattern());
237         }
238 
239         // if match and we are visiting a parameter binding (not a type matching)
240         if (pointcutArgIndex >= 0 && Boolean.TRUE.equals(match)) {
241             ExpressionContext ctx = (ExpressionContext) data;
242             ctx.m_exprIndexToTargetIndex.put(
243                     m_expressionInfo.getArgumentNameAtIndex(pointcutArgIndex), ctx.getCurrentTargetArgsIndex()
244             );
245         }
246         return match;
247     }
248 
249     public Object visit(ASTThis node, Object data) {
250         // if the this(..) node identifier appears in the pointcut signature, we have a bounded type
251         if (m_expressionInfo.getArgumentType(node.getIdentifier()) != null) {
252             ExpressionContext ctx = (ExpressionContext) data;
253             if (ctx.m_thisBoundedName == null) {
254                 ctx.m_thisBoundedName = node.getIdentifier();
255             } else if (ctx.m_thisBoundedName != node.getIdentifier()) {
256                 throw new DefinitionException(
257                         "this(..) seems to be bounded to different bounded entities in \""
258                         + m_expressionInfo.toString() + "\" in " +
259                         m_expressionInfo.getNamespace()
260                         + " : found " + ctx.m_targetBoundedName + " and " +
261                         node.getIdentifier()
262                 );
263             }
264         }
265         return super.visit(node, data);
266     }
267 
268     public Object visit(ASTTarget node, Object data) {
269         // if the target(..) node identifier appears in the pointcut signature, we have a bounded type
270         if (m_expressionInfo.getArgumentType(node.getIdentifier()) != null) {
271             ExpressionContext ctx = (ExpressionContext) data;
272             if (ctx.m_targetBoundedName == null) {
273                 ctx.m_targetBoundedName = node.getIdentifier();
274             } else if (ctx.m_targetBoundedName != node.getIdentifier()) {
275                 throw new DefinitionException(
276                         "target(..) seems to be bounded to different bounded entities in \""
277                         + m_expressionInfo.toString() + "\" in " +
278                         m_expressionInfo.getNamespace()
279                         + " : found " + ctx.m_targetBoundedName + " and " +
280                         node.getIdentifier()
281                 );
282             }
283         }
284         // keep track if the result was undetermined: we will need a runtime check
285         Object match = super.visit(node, data);
286         if (match == null) {
287             ((ExpressionContext) data).m_targetWithRuntimeCheck = true;
288         }
289         return match;
290     }
291 
292     /***
293      * Ensure className is an instance of superClass name (both super class / interface just like "instanceof")
294      * Or throw an exception
295      *
296      * @param className
297      * @param superClassName
298      */
299     private void assertIsInstanceOf(String className, String superClassName) {
300         if (className.equals(superClassName)) {
301             ;//fine
302         } else {
303             // advice(Foo f) for pc(f) with pc(Object o) for example
304             // we need to ensure that Foo is an instance of Object
305             ClassInfo classInfo = AsmClassInfo.getClassInfo(className, m_classLoader);
306             boolean instanceOf = ClassInfoHelper.instanceOf(classInfo, superClassName);
307             if (!instanceOf) {
308                 throw new DefinitionException(
309                         "Attempt to reference a pointcut with incompatible object type: for \""
310                         + m_expression + "\" , " + className + " is not an instance of " +
311                         superClassName +
312                         "."
313                         + " Error occured in " + m_namespace
314                 );
315             }
316         }
317     }
318 }