Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * JBoss, Home of Professional Open Source.
   * Copyright 2013 Red Hat, Inc., and individual contributors
   * as indicated by the @author tags.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package io.undertow.predicate;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
Parser that can build a predicate from a string representation. The underlying syntax is quite simple, and example is shown below:

path["/MyPath"] or (method[value="POST"] and not headersPresent[value={Content-Type, "Content-Encoding"}, ignoreTrailer=true]

The following boolean operators are built in, listed in order or precedence: - not - and - or

They work pretty much as you would expect them to. All other tokens are taken to be predicate names. If the predicate does not require any parameters then the brackets can be omitted, otherwise they are mandatory.

If a predicate is only being passed a single parameter then the parameter name can be omitted. Strings can be enclosed in optional double or single quotations marks, and quotation marks can be escaped using \".

Array types are represented via a comma separated list of values enclosed in curly braces.

TODO: should we use antlr (or whatever) here? I don't really want an extra dependency just for this...

Author(s):
Stuart Douglas
 
 public class PredicateParser {
 
 
     public static final Predicate parse(String stringfinal ClassLoader classLoader) {
         final Map<StringPredicateBuilderbuilders = loadBuilders(classLoader);
         final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader);
         return parse(stringbuildersattributeParser);
     }
 
     private static Map<StringPredicateBuilderloadBuilders(final ClassLoader classLoader) {
         ServiceLoader<PredicateBuilderloader = ServiceLoader.load(PredicateBuilder.classclassLoader);
         final Map<StringPredicateBuilderret = new HashMap<StringPredicateBuilder>();
         for (PredicateBuilder builder : loader) {
             if (ret.containsKey(builder.name())) {
                 if (ret.get(builder.name()).getClass() != builder.getClass()) {
                     throw ..moreThanOnePredicateWithName(builder.name(), builder.getClass(), ret.get(builder.name()).getClass());
                 }
             } else {
                 ret.put(builder.name(), builder);
             }
         }
         return ret;
     }
 
     private static IllegalStateException error(final String stringint posString reason) {
         StringBuilder b = new StringBuilder();
         b.append(string);
         b.append('\n');
         for (int i = 0; i < pos; ++i) {
             b.append(' ');
         }
         b.append('^');
         throw ..errorParsingPredicateString(reasonb.toString());
     }
 
    static Predicate parse(final String stringfinal Map<StringPredicateBuilderbuildersfinal ExchangeAttributeParser attributeParser) {
        //shunting yard algorithm
        //gets rid or parentheses and fixes up operator ordering
        Deque<Tokentokens = tokenize(string);
        Deque<StringoperatorStack = new ArrayDeque<String>();
        //the output, consisting of predicate nodes and string representations of operators
        //it is a bit yuck mixing up the types, but whatever
        Deque<Objectoutput = new ArrayDeque<Object>();
        while (!tokens.isEmpty()) {
            Token token = tokens.poll();
            if (isSpecialChar(token.token)) {
                if (token.token.equals("(")) {
                    operatorStack.push("(");
                } else if (token.token.equals(")")) {
                    for (; ; ) {
                        String op = operatorStack.pop();
                        if (op == null) {
                            throw error(stringtoken.position"Unexpected end of input");
                        } else if (op.equals("(")) {
                            break;
                        } else {
                            output.push(op);
                        }
                    }
                } else {
                    throw error(stringtoken.position"Mismatched parenthesis");
                }
            } else {
                if (isOperator(token.token)) {
                    int prec = precedence(token.token);
                    String top = operatorStack.peek();
                    while (top != null) {
                        if (top.equals("(")) {
                            break;
                        }
                        int exitingPrec = precedence(top);
                        if (prec <= exitingPrec) {
                            output.push(operatorStack.pop());
                        } else {
                            break;
                        }
                        top = operatorStack.peek();
                    }
                    operatorStack.push(token.token);
                } else {
                    output.push(parsePredicate(stringtokentokensbuildersattributeParser));
                }
            }
        }
        while (!operatorStack.isEmpty()) {
            String op = operatorStack.pop();
            if (op.equals(")")) {
                throw error(stringstring.length(), "Mismatched parenthesis");
            }
            output.push(op);
        }
        //now we have our tokens
        Predicate predicate = collapseOutput(output.pop(), output).resolve();
        if (!output.isEmpty()) {
            throw error(string, 0, "Invalid expression");
        }
        return predicate;
    }
    private static Node collapseOutput(final Object tokenfinal Deque<Objecttokens) {
        if (token instanceof Node) {
            return (Nodetoken;
        } else if (token.equals("and")) {
            Node n1 = collapseOutput(tokens.pop(), tokens);
            Node n2 = collapseOutput(tokens.pop(), tokens);
            return new AndNode(n2n1);
        } else if (token.equals("or")) {
            Node n1 = collapseOutput(tokens.pop(), tokens);
            Node n2 = collapseOutput(tokens.pop(), tokens);
            return new OrNode(n2n1);
        } else if (token.equals("not")) {
            Node n1 = collapseOutput(tokens.pop(), tokens);
            return new NotNode(n1);
        } else {
            throw new IllegalStateException("Invalid operator " + token);
        }
    }
    private static Object parsePredicate(final String stringfinal Token tokenfinal Deque<Tokentokensfinal Map<StringPredicateBuilderbuildersfinal ExchangeAttributeParser attributeParser) {
        if (token.token.equals("true")) {
            return new PredicateNode(TruePredicate.instance());
        } else if (token.token.equals("false")) {
            return new PredicateNode(FalsePredicate.instance());
        } else {
            PredicateBuilder builder = builders.get(token.token);
            if (builder == null) {
                throw error(stringtoken.position"no predicate named " + token.token + " known predicates: " + builders.keySet());
            }
            Token next = tokens.peek();
            if (next.token.equals("[")) {
                final Map<StringObjectvalues = new HashMap<StringObject>();
                tokens.poll();
                next = tokens.poll();
                if (next == null) {
                    throw error(stringstring.length(), "Unexpected end of input");
                }
                if (next.token.equals("{")) {
                    return handleSingleArrayValue(stringbuildertokensnextattributeParser);
                }
                while (!next.token.equals("]")) {
                    Token equals = tokens.poll();
                    if (!equals.token.equals("=")) {
                        if (equals.token.equals("]") && values.isEmpty()) {
                            //single value case
                            return handleSingleValue(stringbuildernextattributeParser);
                        } else if (equals.token.equals(",")) {
                            tokens.push(equals);
                            tokens.push(next);
                            return handleSingleVarArgsValue(stringbuildertokensnextattributeParser);
                        }
                        throw error(stringequals.position"Unexpected token");
                    }
                    Token value = tokens.poll();
                    if (value == null) {
                        throw error(stringstring.length(), "Unexpected end of input");
                    }
                    if (value.token.equals("{")) {
                        values.put(next.tokenreadArrayType(stringtokensnextbuilderattributeParser"}"));
                    } else {
                        if (isOperator(value.token) || isSpecialChar(value.token)) {
                            throw error(stringvalue.position"Unexpected token");
                        }
                        Class<?> type = builder.parameters().get(next.token);
                        if (type == null) {
                            throw error(stringnext.position"Unexpected parameter " + next.token);
                        }
                        values.put(next.tokencoerceToType(stringvaluetypeattributeParser));
                    }
                    next = tokens.poll();
                    if (next == null) {
                        throw error(stringstring.length(), "Unexpected end of input");
                    }
                    if (!next.token.equals("]")) {
                        if (!next.token.equals(",")) {
                            throw error(stringstring.length(), "Expecting , or ]");
                        }
                        next = tokens.poll();
                        if (next == null) {
                            throw error(stringstring.length(), "Unexpected end of input");
                        }
                    }
                }
                checkParameters(stringnext.positionvaluesbuilder);
                return new BuilderNode(buildervalues);
            } else {
                if (isSpecialChar(next.token)) {
                    throw error(stringnext.position"Unexpected character");
                }
                return new BuilderNode(builder);
            }
        }
    }
    private static Node handleSingleArrayValue(final String stringfinal PredicateBuilder builderfinal Deque<Tokentokensfinal Token tokenfinal ExchangeAttributeParser attributeParser) {
        String sv = builder.defaultParameter();
        if (sv == null) {
            throw error(stringtoken.position"default parameter not supported");
        }
        Object array = readArrayType(stringtokensnew Token(svtoken.position), builderattributeParser"}");
        Token close = tokens.poll();
        if (!close.token.equals("]")) {
            throw error(stringclose.position"expected ]");
        }
        return new BuilderNode(builder, Collections.singletonMap(svarray));
    }
    private static Node handleSingleVarArgsValue(final String stringfinal PredicateBuilder builderfinal Deque<Tokentokensfinal Token tokenfinal ExchangeAttributeParser attributeParser) {
        String sv = builder.defaultParameter();
        if (sv == null) {
            throw error(stringtoken.position"default parameter not supported");
        }
        Object array = readArrayType(stringtokensnew Token(svtoken.position), builderattributeParser"]");
        return new BuilderNode(builder, Collections.singletonMap(svarray));
    }
    private static Object readArrayType(final String stringfinal Deque<TokentokensToken paramNamePredicateBuilder builderfinal ExchangeAttributeParser attributeParserString expectedEndToken) {
        Class<?> type = builder.parameters().get(paramName.token);
        if (type == null) {
            throw error(stringparamName.position"no parameter called " + paramName.token);
        } else if (!type.isArray()) {
            throw error(stringparamName.position"parameter is not an array type " + paramName.token);
        }
        Class<?> componentType = type.getComponentType();
        final List<Objectvalues = new ArrayList<Object>();
        Token token = tokens.poll();
        while (token != null) {
            Token commaOrEnd = tokens.poll();
            values.add(coerceToType(stringtokencomponentTypeattributeParser));
            if (commaOrEnd.token.equals(expectedEndToken)) {
                Object array = Array.newInstance(componentTypevalues.size());
                for (int i = 0; i < values.size(); ++i) {
                    Array.set(arrayivalues.get(i));
                }
                return array;
            } else if (!commaOrEnd.token.equals(",")) {
                throw error(stringcommaOrEnd.position"expected either , or }");
            }
            token = tokens.poll();
        }
        throw error(stringstring.length(), "unexpected end of input in array");
    }
    private static Object handleSingleValue(final String stringfinal PredicateBuilder builderfinal Token nextfinal ExchangeAttributeParser attributeParser) {
        String sv = builder.defaultParameter();
        if (sv == null) {
            throw error(stringnext.position"default parameter not supported");
        }
        Map<StringObjectvalues = Collections.singletonMap(svcoerceToType(stringnextbuilder.parameters().get(sv), attributeParser));
        checkParameters(stringnext.positionvaluesbuilder);
        return new BuilderNode(buildervalues);
    }
    private static void checkParameters(final String stringint posfinal Map<StringObjectvaluesfinal PredicateBuilder builder) {
        final Set<Stringrequired = new HashSet<String>(builder.requiredParameters());
        for (String key : values.keySet()) {
            required.remove(key);
        }
        if (!required.isEmpty()) {
            throw error(stringpos"Missing required parameters " + required);
        }
    }
    private static Object coerceToType(final String stringfinal Token tokenfinal Class<?> typefinal ExchangeAttributeParser attributeParser) {
        if (type.isArray()) {
            Object array = Array.newInstance(type.getComponentType(), 1);
            Array.set(array, 0, coerceToType(stringtokentype.getComponentType(), attributeParser));
            return array;
        }
        if (type == String.class) {
            return token.token;
        } else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
            return Boolean.valueOf(token.token);
        } else if (type.equals(Byte.class) || type.equals(byte.class)) {
            return Byte.valueOf(token.token);
        } else if (type.equals(Character.class) || type.equals(char.class)) {
            if (token.token.length() != 1) {
                throw error(stringtoken.position"Cannot coerce " + token.token + " to a Character");
            }
            return Character.valueOf(token.token.charAt(0));
        } else if (type.equals(Short.class) || type.equals(short.class)) {
            return Short.valueOf(token.token);
        } else if (type.equals(Integer.class) || type.equals(int.class)) {
            return Integer.valueOf(token.token);
        } else if (type.equals(Long.class) || type.equals(long.class)) {
            return Long.valueOf(token.token);
        } else if (type.equals(Float.class) || type.equals(float.class)) {
            return Float.valueOf(token.token);
        } else if (type.equals(Double.class) || type.equals(double.class)) {
            return Double.valueOf(token.token);
        } else if (type.equals(ExchangeAttribute.class)) {
            return attributeParser.parse(token.token);
        }
        return token.token;
    }
    private static int precedence(String operator) {
        if (operator.equals("not")) {
            return 3;
        } else if (operator.equals("and")) {
            return 2;
        } else if (operator.equals("or")) {
            return 1;
        }
        throw new IllegalStateException();
    }
    private static boolean isOperator(final String op) {
        return op.equals("and") || op.equals("or") || op.equals("not");
    }
    private static boolean isSpecialChar(String token) {
        if (token.length() != 1) {
            return false;
        }
        char c = token.charAt(0);
        switch (c) {
            case '(':
            case ')':
            case ',':
            case '=':
            case '{':
            case '}':
            case '[':
            case ']':
                return true;
            default:
                return false;
        }
    }
    static Deque<Tokentokenize(final String string) {
        char currentStringDelim = 0;
        boolean inVariable = false;
        int pos = 0;
        StringBuilder current = new StringBuilder();
        Deque<Tokenret = new ArrayDeque<Token>();
        while (pos < string.length()) {
            char c = string.charAt(pos);
            if (currentStringDelim != 0) {
                if (c == currentStringDelim && current.charAt(current.length() - 1) != '\\') {
                    ret.add(new Token(current.toString(), pos));
                    current.setLength(0);
                    currentStringDelim = 0;
                } else {
                    current.append(c);
                }
            } else {
                switch (c) {
                    case ' ':
                    case '\t': {
                        if (current.length() != 0) {
                            ret.add(new Token(current.toString(), pos));
                            current.setLength(0);
                        }
                        break;
                    }
                    case '(':
                    case ')':
                    case ',':
                    case '=':
                    case '[':
                    case ']':
                    case '{':
                    case '}': {
                        if (inVariable) {
                            current.append(c);
                            if (c == '}') {
                                inVariable = false;
                            }
                        } else {
                            if (current.length() != 0) {
                                ret.add(new Token(current.toString(), pos));
                                current.setLength(0);
                            }
                            ret.add(new Token("" + cpos));
                        }
                        break;
                    }
                    case '"':
                    case '\'': {
                        if (current.length() != 0) {
                            throw error(stringpos"Unexpected token");
                        }
                        currentStringDelim = c;
                        break;
                    }
                    case '%': {
                        current.append(c);
                        if (string.charAt(pos + 1) == '{') {
                            inVariable = true;
                        }
                        break;
                    }
                    default:
                        current.append(c);
                }
            }
            ++pos;
        }
        if (current.length() > 0) {
            ret.add(new Token(current.toString(), string.length()));
        }
        return ret;
    }
    static final class Token {
        final String token;
        final int position;
        private Token(final String tokenfinal int position) {
            this. = token;
            this. = position;
        }
    }
    private interface Node {
        Predicate resolve();
    }
    private static class AndNode implements Node {
        private final Node node1node2;
        private AndNode(final Node node1final Node node2) {
            this. = node1;
            this. = node2;
        }
        @Override
        public Predicate resolve() {
            return new AndPredicate(.resolve(), .resolve());
        }
    }
    private static class OrNode implements Node {
        private final Node node1node2;
        private OrNode(final Node node1final Node node2) {
            this. = node1;
            this. = node2;
        }
        @Override
        public Predicate resolve() {
            return new OrPredicate(.resolve(), .resolve());
        }
    }
    private static class NotNode implements Node {
        private final Node node;
        private NotNode(final Node node) {
            this. = node;
        }
        @Override
        public Predicate resolve() {
            return new NotPredicate(.resolve());
        }
    }
    private static class BuilderNode implements Node {
        private final PredicateBuilder builder;
        private final Map<StringObjectparameters;
        private BuilderNode(final PredicateBuilder builder) {
            this. = builder;
            this. = Collections.emptyMap();
        }
        private BuilderNode(final PredicateBuilder builderfinal Map<StringObjectparameters) {
            this. = builder;
            this. = parameters;
        }
        @Override
        public Predicate resolve() {
            return .build();
        }
    }
    private static class PredicateNode implements Node {
        private final Predicate predicate;
        private PredicateNode(final Predicate predicate) {
            this. = predicate;
        }
        @Override
        public Predicate resolve() {
            return ;
        }
    }
New to GrepCode? Check out our FAQ X