org.summer.aop.PointcutExpressionParser.java Source code

Java tutorial

Introduction

Here is the source code for org.summer.aop.PointcutExpressionParser.java

Source

/***
 * 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;
}