Java tutorial
/*** * Summer: An enhanced, non-invasive, and easy-to-use IoC container and * LTW-AOP framework enabling brand-new mocking capabilities in combination * with a lightweight rule engine and general meta expression language purely * written in Java. * It provides component composition at run-time as well as brand-new mocking * capabilities that are also applicable to binary third-party libraries, to name * only two of all its features. * There are barely limitations due to the lack of * Java in adding and removing fields and methods to and from a class at run-time. * * Copyright (C) 2011-2013 Sandro Sebastian Koll * * This file is part of Summer. * * Summer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Summer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Summer. If not, see <http://www.gnu.org/licenses/>. */ package org.summer.aop; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.summer.mel.ELContext; import org.summer.mel.ExpressionParser; import org.summer.mel.Tokenizer; import org.summer.mel.function.NAryHomogeneousFunction; import org.summer.mel.function.UnaryFunction; import org.summer.mel.operator.BinaryOperator; import org.summer.mel.operator.OperatorRegistry; import org.summer.mel.operator.UnaryOperator; import java.util.StringTokenizer; import static org.summer.mel.operator.Associativity.LeftAssociative; import static org.summer.mel.operator.UnaryOperator.Position.Before; /** * PointcutExpression Grammar: * <p/> * {@code NONTERMINAL PRODUCTION RULES}<pre> * pointcut-expression ::= pointcut-pattern * | and-pointcut-expression * | or-pointcut-expression * | not-pointcut-expression * | '(' pointcut-expression ')' * and-pointcut-expression ::= pointcut-expression ('&&' | 'and') pointcut-expression * or-pointcut-expression ::= pointcut-expression ('||' | 'or') pointcut-expression * not-pointcut-expression ::= ('!' | 'not') pointcut-expression * pointcut-pattern ::= execution-pattern | init-pattern | within-pattern * execution-pattern ::= 'execution(' * (modifiers-pattern ' ')? return-type-pattern ' ' declaring-method-pattern * (' ' throws-pattern)? * ')' * init-pattern ::= 'init(' * (visibility ' ')? declaring-type-pattern (' ' throws-pattern)? * ')' * within-pattern ::= 'within(' qualified-class-name-pattern | package-name-pattern ')' * declaring-type-pattern ::= qualified-class-name-pattern ('(' parameters-pattern? ')')? * declaring-method-pattern ::= qualified-class-name-pattern '.' identifier-pattern '(' parameters-pattern? ')' * modifiers-pattern ::= visibility | visibility ' static' | visibility ' final' * | visibility ' static final' | visibility ' final static' * parameters-pattern ::= '..' | parameter-pattern (',' parameter-pattern)* * parameter-pattern ::= primitive-type | qualified-class-name-pattern * throws-pattern ::= 'throws ' qualified-class-name-pattern (',' qualified-class-name-pattern)* * return-type-pattern ::= '*' | 'void' | primitive-type | qualified-class-name-pattern * qualified-class-name-pattern ::= '*' | (package-name-pattern '.')? identifier-pattern * package-name-pattern ::= ('*' | identifier | '..' identifier) (('.' | '..') ('*' | identifier))* * identifier-pattern ::= '*' | identifier * identifier ::= ('*' character+ | ('?' | starting-character) character*) * ('*' character+ | '?' character*)* '*'? * character ::= special-character | letter | digit * starting-character ::= special-character | letter</pre> * <p/> * {@code TERMINAL SYMBOLS}<br><pre> * visibility ::= '*' | 'public' | 'package-private' | 'protected' | 'private' * primitive-type ::= 'byte' | 'short' | 'int' | 'long' | 'float' | 'double' | 'boolean' | 'char' * special-character ::= '_' | '$' * letter ::= lowercase-letter | uppercase-letter * lowercase-letter ::= 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' * | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' * | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' * | '' * uppercase-letter ::= 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' * | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' * | '' | '' | '' | '' | '' | '' | '' | '' | '?' | '' | '?' | '' | '' * digit ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'</pre> * * @author Sandro Sebastian Koll */ public class PointcutExpressionParser { public static PointcutExpression parse(String pointcutExpression) { if (tokenizer == null) init(); return new PointcutExpression( ExpressionParser.parse(pointcutExpression.replaceAll("\\s+", " "), tokenizer)); } private static void init() { tokenizer = new Tokenizer(); ELContext context = tokenizer.getContext(); context.setDebugging(false); context.skipSpaces(); context.setParameterSeparator(" "); OperatorRegistry reg = context.getOperatorRegistry(); reg.registerLogicalOperators(); reg.registerOperator(new UnaryOperator<Boolean, Boolean>("not1", "not", Before, LeftAssociative) { @Override public Boolean apply(Boolean arg) { return !arg; } }, 0.1); reg.registerOperator(new UnaryOperator<Boolean, Boolean>("not2", "!", Before, LeftAssociative) { @Override public Boolean apply(Boolean arg) { return !arg; } }, 0.1); reg.registerOperator(new BinaryOperator<Boolean, Boolean, Boolean>("union1", " and ", LeftAssociative) { @Override public Boolean apply(Boolean arg1, Boolean arg2) { return arg1 && arg2; } }, 0.5); reg.registerOperator(new BinaryOperator<Boolean, Boolean, Boolean>("union2", " && ", LeftAssociative) { @Override public Boolean apply(Boolean arg1, Boolean arg2) { return arg1 && arg2; } }, 0.5); reg.registerOperator( new BinaryOperator<Boolean, Boolean, Boolean>("alternative1", " or ", LeftAssociative) { @Override public Boolean apply(Boolean arg1, Boolean arg2) { return arg1 || arg2; } }, 1.0); reg.registerOperator( new BinaryOperator<Boolean, Boolean, Boolean>("alternative2", " || ", LeftAssociative) { @Override public Boolean apply(Boolean arg1, Boolean arg2) { return arg1 || arg2; } }, 1.0); reg.registerOperator(new NAryHomogeneousFunction<Boolean, String>("execution-pattern", "execution(", ")") { @Override public Boolean apply(String[] args) { String className = (String) getContext().getContextObject("class"); int access = (Integer) getContext().getContextObject("access"); String method = (String) getContext().getContextObject("method"); String desc = (String) getContext().getContextObject("desc"); String[] exceptions = (String[]) getContext().getContextObject("exceptions"); String[] operands = new String[7]; switch (args.length) { case 1: operands[4] = args[0]; // declaring-method-pattern break; case 2: operands[3] = args[0]; // return-type-pattern operands[4] = args[1]; // declaring-method-pattern break; case 3: operands[0] = args[0]; // modifiers-pattern: visibility operands[3] = args[1]; // return-type-pattern operands[4] = args[2]; // declaring-method-pattern break; case 4: if ("throws".equals(args[2])) { operands[3] = args[0]; // return-type-pattern operands[4] = args[1]; // declaring-method-pattern operands[5] = args[2]; // throws-pattern: 'throws ' operands[6] = args[3]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* } else { operands[0] = args[0]; // modifiers-pattern: visibility operands[1] = args[1]; // modifiers-pattern: 'static' | 'final' operands[3] = args[2]; // return-type-pattern operands[4] = args[3]; // declaring-method-pattern } break; case 5: if ("throws".equals(args[3])) { operands[0] = args[0]; // modifiers-pattern: visibility operands[3] = args[1]; // return-type-pattern operands[4] = args[2]; // declaring-method-pattern operands[5] = args[3]; // throws-pattern: 'throws ' operands[6] = args[4]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* } else { operands[0] = args[0]; // modifiers-pattern: visibility operands[1] = args[1]; // modifiers-pattern: 'static' | 'final' operands[2] = args[2]; // modifiers-pattern: operands[1] == 'static' ? 'final' : 'static' operands[3] = args[3]; // return-type-pattern operands[4] = args[4]; // declaring-method-pattern } break; case 6: operands[0] = args[0]; // modifiers-pattern: visibility operands[1] = args[1]; // modifiers-pattern: 'static' | 'final' operands[3] = args[2]; // return-type-pattern operands[4] = args[3]; // declaring-method-pattern operands[5] = args[4]; // throws-pattern: 'throws ' operands[6] = args[5]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* break; case 7: operands[0] = args[0]; // modifiers-pattern: visibility operands[1] = args[1]; // modifiers-pattern: 'static' | 'final' operands[2] = args[2]; // modifiers-pattern: operands[1] == 'static' ? 'final' : 'static' operands[3] = args[3]; // return-type-pattern operands[4] = args[4]; // declaring-method-pattern operands[5] = args[5]; // throws-pattern: 'throws ' operands[6] = args[6]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* break; default: throw new RuntimeException("Too many or not enough arguments: Found " + args.length + " arguments, but 1 (inclusive) to 7 (inclusive) are required for an 'execution' pointcut expression"); } return matchModifiersPattern(access, operands[0], operands[1], operands[2]) && matchReturnTypePattern(desc, operands[3]) && matchDeclaringMethodPattern(className, method, desc, operands[4]) && matchThrowsPattern(exceptions, operands[5], operands[6]); } }, -0.1); reg.registerOperator(new NAryHomogeneousFunction<Boolean, String>("init-pattern", "init(", ")") { @Override public Boolean apply(String[] args) { if (!"<init>".equals((String) getContext().getContextObject("method"))) return false; String className = (String) getContext().getContextObject("class"); int access = (Integer) getContext().getContextObject("access"); String desc = (String) getContext().getContextObject("desc"); String[] exceptions = (String[]) getContext().getContextObject("exceptions"); String[] operands = new String[4]; switch (args.length) { case 1: operands[1] = args[0]; // declaring-type-pattern break; case 2: operands[0] = args[0]; // visibility operands[1] = args[1]; // declaring-type-pattern break; case 3: operands[1] = args[0]; // declaring-type-pattern operands[2] = args[1]; // throws-pattern: 'throws ' operands[3] = args[2]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* break; case 4: operands[0] = args[0]; // visibility operands[1] = args[1]; // declaring-type-pattern operands[2] = args[2]; // throws-pattern: 'throws ' operands[3] = args[3]; // throws-pattern: qualified-class-name-pattern (',' qualified-class-name-pattern)* break; default: throw new RuntimeException("Too many or not enough arguments: Found " + args.length + " arguments, but 1 (inclusive) to 4 (inclusive) are required for an 'init' pointcut expression"); } return matchVisibility(access, operands[0]) && matchDeclaringTypePattern(className, desc, operands[1]) && matchThrowsPattern(exceptions, operands[2], operands[3]); } }, -0.1); reg.registerOperator(new UnaryFunction<Boolean, String>("within-pattern", "within(", ")") { @Override public Boolean apply(String arg) { String className = (String) getContext().getContextObject("class"); return matchQualifiedClassNamePattern(className, arg) || matchPackageNamePattern(className, arg); } }, -0.1); } private static boolean matchVisibility(int access, String visibility) { if (visibility == null || "*".equals(visibility)) return true; // visibility is optional; the asterisk has the same effect as omitting the visibility switch (access) { case Opcodes.ACC_PUBLIC: return visibility.equals("public"); case Opcodes.ACC_PROTECTED: return visibility.equals("protected"); case Opcodes.ACC_PRIVATE: return visibility.equals("private"); case 0x0000: return visibility.equals("package-private"); } return false; } private static boolean matchQualifiedClassNamePattern(String className, String qualifiedClassNamePattern) { if (qualifiedClassNamePattern == null) return false; // qualified-class-name-pattern is required if (qualifiedClassNamePattern.equals("*")) return true; int index = className.lastIndexOf("."); String pckg = index == -1 ? null : className.substring(0, index); if (index != -1) className = className.substring(index + 1); index = qualifiedClassNamePattern.lastIndexOf("."); String packageNamePattern = index == -1 ? null : qualifiedClassNamePattern.substring(0, index); if (packageNamePattern != null && !matchPackageNamePattern(pckg, packageNamePattern)) return false; return matchIdentifierPattern(className, index == -1 ? qualifiedClassNamePattern : qualifiedClassNamePattern.substring(index + 1)); } private static boolean matchIdentifierPattern(String identifierToMatch, String identifierPattern) { if (identifierPattern == null) return false; // identifier-pattern is required if (identifierPattern.equals("*")) return true; return matchIdentifier(identifierToMatch, identifierPattern); } private static boolean matchIdentifier(String identifierToMatch, String identifier) { return (identifier.indexOf("*") != -1) ? identifierToMatch.matches(identifier.replace("*", ".*").replace("?", ".")) : identifierToMatch.equals(identifier); } private static boolean matchDeclaringTypePattern(String className, String desc, String declaringTypePattern) { if (declaringTypePattern == null) return false; // declaring-type-pattern is required int openParIndex = declaringTypePattern.indexOf("("); if (openParIndex == -1) return matchQualifiedClassNamePattern(className, declaringTypePattern); String qualifiedClassNamePattern = declaringTypePattern.substring(0, openParIndex); String parametersPattern = declaringTypePattern.substring(openParIndex + 1, declaringTypePattern.length() - 1); return matchQualifiedClassNamePattern(className, qualifiedClassNamePattern) && matchParametersPattern(desc, parametersPattern); } private static boolean matchModifiersPattern(int access, String modifier1, String modifier2, String modifier3) { if (modifier1 == null && modifier2 == null && modifier3 == null) return true; // modifiers-pattern is optional if ("*".equals(modifier1)) modifier1 = null; switch (access) { case Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("public")) && "static".equals(modifier2) && "final".equals(modifier3); case Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("protected")) && "static".equals(modifier2) && "final".equals(modifier3); case Opcodes.ACC_STATIC + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("package-private")) && "static".equals(modifier2) && "final".equals(modifier3); case Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("private")) && "static".equals(modifier2) && "final".equals(modifier3); case Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC: return (modifier1 == null || modifier1.equals("public")) && "static".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC: return (modifier1 == null || modifier1.equals("protected")) && "static".equals(modifier2) && modifier3 == null; case Opcodes.ACC_STATIC: return (modifier1 == null || modifier1.equals("package-private")) && "static".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC: return (modifier1 == null || modifier1.equals("private")) && "static".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("public")) && "final".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PROTECTED + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("protected")) && "final".equals(modifier2) && modifier3 == null; case Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("package-private")) && "final".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL: return (modifier1 == null || modifier1.equals("private")) && "final".equals(modifier2) && modifier3 == null; case Opcodes.ACC_PUBLIC: return (modifier1 == null || modifier1.equals("public")) && modifier2 == null && modifier3 == null; case Opcodes.ACC_PROTECTED: return (modifier1 == null || modifier1.equals("protected")) && modifier2 == null && modifier3 == null; case 0x0000: return (modifier1 == null || modifier1.equals("package-private")) && modifier2 == null && modifier3 == null; case Opcodes.ACC_PRIVATE: return (modifier1 == null || modifier1.equals("private")) && modifier2 == null && modifier3 == null; } return false; } private static boolean matchReturnTypePattern(String desc, String returnTypePattern) { if (returnTypePattern == null || returnTypePattern.equals("*")) return true; // return-type-pattern is optional; the asterisk has the same effect as omitting the return type Type returnType = Type.getReturnType(desc); if (matchVoid(returnType, returnTypePattern) || matchPrimitiveType(returnType, returnTypePattern)) return true; return returnType.getSort() != Type.OBJECT ? false : matchQualifiedClassNamePattern(returnType.getInternalName().replace('/', '.'), returnTypePattern); } private static boolean matchParametersPattern(String desc, String parametersPattern) { if (parametersPattern == null || parametersPattern.equals("..")) return true; // parameters-pattern is optional; the two dots have the same effect as omitting the parameters pattern StringTokenizer st = new StringTokenizer(parametersPattern, ",", false); Type[] argumentTypes = Type.getArgumentTypes(desc); int index = 0; while (st.hasMoreTokens()) { if (index >= argumentTypes.length || !matchParameterPattern(argumentTypes[index++], st.nextToken())) return false; } return true; } private static boolean matchParameterPattern(Type parameterType, String parameterPattern) { if (matchPrimitiveType(parameterType, parameterPattern)) return true; return parameterType.getSort() != Type.OBJECT ? false : matchQualifiedClassNamePattern(parameterType.getInternalName().replace('/', '.'), parameterPattern); } private static boolean matchDeclaringMethodPattern(String className, String method, String desc, String declaringMethodPattern) { if (declaringMethodPattern == null) return false; // declaring-method-pattern is required int openParIndex = declaringMethodPattern.indexOf("("); if (openParIndex == -1) return false; int lastDotIndex = declaringMethodPattern.substring(0, openParIndex).lastIndexOf("."); if (lastDotIndex == -1) return false; String qualifiedClassNamePattern = declaringMethodPattern.substring(0, lastDotIndex); String identifierPattern = declaringMethodPattern.substring(lastDotIndex + 1, openParIndex); String parametersPattern = declaringMethodPattern.substring(openParIndex + 1, declaringMethodPattern.length() - 1); return matchQualifiedClassNamePattern(className, qualifiedClassNamePattern) && matchIdentifierPattern(method, identifierPattern) && matchParametersPattern(desc, parametersPattern); } private static boolean matchThrowsPattern(String[] exceptions, String throwsKeyword, String qualifiedClassNamePattern) { if (throwsKeyword == null) return true; // throws-pattern is optional if (!throwsKeyword.equals("throws") || exceptions == null || exceptions.length == 0) return false; StringTokenizer st = new StringTokenizer(qualifiedClassNamePattern, ",", false); int index = 0; while (st.hasMoreTokens()) { if (index++ >= exceptions.length) return false; String token = st.nextToken(); boolean flag = false; for (String ex : exceptions) { if (matchQualifiedClassNamePattern(ex, token)) { flag = true; break; } } if (!flag) return false; } return true; } private static boolean matchPackageNamePattern(String packageToMatch, String packageNamePattern) { if (packageNamePattern == null) return false; // package-name-pattern is required if (packageNamePattern.equals("*")) return true; if (packageNamePattern.startsWith("*")) packageNamePattern = packageNamePattern.substring(1); boolean flag = packageNamePattern.startsWith(".."); packageNamePattern = packageNamePattern.replaceAll("\\.\\*\\.", "\\@[^@]#\\@") .replaceAll("\\.\\*$", "\\@[^@]#\\@").replace("..", "[^@]#\\@").replace(".", "\\.") .replace("*", "[^.]*").replace('?', '.').replace('@', '.').replace('#', '*'); if (flag) packageNamePattern = packageNamePattern + "|" + packageNamePattern.substring(7); return packageToMatch.matches(packageNamePattern); } private static boolean matchPrimitiveType(Type typeToMatch, String primitiveType) { switch (typeToMatch.getSort()) { case Type.BYTE: return primitiveType.equals("byte"); case Type.SHORT: return primitiveType.equals("short"); case Type.INT: return primitiveType.equals("int"); case Type.LONG: return primitiveType.equals("long"); case Type.FLOAT: return primitiveType.equals("float"); case Type.DOUBLE: return primitiveType.equals("double"); case Type.BOOLEAN: return primitiveType.equals("boolean"); case Type.CHAR: return primitiveType.equals("char"); default: return false; } } private static boolean matchVoid(Type typeToMatch, String voidType) { return voidType.equals("void") && typeToMatch.getSort() == Type.VOID; } private static Tokenizer tokenizer; }