/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.expression;
import org.codehaus.aspectwerkz.expression.ast.ASTRoot;
import org.codehaus.aspectwerkz.expression.ast.ASTPointcutReference;
import org.codehaus.aspectwerkz.expression.ast.ASTArgParameter;
import org.codehaus.aspectwerkz.expression.ast.ASTArgs;
import org.codehaus.aspectwerkz.expression.ast.ASTThis;
import org.codehaus.aspectwerkz.expression.ast.ASTTarget;
import org.codehaus.aspectwerkz.expression.ast.Node;
import org.codehaus.aspectwerkz.expression.ast.ASTCflow;
import org.codehaus.aspectwerkz.util.Strings;
import org.codehaus.aspectwerkz.exception.DefinitionException;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.util.ContextClassLoader;
import java.util.Iterator;
import gnu.trove.TIntIntHashMap;
import gnu.trove.TObjectIntHashMap;
/**
* A visitor to compute the args index of the target (matching) method/constructor which match the advice args. Note:
* extends the ExpressionVisitor. We should allow for optimization (all=TRUE) by assuming that args(..) does not depends
* of the matching context. The "(String a, String b):methodX && args(a,b) -OR- methodY && args(b,a)" expression should
* not be allowed then.
*
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
*/
public class ArgsIndexVisitor extends ExpressionVisitor {
/**
* Classloader used to perform type checks (for target / this bindings)
* A strong reference is enough since this visitor is not be referenced.
*/
private ClassLoader m_classLoader;
/**
* Update the given context with its runtime information (this, target, args).
* It should be called for each advice.
*
* @param expressionInfo
* @param context
*/
public static void updateContextForRuntimeInformation(final ExpressionInfo expressionInfo,
final ExpressionContext context,
final ClassLoader loader) {
ArgsIndexVisitor visitor = new ArgsIndexVisitor(
expressionInfo, expressionInfo.toString(),
expressionInfo.getNamespace(),
expressionInfo.getExpression().getASTRoot(),
loader
);
visitor.match(context);
}
private ArgsIndexVisitor(final ExpressionInfo expressionInfo,
final String expression,
final String namespace,
final Node root,
final ClassLoader loader) {
super(expressionInfo, expression, namespace, root);
m_classLoader = loader;
}
//-- overrided methods to compute the args index mapping --//
public Object visit(ASTPointcutReference node, Object data) {
// do the sub expression visit
ExpressionContext context = (ExpressionContext) data;
ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
ExpressionInfo expressionInfo = namespace.getExpressionInfo(node.getName());
ArgsIndexVisitor referenced = new ArgsIndexVisitor(
expressionInfo, expressionInfo.toString(),
expressionInfo.getNamespace(),
expressionInfo.getExpression().getASTRoot(),
m_classLoader
);
// keep track of the state we already had
String targetSoFar = context.m_targetBoundedName;
String thisSoFar = context.m_thisBoundedName;
boolean targetWithRuntimeCheckSoFar = context.m_targetWithRuntimeCheck;
TObjectIntHashMap exprIndexToTargetIndexSoFar = (TObjectIntHashMap) context.m_exprIndexToTargetIndex.clone();
context.resetRuntimeState();
Boolean match = referenced.matchUndeterministic(context);
// merge the state
if (context.m_targetBoundedName == null) {
context.m_targetBoundedName = targetSoFar;
} else if (targetSoFar != null) {
if (node.jjtGetNumChildren() == 1) {
String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(0)).getTypePattern().getPattern();
if (!targetSoFar.equals(referenceCallArg)) {
throw new UnsupportedOperationException("should not occur");
}
}
}
if (context.m_thisBoundedName == null) {
context.m_thisBoundedName = thisSoFar;
} else if (thisSoFar != null) {
if (node.jjtGetNumChildren() == 1) {
String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(0)).getTypePattern().getPattern();
if (!thisSoFar.equals(referenceCallArg)) {
throw new UnsupportedOperationException("should not occur");
}
}
}
if (!context.m_targetWithRuntimeCheck) {
// restore
context.m_targetWithRuntimeCheck = targetWithRuntimeCheckSoFar;
}
if (context.m_exprIndexToTargetIndex.isEmpty()) {
// restore
context.m_exprIndexToTargetIndex = exprIndexToTargetIndexSoFar;
} else if (!exprIndexToTargetIndexSoFar.isEmpty()) {
//should merge ?
throw new UnsupportedOperationException("should not occur");
}
// update the this and target bounded name from this last visit as well as args
TObjectIntHashMap exprToTargetArgIndexes = new TObjectIntHashMap();
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
String referenceCallArg = ((ASTArgParameter) node.jjtGetChild(i)).getTypePattern().getPattern();
String referentArg = expressionInfo.getArgumentNameAtIndex(i);
if (referentArg.equals(context.m_targetBoundedName)) {
context.m_targetBoundedName = referenceCallArg;
assertIsInstanceOf(
expressionInfo.getArgumentType(referentArg),
m_expressionInfo.getArgumentType(referenceCallArg)
);
} else if (referentArg.equals(context.m_thisBoundedName)) {
context.m_thisBoundedName = referenceCallArg;
assertIsInstanceOf(
expressionInfo.getArgumentType(referentArg),
m_expressionInfo.getArgumentType(referenceCallArg)
);
} else {
int adviceArgIndex = i;
if (context.m_exprIndexToTargetIndex.containsKey(referentArg)) {
int targetArgIndex = context.m_exprIndexToTargetIndex.get(referentArg);
exprToTargetArgIndexes.put(referenceCallArg, targetArgIndex);
}
}
}
// merge with index found so far (inlined args() f.e.)
Object[] soFar = exprIndexToTargetIndexSoFar.keys();
for (int i = 0; i < soFar.length; i++) {
String name = (String) soFar[i];
if (!exprToTargetArgIndexes.containsKey(name)) {
exprToTargetArgIndexes.put(name, exprIndexToTargetIndexSoFar.get(name));
}
}
context.m_exprIndexToTargetIndex = exprToTargetArgIndexes;
return match;
}
public Object visit(ASTCflow node, Object data) {
// do the sub expression visit
ExpressionContext context = (ExpressionContext) data;
//ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
//ExpressionInfo expressionInfo = namespace.getExpressionInfo(node.getName());
ExpressionInfo expressionInfo = new ExpressionInfo(
node.jjtGetChild(0), m_namespace
);
expressionInfo.inheritPossibleArgumentFrom(m_expressionInfo);
ArgsIndexVisitor referenced = new ArgsIndexVisitor(
expressionInfo, "N/A",
m_namespace,
node.jjtGetChild(0),
m_classLoader
);
// keep track of the state we already had
String targetSoFar = context.m_targetBoundedName;
String thisSoFar = context.m_thisBoundedName;
boolean targetWithRuntimeCheckSoFar = context.m_targetWithRuntimeCheck;
TObjectIntHashMap exprIndexToTargetIndexSoFar = (TObjectIntHashMap) context.m_exprIndexToTargetIndex.clone();
context.resetRuntimeState();
Boolean match = referenced.matchUndeterministic(context);
// TODO FIX ME merge the state
if (context.m_targetBoundedName == null) {
context.m_targetBoundedName = targetSoFar;
} else if (targetSoFar != null) {
// cflow target
}
if (context.m_thisBoundedName == null) {
context.m_thisBoundedName = thisSoFar;
} else if (thisSoFar != null) {
// cflow this
}
if (!context.m_targetWithRuntimeCheck) {
// restore
context.m_targetWithRuntimeCheck = targetWithRuntimeCheckSoFar;
}
if (context.m_exprIndexToTargetIndex.isEmpty()) {
// restore
context.m_exprIndexToTargetIndex = exprIndexToTargetIndexSoFar;
} else if (!exprIndexToTargetIndexSoFar.isEmpty()) {
//should merge ?
for (int i = 0; i < exprIndexToTargetIndexSoFar.keys().length; i++) {
Object o = exprIndexToTargetIndexSoFar.keys()[i];
context.m_exprIndexToTargetIndex.put(o, exprIndexToTargetIndexSoFar.get(o));
}
}
return match;
}
public Object visit(ASTArgs node, Object data) {
return super.visit(node, data);
}
public Object visit(ASTArgParameter node, Object data) {
// do the visit
Boolean match = (Boolean) super.visit(node, data);
// get the pointcut signature arg index of the arg we are visiting
int pointcutArgIndex = -1;
if (node.getTypePattern().getPattern().indexOf(".") < 0) {
pointcutArgIndex = m_expressionInfo.getArgumentIndex(node.getTypePattern().getPattern());
}
// if match and we are visiting a parameter binding (not a type matching)
if (pointcutArgIndex >= 0 && Boolean.TRUE.equals(match)) {
ExpressionContext ctx = (ExpressionContext) data;
ctx.m_exprIndexToTargetIndex.put(
m_expressionInfo.getArgumentNameAtIndex(pointcutArgIndex), ctx.getCurrentTargetArgsIndex()
);
}
return match;
}
public Object visit(ASTThis node, Object data) {
// if the this(..) node identifier appears in the pointcut signature, we have a bounded type
if (m_expressionInfo.getArgumentType(node.getIdentifier()) != null) {
ExpressionContext ctx = (ExpressionContext) data;
if (ctx.m_thisBoundedName == null) {
ctx.m_thisBoundedName = node.getIdentifier();
} else if (ctx.m_thisBoundedName != node.getIdentifier()) {
throw new DefinitionException(
"this(..) seems to be bounded to different bounded entities in \""
+ m_expressionInfo.toString() + "\" in " +
m_expressionInfo.getNamespace()
+ " : found " + ctx.m_targetBoundedName + " and " +
node.getIdentifier()
);
}
}
return super.visit(node, data);
}
public Object visit(ASTTarget node, Object data) {
// if the target(..) node identifier appears in the pointcut signature, we have a bounded type
if (m_expressionInfo.getArgumentType(node.getIdentifier()) != null) {
ExpressionContext ctx = (ExpressionContext) data;
if (ctx.m_targetBoundedName == null) {
ctx.m_targetBoundedName = node.getIdentifier();
} else if (ctx.m_targetBoundedName != node.getIdentifier()) {
throw new DefinitionException(
"target(..) seems to be bounded to different bounded entities in \""
+ m_expressionInfo.toString() + "\" in " +
m_expressionInfo.getNamespace()
+ " : found " + ctx.m_targetBoundedName + " and " +
node.getIdentifier()
);
}
}
// keep track if the result was undetermined: we will need a runtime check
Object match = super.visit(node, data);
if (match == null) {
((ExpressionContext) data).m_targetWithRuntimeCheck = true;
}
return match;
}
/**
* Ensure className is an instance of superClass name (both super class / interface just like "instanceof")
* Or throw an exception
*
* @param className
* @param superClassName
*/
private void assertIsInstanceOf(String className, String superClassName) {
if (className.equals(superClassName)) {
;//fine
} else {
// advice(Foo f) for pc(f) with pc(Object o) for example
// we need to ensure that Foo is an instance of Object
ClassInfo classInfo = AsmClassInfo.getClassInfo(className, m_classLoader);
boolean instanceOf = ClassInfoHelper.instanceOf(classInfo, superClassName);
if (!instanceOf) {
throw new DefinitionException(
"Attempt to reference a pointcut with incompatible object type: for \""
+ m_expression + "\" , " + className + " is not an instance of " +
superClassName +
"."
+ " Error occured in " + m_namespace
);
}
}
}
}