/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.aspectj.weaver.tools;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.weaver.IHasPosition;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.AtAjAttributes;
import org.aspectj.weaver.internal.tools.PointcutExpressionImpl;
import org.aspectj.weaver.internal.tools.TypePatternMatcherImpl;
import org.aspectj.weaver.patterns.AndPointcut;
import org.aspectj.weaver.patterns.CflowPointcut;
import org.aspectj.weaver.patterns.FormalBinding;
import org.aspectj.weaver.patterns.IScope;
import org.aspectj.weaver.patterns.KindedPointcut;
import org.aspectj.weaver.patterns.NotPointcut;
import org.aspectj.weaver.patterns.OrPointcut;
import org.aspectj.weaver.patterns.ParserException;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.Pointcut;
import org.aspectj.weaver.patterns.SimpleScope;
import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut;
import org.aspectj.weaver.patterns.ThisOrTargetPointcut;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.reflect.PointcutParameterImpl;
import org.aspectj.weaver.reflect.ReflectionWorld;
/**
* A PointcutParser can be used to build PointcutExpressions for a
* user-defined subset of AspectJ's pointcut language
*/
public class PointcutParser {
private ReflectionWorld world;
private ClassLoader classLoader;
private Set supportedPrimitives;
private Set pointcutDesignators = new HashSet();
/**
* @return a Set containing every PointcutPrimitive except
* if, cflow, and cflowbelow (useful for passing to
* PointcutParser constructor).
*/
public static Set getAllSupportedPointcutPrimitives() {
Set primitives = new HashSet();
primitives.add(PointcutPrimitive.ADVICE_EXECUTION);
primitives.add(PointcutPrimitive.ARGS);
primitives.add(PointcutPrimitive.CALL);
primitives.add(PointcutPrimitive.EXECUTION);
primitives.add(PointcutPrimitive.GET);
primitives.add(PointcutPrimitive.HANDLER);
primitives.add(PointcutPrimitive.INITIALIZATION);
primitives.add(PointcutPrimitive.PRE_INITIALIZATION);
primitives.add(PointcutPrimitive.SET);
primitives.add(PointcutPrimitive.STATIC_INITIALIZATION);
primitives.add(PointcutPrimitive.TARGET);
primitives.add(PointcutPrimitive.THIS);
primitives.add(PointcutPrimitive.WITHIN);
primitives.add(PointcutPrimitive.WITHIN_CODE);
primitives.add(PointcutPrimitive.AT_ANNOTATION);
primitives.add(PointcutPrimitive.AT_THIS);
primitives.add(PointcutPrimitive.AT_TARGET);
primitives.add(PointcutPrimitive.AT_ARGS);
primitives.add(PointcutPrimitive.AT_WITHIN);
primitives.add(PointcutPrimitive.AT_WITHINCODE);
primitives.add(PointcutPrimitive.REFERENCE);
return primitives;
}
/**
* Returns a pointcut parser that can parse the full AspectJ pointcut
* language with the following exceptions:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
* <p>When resolving types in pointcut expressions, the context classloader is used to find types.</p>
*/
public static PointcutParser getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution() {
PointcutParser p = new PointcutParser();
p.setClassLoader(Thread.currentThread().getContextClassLoader());
return p;
}
/**
* Returns a pointcut parser that can parse pointcut expressions built
* from a user-defined subset of AspectJ's supported pointcut primitives.
* The following restrictions apply:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
* <p>When resolving types in pointcut expressions, the context classloader is used to find types.</p>
* @param supportedPointcutKinds a set of PointcutPrimitives this parser
* should support
* @throws UnsupportedOperationException if the set contains if, cflow, or
* cflow below
*/
public static PointcutParser getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(Set supportedPointcutKinds) {
PointcutParser p = new PointcutParser(supportedPointcutKinds);
p.setClassLoader(Thread.currentThread().getContextClassLoader());
return p;
}
/**
* Returns a pointcut parser that can parse the full AspectJ pointcut
* language with the following exceptions:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
* <p>When resolving types in pointcut expressions, the given classloader is used to find types.</p>
*/
public static PointcutParser getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(ClassLoader classLoader) {
PointcutParser p = new PointcutParser();
p.setClassLoader(classLoader);
return p;
}
/**
* Returns a pointcut parser that can parse pointcut expressions built
* from a user-defined subset of AspectJ's supported pointcut primitives.
* The following restrictions apply:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
* <p>When resolving types in pointcut expressions, the given classloader is used to find types.</p>
* @param supportedPointcutKinds a set of PointcutPrimitives this parser
* should support
* @throws UnsupportedOperationException if the set contains if, cflow, or
* cflow below
*/
public static PointcutParser getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(Set supportedPointcutKinds, ClassLoader classLoader) {
PointcutParser p = new PointcutParser(supportedPointcutKinds);
p.setClassLoader(classLoader);
return p;
}
/**
* Create a pointcut parser that can parse the full AspectJ pointcut
* language with the following exceptions:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
*/
private PointcutParser() {
supportedPrimitives = getAllSupportedPointcutPrimitives();
setClassLoader(PointcutParser.class.getClassLoader());
}
/**
* Create a pointcut parser that can parse pointcut expressions built
* from a user-defined subset of AspectJ's supported pointcut primitives.
* The following restrictions apply:
* <ul>
* <li>The <code>if, cflow, and cflowbelow</code> pointcut designators are not supported
* <li>Pointcut expressions must be self-contained :- they cannot contain references
* to other named pointcuts
* <li>The pointcut expression must be anonymous with no formals allowed.
* </ul>
* @param supportedPointcutKinds a set of PointcutPrimitives this parser
* should support
* @throws UnsupportedOperationException if the set contains if, cflow, or
* cflow below
*/
private PointcutParser(Set/*<PointcutPrimitives>*/ supportedPointcutKinds) {
supportedPrimitives = supportedPointcutKinds;
for (Iterator iter = supportedPointcutKinds.iterator(); iter.hasNext();) {
PointcutPrimitive element = (PointcutPrimitive) iter.next();
if ((element == PointcutPrimitive.IF) ||
(element == PointcutPrimitive.CFLOW) ||
(element == PointcutPrimitive.CFLOW_BELOW)) {
throw new UnsupportedOperationException("Cannot handle if, cflow, and cflowbelow primitives");
}
}
setClassLoader(PointcutParser.class.getClassLoader());
}
/**
* Set the classloader that this parser should use for
* type resolution.
* @param aLoader
*/
private void setClassLoader(ClassLoader aLoader) {
this.classLoader = aLoader;
world = new ReflectionWorld(this.classLoader);
}
/**
* Set the lint properties for this parser from the
* given resource on the classpath.
* @param resourcePath path to a file containing aspectj
* lint properties
*/
public void setLintProperties(String resourcePath)throws IOException {
URL url = this.classLoader.getResource(resourcePath);
InputStream is = url.openStream();
Properties p = new Properties();
p.load(is);
setLintProperties(p);
}
/**
* Set the lint properties for this parser from the
* given properties set.
* @param properties
*/
public void setLintProperties(Properties properties) {
getWorld().getLint().setFromProperties(properties);
}
/**
* Register a new pointcut designator handler with this parser.
* This provides an extension mechansim for the integration of
* domain-specific pointcut designators with the AspectJ
* pointcut language.
* @param designatorHandler
*/
public void registerPointcutDesignatorHandler(PointcutDesignatorHandler designatorHandler) {
this.pointcutDesignators.add(designatorHandler);
}
/**
* Create a pointcut parameter of the given name and type.
* @param name
* @param type
* @return
*/
public PointcutParameter createPointcutParameter(String name, Class type) {
return new PointcutParameterImpl(name,type);
}
/**
* Parse the given pointcut expression.
* A global scope is assumed for resolving any type references, and the pointcut
* must contain no formals (variables to be bound).
* @throws UnsupportedPointcutPrimitiveException if the parser encounters a
* primitive pointcut expression of a kind not supported by this PointcutParser.
* @throws IllegalArgumentException if the expression is not a well-formed
* pointcut expression
*/
public PointcutExpression parsePointcutExpression(String expression)
throws UnsupportedPointcutPrimitiveException, IllegalArgumentException {
return parsePointcutExpression(expression,null,new PointcutParameter[0]);
}
/**
* Parse the given pointcut expression.
* The pointcut is resolved as if it had been declared inside the inScope class
* (this allows the pointcut to contain unqualified references to other pointcuts
* declared in the same type for example).
* The pointcut may contain zero or more formal parameters to be bound at matched
* join points.
* @throws UnsupportedPointcutPrimitiveException if the parser encounters a
* primitive pointcut expression of a kind not supported by this PointcutParser.
* @throws IllegalArgumentException if the expression is not a well-formed
* pointcut expression
*/
public PointcutExpression parsePointcutExpression(
String expression,
Class inScope,
PointcutParameter[] formalParameters)
throws UnsupportedPointcutPrimitiveException, IllegalArgumentException {
PointcutExpressionImpl pcExpr = null;
try {
PatternParser parser = new PatternParser(expression);
parser.setPointcutDesignatorHandlers(pointcutDesignators, world);
Pointcut pc = parser.parsePointcut();
validateAgainstSupportedPrimitives(pc,expression);
IScope resolutionScope = buildResolutionScope((inScope == null ? Object.class : inScope),formalParameters);
pc = pc.resolve(resolutionScope);
ResolvedType declaringTypeForResolution = null;
if (inScope != null) {
declaringTypeForResolution = getWorld().resolve(inScope.getName());
} else {
declaringTypeForResolution = ResolvedType.OBJECT.resolve(getWorld());
}
IntMap arity = new IntMap(formalParameters.length);
for (int i = 0; i < formalParameters.length; i++) {
arity.put(i, i);
}
pc = pc.concretize(declaringTypeForResolution, declaringTypeForResolution, arity);
validateAgainstSupportedPrimitives(pc,expression); // again, because we have now followed any ref'd pcuts
pcExpr = new PointcutExpressionImpl(pc,expression,formalParameters,getWorld());
} catch (ParserException pEx) {
throw new IllegalArgumentException(buildUserMessageFromParserException(expression,pEx));
} catch (ReflectionWorld.ReflectionWorldException rwEx) {
throw new IllegalArgumentException(rwEx.getMessage());
}
return pcExpr;
}
/**
* Parse the given aspectj type pattern, and return a
* matcher that can be used to match types using it.
* @param typePattern an aspectj type pattern
* @return a type pattern matcher that matches using the given
* pattern
* @throws IllegalArgumentException if the type pattern cannot
* be successfully parsed.
*/
public TypePatternMatcher parseTypePattern(String typePattern)
throws IllegalArgumentException {
try {
TypePattern tp = new PatternParser(typePattern).parseTypePattern();
tp.resolve(world);
return new TypePatternMatcherImpl(tp,world);
} catch (ParserException pEx) {
throw new IllegalArgumentException(buildUserMessageFromParserException(typePattern,pEx));
} catch (ReflectionWorld.ReflectionWorldException rwEx) {
throw new IllegalArgumentException(rwEx.getMessage());
}
}
private World getWorld() {
return world;
}
/* for testing */
Set getSupportedPrimitives() {
return supportedPrimitives;
}
/* for testing */
IMessageHandler setCustomMessageHandler(IMessageHandler aHandler) {
IMessageHandler current = getWorld().getMessageHandler();
getWorld().setMessageHandler(aHandler);
return current;
}
private IScope buildResolutionScope(Class inScope, PointcutParameter[] formalParameters) {
if (formalParameters == null) formalParameters = new PointcutParameter[0];
FormalBinding[] formalBindings = new FormalBinding[formalParameters.length];
for (int i = 0; i < formalBindings.length; i++) {
formalBindings[i] = new FormalBinding(UnresolvedType.forName(formalParameters[i].getType().getName()),formalParameters[i].getName(),i);
}
if (inScope == null) {
return new SimpleScope(getWorld(),formalBindings);
} else {
ResolvedType inType = getWorld().resolve(inScope.getName());
ISourceContext sourceContext = new ISourceContext() {
public ISourceLocation makeSourceLocation(IHasPosition position) {
return new SourceLocation(new File(""),0);
}
public ISourceLocation makeSourceLocation(int line, int offset) {
return new SourceLocation(new File(""),line);
}
public int getOffset() {
return 0;
}
};
return new AtAjAttributes.BindingScope(inType,sourceContext,formalBindings);
}
}
private void validateAgainstSupportedPrimitives(Pointcut pc, String expression) {
switch(pc.getPointcutKind()) {
case Pointcut.AND:
validateAgainstSupportedPrimitives(((AndPointcut)pc).getLeft(),expression);
validateAgainstSupportedPrimitives(((AndPointcut)pc).getRight(),expression);
break;
case Pointcut.ARGS:
if (!supportedPrimitives.contains(PointcutPrimitive.ARGS))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.ARGS);
break;
case Pointcut.CFLOW:
CflowPointcut cfp = (CflowPointcut) pc;
if (cfp.isCflowBelow()) {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.CFLOW_BELOW);
} else {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.CFLOW);
}
case Pointcut.HANDLER:
if (!supportedPrimitives.contains(PointcutPrimitive.HANDLER))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.HANDLER);
break;
case Pointcut.IF:
case Pointcut.IF_FALSE:
case Pointcut.IF_TRUE:
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.IF);
case Pointcut.KINDED:
validateKindedPointcut(((KindedPointcut)pc),expression);
break;
case Pointcut.NOT:
validateAgainstSupportedPrimitives(((NotPointcut)pc).getNegatedPointcut(),expression);
break;
case Pointcut.OR:
validateAgainstSupportedPrimitives(((OrPointcut)pc).getLeft(),expression);
validateAgainstSupportedPrimitives(((OrPointcut)pc).getRight(),expression);
break;
case Pointcut.THIS_OR_TARGET:
boolean isThis = ((ThisOrTargetPointcut)pc).isThis();
if (isThis && !supportedPrimitives.contains(PointcutPrimitive.THIS)) {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.THIS);
} else if (!supportedPrimitives.contains(PointcutPrimitive.TARGET)) {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.TARGET);
}
break;
case Pointcut.WITHIN:
if (!supportedPrimitives.contains(PointcutPrimitive.WITHIN))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.WITHIN);
break;
case Pointcut.WITHINCODE:
if (!supportedPrimitives.contains(PointcutPrimitive.WITHIN_CODE))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.WITHIN_CODE);
break;
case Pointcut.ATTHIS_OR_TARGET:
isThis = ((ThisOrTargetAnnotationPointcut)pc).isThis();
if (isThis && !supportedPrimitives.contains(PointcutPrimitive.AT_THIS)) {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_THIS);
} else if (!supportedPrimitives.contains(PointcutPrimitive.AT_TARGET)) {
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_TARGET);
}
break;
case Pointcut.ATARGS:
if (!supportedPrimitives.contains(PointcutPrimitive.AT_ARGS))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_ARGS);
break;
case Pointcut.ANNOTATION:
if (!supportedPrimitives.contains(PointcutPrimitive.AT_ANNOTATION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_ANNOTATION);
break;
case Pointcut.ATWITHIN:
if (!supportedPrimitives.contains(PointcutPrimitive.AT_WITHIN))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_WITHIN);
break;
case Pointcut.ATWITHINCODE:
if (!supportedPrimitives.contains(PointcutPrimitive.AT_WITHINCODE))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.AT_WITHINCODE);
break;
case Pointcut.REFERENCE:
if (!supportedPrimitives.contains(PointcutPrimitive.REFERENCE))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.REFERENCE);
break;
case Pointcut.USER_EXTENSION:
// always ok...
break;
case Pointcut.NONE: // deliberate fall-through
default:
throw new IllegalArgumentException("Unknown pointcut kind: " + pc.getPointcutKind());
}
}
private void validateKindedPointcut(KindedPointcut pc, String expression) {
Shadow.Kind kind = pc.getKind();
if ((kind == Shadow.MethodCall) || (kind == Shadow.ConstructorCall)) {
if (!supportedPrimitives.contains(PointcutPrimitive.CALL))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.CALL);
} else if ((kind == Shadow.MethodExecution) || (kind == Shadow.ConstructorExecution)) {
if (!supportedPrimitives.contains(PointcutPrimitive.EXECUTION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.EXECUTION);
} else if (kind == Shadow.AdviceExecution) {
if (!supportedPrimitives.contains(PointcutPrimitive.ADVICE_EXECUTION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.ADVICE_EXECUTION);
} else if (kind == Shadow.FieldGet) {
if (!supportedPrimitives.contains(PointcutPrimitive.GET))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.GET);
} else if (kind == Shadow.FieldSet) {
if (!supportedPrimitives.contains(PointcutPrimitive.SET))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.SET);
} else if (kind == Shadow.Initialization) {
if (!supportedPrimitives.contains(PointcutPrimitive.INITIALIZATION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.INITIALIZATION);
} else if (kind == Shadow.PreInitialization) {
if (!supportedPrimitives.contains(PointcutPrimitive.PRE_INITIALIZATION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.PRE_INITIALIZATION);
} else if (kind == Shadow.StaticInitialization) {
if (!supportedPrimitives.contains(PointcutPrimitive.STATIC_INITIALIZATION))
throw new UnsupportedPointcutPrimitiveException(expression, PointcutPrimitive.STATIC_INITIALIZATION);
}
}
private String buildUserMessageFromParserException(String pc, ParserException ex) {
StringBuffer msg = new StringBuffer();
msg.append("Pointcut is not well-formed: expecting '");
msg.append(ex.getMessage());
msg.append("'");
IHasPosition location = ex.getLocation();
msg.append(" at character position ");
msg.append(location.getStart());
msg.append("\n");
msg.append(pc);
msg.append("\n");
for (int i = 0; i < location.getStart(); i++) {
msg.append(" ");
}
for (int j=location.getStart(); j <= location.getEnd(); j++) {
msg.append("^");
}
msg.append("\n");
return msg.toString();
}
}