/* * Copyright 2016 Skynav, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.skynav.ttv.util; import java.nio.CharBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import com.skynav.xml.helpers.XML; public class Condition { public interface EvaluatorState { boolean hasBinding(String identifier); Object getBinding(String identifier); void setBinding(String identifier, Object value); } public interface EvaluatorFunction { Object apply(EvaluatorState state, List<Object> arguments); } public interface Evaluator { Object evaluate(ExpressionEvaluator ee, Expression e); } private Expression expression; private Condition(Expression expression) { assert expression != null; this.expression = expression; } public boolean evaluate(EvaluatorState state) { Object o = new ExpressionEvaluator(state).evaluate(expression); Class<?> operandClass = Boolean.class; if (checkCompatibleOperand(o, operandClass)) { o = convertCompatibleOperand(o, operandClass); if (o instanceof Boolean) return ((Boolean) o).booleanValue(); else throw new IllegalStateException(); } else throw new EvaluatorException("condition expression evaluates to non-boolean compatible value " + o); } @Override public String toString() { return expression.toString(); } public static Condition valueOf(String condition) throws ParserException { Parser p = new Parser(); return p.parse(CharBuffer.wrap(condition.trim())); } public static EvaluatorState makeEvaluatorState(Map<String,Object> mediaParams, Map<String,Object> boundParams, Set<String> supportFeatures) { EvaluatorState state = new EvaluatorBindingState(); state.setBinding("media", new MediaFunction(mediaParams)); state.setBinding("parameter", new ParameterFunction(boundParams)); state.setBinding("supports", new SupportsFunction(supportFeatures)); return state; } /** * A condition expression parser based upon the algorithm * described in "Parsing Expressions by Recursive Descent", by * Theodore Norvell (1999). * * For the sake of convenience, this implementation retains the * recursive nature of the Norvell algorithm. Converting to full * tail recursion or iteration is an exercise for future * optimizers. */ private static class Parser { private State state; private boolean skipWhitespace; Parser() { this.state = new State(); this.skipWhitespace = true; } Condition parse(CharBuffer cb) { if (cb.hasRemaining()) { state.setInput(cb, skipWhitespace); state.operators().push(Operator.SENTINEL); parseExpression(); state.expect(Token.EOS); return new Condition(state.topOperand()); } else throw new ParserException("empty condition"); } private void parseExpression() { parsePrimary(); Token t = state.next(); while (t != Token.EOS) { if (t.isBinaryOp()) { state.consume(); state.pushOperator(Operator.fromToken(t, OperatorContext.BINARY)); parsePrimary(); } else break; t = state.next(); } while (state.topOperator() != Operator.SENTINEL) state.popOperator(); } private void parsePrimary() { Token t0 = state.next(); if (t0 == null) { state.error(); } else if (t0.isLiteral()) { state.consume(); Token t1 = state.next(); if (t1 == Token.OPEN) { state.pushOperator(Operator.APPLY); state.pushOperand(t0); parseArguments(); } else state.pushOperand(t0); } else if (t0 == Token.OPEN) { state.consume(); state.operators().push(Operator.SENTINEL); parseExpression(); state.expect(Token.CLOSE); state.operators.pop(); } else if (t0.isUnaryOp()) { state.consume(); state.pushOperator(Operator.fromToken(t0, OperatorContext.UNARY)); parsePrimary(); } else state.error(t0); } private void parseArguments() { assert state.next() == Token.OPEN; state.consume(); state.operators().push(Operator.SENTINEL); int na = 0; Token t; while ((t = state.next()) != Token.CLOSE) { if (na > 0) { if (t == Token.COMMA) state.consume(); else state.error(Token.COMMA, t); } parseExpression(); ++na; } state.expect(Token.CLOSE); state.operators.pop(); List<Object> args = new java.util.ArrayList<Object>(); while (na > 0) { args.add(state.operands.pop()); --na; } state.operands.push(new Expression(Operator.GROUP, args)); } } private static class State { Tokenizer tokenizer; // input tokenizer Stack<Operator> operators; // operator stack Stack<Expression> operands; // operand stack (an operand is either a literal token or an expression) State() { this.operators = new Stack<Operator>(); this.operands = new Stack<Expression>(); } Stack<Operator> operators() { return operators; } Stack<Expression> operands() { return operands; } void setInput(CharBuffer cb, boolean skipWhitespace) { this.tokenizer = new Tokenizer(cb, skipWhitespace); operators().clear(); operands().clear(); } Token next() { assert tokenizer != null; return tokenizer.next(); } void consume() { assert tokenizer != null; tokenizer.consume(); } void expect(Token t) { assert tokenizer != null; tokenizer.expect(t); } void error() { error(null); } void error(Token actual) { error(null, actual); } void error(Token expected, Token actual) { tokenizer.error(expected, actual); } void pushOperator(Operator op) { Operator top; while ((top = topOperator()) != null) { if (top.comparePrecedence(op) > 0) popOperator(); else break; } operators().push(op); } Operator popOperator() { assert !operators().empty(); Operator top = topOperator(); if (top != null) { if (top.isBinary() || (top == Operator.APPLY)) { assert operands().size() > 1; Object o1 = operands().pop(); Object o0 = operands().pop(); pushOperand(new Expression(operators().pop(), Arrays.asList(new Object[]{ o0, o1 }))); return top; } else if (top.isUnary()) { assert operands().size() > 0; Object o0 = operands().pop(); pushOperand(new Expression(operators().pop(), o0)); return top; } else { assert false; throw new ParserStateException("unexpected operator: " + top); } } else throw new ParserStateException("operator stack is empty"); } Operator topOperator() { return operators().empty() ? null : operators().peek(); } void pushOperand(Token t) { assert t.isLiteral(); pushOperand(new Expression(Operator.LITERAL, t)); } void pushOperand(Expression e) { operands().push(e); } Expression topOperand() { return operands().empty() ? null : operands().peek(); } } public static class Token { enum Type { AND ("&&"), // logical and BOOLEAN ("#B"), // boolean literal CLOSE (")" ), // open group COMMA ("," ), // comma (argument list separator) DIVIDE ("/" ), // divide by EOS ("#E"), // end of stream literal EQ ("=="), // equals GEQ (">="), // greater than or equals GT (">" ), // greater than IDENT ("#I"), // symbol (identifier) literal LEQ ("<="), // less than or equals LT ("<" ), // less than MINUS ("-" ), // minus or subtract from MODULO ("%" ), // modulus of MULTIPLY("*" ), // multiply by NEQ ("!="), // not equals NOT ("!" ), // logical not NUMERIC ("#N"), // numeric literal OPEN ("(" ), // open group OR ("||"), // logical or PLUS ("+" ), // plus or add to SPACE ("#W"), // whitespace literal STRING ("#S"); // string literal private String shorthand; private Type(String shorthand) { this.shorthand = shorthand; } String shorthand() { return shorthand; } boolean isLiteral() { return shorthand.charAt(0) == '#'; } @Override public String toString() { return shorthand(); } static Type valueOfShorthand(String shorthand) { if ((shorthand == null) || shorthand.isEmpty()) throw new IllegalArgumentException(); for (Type v: values()) { if (shorthand.equals(v.shorthand())) return v; } throw new IllegalArgumentException(); } } static final Token AND = new Token(Type.AND); static final Token CLOSE = new Token(Type.CLOSE); static final Token COMMA = new Token(Type.COMMA); static final Token DIVIDE = new Token(Type.DIVIDE); static final Token EOS = new Token(Type.EOS); static final Token EQ = new Token(Type.EQ); static final Token GEQ = new Token(Type.GEQ); static final Token GT = new Token(Type.GT); static final Token IDENT = new Token(Type.IDENT); static final Token LEQ = new Token(Type.LEQ); static final Token LT = new Token(Type.LT); static final Token MINUS = new Token(Type.MINUS); static final Token MODULO = new Token(Type.MODULO); static final Token MULTIPLY = new Token(Type.MULTIPLY); static final Token NEQ = new Token(Type.NEQ); static final Token NOT = new Token(Type.NOT); static final Token OPEN = new Token(Type.OPEN); static final Token OR = new Token(Type.OR); static final Token PLUS = new Token(Type.PLUS); private Type type; private String value; Token(Type type) { this(type, null); } Token(String shorthand, String value) { this(Type.valueOfShorthand(shorthand), value); } Token(Type type, String value) { this.type = type; this.value = value; } public Type getType() { return type; } public String getValue() { return value; } public Object getLiteralValue(EvaluatorState state) { assert isLiteral(); if (type == Type.IDENT) { return state.getBinding(value); } else if (type == Type.STRING) { return value; } else if (type == Type.NUMERIC) { try { return Double.valueOf(value); } catch (NumberFormatException e) { throw new IllegalStateException(e); } } else if (type == Type.BOOLEAN) { return Boolean.valueOf(value); } else { throw new IllegalStateException(); } } public int length() { if (isLiteral()) { assert value != null; return value.length(); } else return getType().shorthand().length(); } public boolean isLiteral() { return type.shorthand().charAt(0) == '#'; } public boolean isBinaryOp() { Operator op = Operator.fromToken(this, OperatorContext.BINARY); return (op != null) ? op.isBinary() : false; } public boolean isUnaryOp() { Operator op = Operator.fromToken(this, OperatorContext.UNARY); return (op != null) ? op.isUnary() : false; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getType().toString()); if (isLiteral() && (getValue() != null)) { sb.append('('); sb.append(getValue()); sb.append(')'); } return sb.toString(); } } private static class Tokenizer { private CharBuffer cb; private boolean skipWhitespace; Tokenizer(CharBuffer cb, boolean skipWhitespace) { this.cb = cb; this.skipWhitespace = skipWhitespace; } private void expect(Token token) { Token next = next(); if ((next != null) && next.equals(token)) consume(); else error(token, next); } private Token next() { return getToken(cb, false, false); } private void consume() { getToken(cb, true, skipWhitespace); } private void error(Token expected, Token actual) { throw new UnexpectedTokenException(cb, expected, actual); } private static Token getToken(CharBuffer cb, boolean consume, boolean skipWhitespace) { Token t; int p0 = cb.position(); int n = cb.remaining(); char c1 = (n > 0) ? cb.charAt(0) : 0; char c2 = (n > 1) ? cb.charAt(1) : 0; // the following clauses are order dependent if (c1 == 0) { t = Token.EOS; } else if (c1 == '(') { t = Token.OPEN; } else if (c1 == ')') { t = Token.CLOSE; } else if (c1 == ',') { t = Token.COMMA; } else if (c1 == '+') { t = Token.PLUS; } else if (c1 == '-') { t = Token.MINUS; } else if (c1 == '*') { t = Token.MULTIPLY; } else if (c1 == '/') { t = Token.DIVIDE; } else if (c1 == '%') { t = Token.MODULO; } else if (c1 == '=') { if (c2 == '=') { t = Token.EQ; } else t = null; } else if (c1 == '!') { if (c2 == '=') { t = Token.NEQ; } else t = Token.NOT; } else if (c1 == '<') { if (c2 == '=') t = Token.LEQ; else t = Token.LT; } else if (c1 == '>') { if (c2 == '=') t = Token.GEQ; else t = Token.GT; } else if (c1 == '|') { if (c2 == '|') t = Token.OR; else t = null; } else if (c1 == '&') { if (c2 == '&') t = Token.AND; else t = null; } else if (isString(cb)) { t = getString(cb); } else if (isNumeric(cb)) { t = getNumeric(cb); } else if (isBoolean(cb)) { t = getBoolean(cb); } else if (isIdent(cb)) { t = getIdent(cb); } else t = null; if (consume && (t != null)) { int p1 = p0; if (!t.isLiteral()) p1 += t.length(); else p1 = cb.position(); cb.position(p1); if (skipWhitespace) { if (isWhitespace(cb)) getWhitespace(cb); } } else cb.position(p0); return t; } private static boolean isWhitespace(CharBuffer cb) { return getWhitespace(cb, false) != null; } private static Token getWhitespace(CharBuffer cb) { return getWhitespace(cb, true); } private static Token getWhitespace(CharBuffer cb, boolean consume) { int i = 0; // index from cb[position] int n = cb.remaining(); // # indices remaining StringBuffer sb = new StringBuffer(); while (i < n) { char c = cb.charAt(i); if (Character.isWhitespace(c)) { sb.append(c); ++i; } else break; } if (sb.length() > 0) { if (consume) cb.position(cb.position() + i); return new Token(Token.Type.SPACE, sb.toString()); } else return null; } private static boolean isString(CharBuffer cb) { return getString(cb, false) != null; } private static Token getString(CharBuffer cb) { return getString(cb, true); } private static Token getString(CharBuffer cb, boolean consume) { int i = 0; // index from cb[position] int n = cb.remaining(); // # indices remaining char d; // string delimiter StringBuffer sb = new StringBuffer(); // get string delimiter d = (i < n) ? cb.charAt(i) : 0; if ((d == '\'') || (d == '\"')) { ++i; // get delimited content, handling escapes char c = 0; while (i < n) { c = (i < n) ? cb.charAt(i) : 0; if (c == d) { ++i; break; } else if (c == '\\') { ++i; if (i < n) { c = (i < n) ? cb.charAt(i) : 0; sb.append(c); ++i; } else break; } else { sb.append(c); ++i; } } if (c != d) sb.setLength(0); } if (sb.length() > 0) { if (consume) cb.position(cb.position() + i); return new Token(Token.Type.STRING, sb.toString()); } else return null; } private static boolean isNumeric(CharBuffer cb) { return getNumeric(cb, false) != null; } private static Token getNumeric(CharBuffer cb) { return getNumeric(cb, true); } private static Token getNumeric(CharBuffer cb, boolean consume) { int i = 0; // index from cb[position] int j; // helper index int k; // helper index int n = cb.remaining(); // # indices remaining int m = 0; // # digits in mantissa int e = 0; // # digits in exponent char c; StringBuffer sb = new StringBuffer(); // integral component j = i; c = (i < n) ? cb.charAt(i) : 0; if (c == '0') { sb.append(c); ++i; } else if ((c >= '1') && (c <= '9')) { sb.append(c); ++i; while (i < n) { c = cb.charAt(i); if ((c >= '0') && (c <= '9')) { sb.append(c); ++i; } else break; } } m += j - i; // fractional component j = i; c = (i < n) ? cb.charAt(i) : 0; if (c == '.') { sb.append(c); ++i; while (i < n) { c = cb.charAt(i); if ((c >= '0') && (c <= '9')) { sb.append(c); ++i; } else break; } } m += j - i; // ensure mantissa is non-empty if (m == 0) return null; // exponent component k = sb.length(); c = (i < n) ? cb.charAt(i) : 0; if ((c == 'e') || (c == 'E')) { sb.append('E'); ++i; c = (i < n) ? cb.charAt(i) : 0; if ((c == '+') || (c == '-')) { sb.append(c); ++i; } j = i; while (i < n) { c = cb.charAt(i); if ((c >= '0') && (c <= '9')) { sb.append(c); ++i; } else break; } e += j - i; } if (e == 0) sb.setLength(k); if (sb.length() > 0) { if (consume) cb.position(cb.position() + i); return new Token(Token.Type.NUMERIC, sb.toString()); } else return null; } private static boolean isBoolean(CharBuffer cb) { return getBoolean(cb, false) != null; } private static Token getBoolean(CharBuffer cb) { return getBoolean(cb, true); } private static Token getBoolean(CharBuffer cb, boolean consume) { Token t = getIdent(cb, false); if (t != null) { String ident = t.getValue(); assert ident != null; if (ident.equals("true") || ident.equals("false")) t = new Token(Token.Type.BOOLEAN, ident); else t = null; } if (consume && (t != null)) cb.position(cb.position() + t.length()); return t; } private static boolean isIdent(CharBuffer cb) { return getIdent(cb, false) != null; } private static Token getIdent(CharBuffer cb) { return getIdent(cb, true); } private static Token getIdent(CharBuffer cb, boolean consume) { int i = 0; // index from cb[position] int n = cb.remaining(); // # indices remaining char c; StringBuffer sb = new StringBuffer(); c = (i < n) ? cb.charAt(i) : 0; if (XML.isNCNameCharStart(c)) { sb.append(c); ++i; while (i < n) { c = (i < n) ? cb.charAt(i) : 0; if (XML.isNCNameCharPart(c)) { sb.append(c); ++i; } else break; } } if (sb.length() > 0) { if (consume) cb.position(cb.position() + i); return new Token(Token.Type.IDENT, sb.toString()); } else return null; } } public enum Associativity { NONE, LEFT, RIGHT; } public enum OperatorContext { BINARY, UNARY; } public enum Operator { ADD ( 5, Associativity.LEFT ), AND ( 2, Associativity.LEFT ), APPLY ( 9, Associativity.NONE ), COMMA ( 0, Associativity.LEFT ), DIVIDE ( 6, Associativity.LEFT ), EQ ( 3, Associativity.LEFT ), GEQ ( 4, Associativity.LEFT ), GROUP ( 8, Associativity.NONE ), GT ( 4, Associativity.LEFT ), LEQ ( 4, Associativity.LEFT ), LITERAL (-1, Associativity.NONE ), LT ( 4, Associativity.LEFT ), MINUS ( 7, Associativity.RIGHT ), MODULO ( 6, Associativity.LEFT ), MULTIPLY ( 6, Associativity.LEFT ), NEQ ( 3, Associativity.LEFT ), NOT ( 7, Associativity.RIGHT ), OR ( 1, Associativity.LEFT ), PLUS ( 7, Associativity.RIGHT ), SENTINEL (-1, Associativity.NONE ), SUBTRACT ( 5, Associativity.LEFT ); private int precedence; private Associativity associativity; Operator(int precedence, Associativity associativity) { this.precedence = precedence; this.associativity = associativity; } public int getPrecedence() { return precedence; } public Associativity getAssociativity() { return associativity; } public boolean isBinary() { return (precedence > 0) && (precedence < 7); } public boolean isUnary() { return precedence == 7; } public int comparePrecedence(Operator other) { if (getPrecedence() < other.getPrecedence()) return -1; else if (getPrecedence() > other.getPrecedence()) return 1; else return 0; } public static Operator fromToken(Token t, OperatorContext context) { if (t == Token.AND) return AND; else if (t == Token.COMMA) return COMMA; else if (t == Token.DIVIDE) return DIVIDE; else if (t == Token.EQ) return EQ; else if (t == Token.GEQ) return GEQ; else if (t == Token.GT) return GT; else if (t == Token.LEQ) return LEQ; else if (t == Token.LT) return LT; else if (t == Token.MINUS) return (context == OperatorContext.UNARY) ? MINUS : SUBTRACT; else if (t == Token.MODULO) return MODULO; else if (t == Token.MULTIPLY) return MULTIPLY; else if (t == Token.NEQ) return NEQ; else if (t == Token.NOT) return NOT; else if (t == Token.OR) return OR; else if (t == Token.PLUS) return (context == OperatorContext.UNARY) ? PLUS : ADD; else return null; } } public static class Expression { private Operator operator; private List<Object> operands; // (Expression|Token)* Expression(Operator operator, Object operand) { this(operator, Arrays.asList(new Object[]{operand})); } Expression(Operator operator, List<Object> operands) { assert operator != null; this.operator = operator; assert operands != null; this.operands = operands; } public Operator getOperator() { return operator; } public int getOperandCount() { return operands.size(); } public Object getOperand(int index) { if ((index < 0) || (index >= operands.size())) throw new IllegalArgumentException(); else return operands.get(index); } public List<Object> getOperands() { return Collections.unmodifiableList(operands); } public Object evaluate(ExpressionEvaluator ee) { return ee.evaluate(this); } public String toString() { StringBuffer sb = new StringBuffer(); sb.append(operator); sb.append('('); boolean firstOperand = true; for (Object operand : operands) { if (!firstOperand) sb.append(','); else firstOperand = false; sb.append(operand); } sb.append(')'); return sb.toString(); } } private static class EvaluatorBindingState implements EvaluatorState { private Map<String,Object> bindings; public EvaluatorBindingState() { this.bindings = new java.util.HashMap<String,Object>(); } public boolean hasBinding(String identifier) { return bindings.containsKey(identifier); } public Object getBinding(String identifier) { return bindings.get(identifier); } public void setBinding(String identifier, Object value) { bindings.put(identifier, value); } } private static class ExpressionEvaluator { private static final Map<Operator,Evaluator> evaluators; static { Map<Operator,Evaluator> m = new java.util.HashMap<Operator,Evaluator>(); Evaluator e; e = new ApplyEvaluator(); m.put(Operator.APPLY, e); e = new BinaryArithmeticEvaluator(); m.put(Operator.ADD, e); m.put(Operator.SUBTRACT, e); m.put(Operator.MULTIPLY, e); m.put(Operator.DIVIDE, e); m.put(Operator.MODULO, e); e = new BinaryLogicEvaluator(); m.put(Operator.AND, e); m.put(Operator.OR, e); e = new BinaryRelationEvaluator(); m.put(Operator.EQ, e); m.put(Operator.GEQ, e); m.put(Operator.GT, e); m.put(Operator.LEQ, e); m.put(Operator.LT, e); m.put(Operator.NEQ, e); e = new GroupEvaluator(); m.put(Operator.GROUP, e); e = new LiteralEvaluator(); m.put(Operator.LITERAL, e); e = new UnaryLogicEvaluator(); m.put(Operator.NOT, e); e = new UnaryArithmeticEvaluator(); m.put(Operator.MINUS, e); m.put(Operator.PLUS, e); evaluators = Collections.unmodifiableMap(m); } EvaluatorState state; public ExpressionEvaluator(EvaluatorState state) { assert state != null; this.state = state; } private EvaluatorState getState() { return state; } public Object evaluate(Object o) { if (o == null) { throw new IllegalArgumentException(); } else if (o instanceof Expression) { Expression e = (Expression) o; Operator operator = e.getOperator(); Evaluator evaluator = evaluators.get(operator); if (evaluator != null) return evaluator.evaluate(this, e); else throw new IllegalArgumentException(o.toString()); } else if (o instanceof Number) { return o; } else if (o instanceof String) { return o; } else if (o instanceof Boolean) { return o; } else { throw new IllegalArgumentException(); } } } private static class ApplyEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int minCount = 1; final int maxCount = -1; if (checkOperandCount(e, minCount, maxCount)) { Object o0 = ee.evaluate(e.getOperand(0)); if (o0 instanceof EvaluatorFunction) { EvaluatorFunction f = (EvaluatorFunction) o0; Object o1 = e.getOperand(1); if (o1 instanceof Expression) { Expression eArguments = (Expression) o1; if (eArguments.getOperator() == Operator.GROUP) { List<Object> arguments = new java.util.ArrayList<Object>(); for (Object operand : eArguments.getOperands()) { arguments.add(ee.evaluate(operand)); } return f.apply(ee.getState(), arguments); } else throw new IllegalStateException(); } else throw new IllegalStateException(); } else throw new IncompatibleOperandException(e, o0, EvaluatorFunction.class); } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "apply"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.APPLY) return true; else return false; } } private static class BinaryArithmeticEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int minCount = 2; final int maxCount = 2; if (checkOperandCount(e, minCount, maxCount)) { Class<?> operandClass = Number.class; Object o0 = ee.evaluate(e.getOperand(0)); if (!checkCompatibleOperand(o0, operandClass)) throw new IncompatibleOperandException(e, o0, operandClass); else o0 = convertCompatibleOperand(o0, operandClass); Object o1 = ee.evaluate(e.getOperand(1)); if (!checkCompatibleOperand(o1, operandClass)) throw new IncompatibleOperandException(e, o1, operandClass); else o1 = convertCompatibleOperand(o1, operandClass); return evaluate(ee.getState(), e.getOperator(), o0, o1); } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "binary arithmetic"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.ADD) return true; else if (operator == Operator.SUBTRACT) return true; else if (operator == Operator.MULTIPLY) return true; else if (operator == Operator.DIVIDE) return true; else if (operator == Operator.MODULO) return true; else return false; } private Object evaluate(EvaluatorState state, Operator operator, Object o0, Object o1) { assert o0 instanceof Number; double d0 = ((Number) o0).doubleValue(); assert o1 instanceof Number; double d1 = ((Number) o1).doubleValue(); if (operator == Operator.ADD) { return (Double) (d0 + d1); } else if (operator == Operator.SUBTRACT) { return (Double) (d0 - d1); } else if (operator == Operator.MULTIPLY) { return (Double) (d0 * d1); } else if (operator == Operator.DIVIDE) { return (Double) (d0 / d1); } else if (operator == Operator.MODULO) { return (Double) (d0 % d1); } else throw new IllegalStateException(); } } private static class BinaryLogicEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int minCount = 2; final int maxCount = 2; if (checkOperandCount(e, minCount, maxCount)) { Class<?> operandClass = Boolean.class; Object o0 = ee.evaluate(e.getOperand(0)); if (!checkCompatibleOperand(o0, operandClass)) throw new IncompatibleOperandException(e, o0, operandClass); else o0 = convertCompatibleOperand(o0, operandClass); Object o1 = ee.evaluate(e.getOperand(1)); if (!checkCompatibleOperand(o1, operandClass)) throw new IncompatibleOperandException(e, o1, operandClass); else o1 = convertCompatibleOperand(o1, operandClass); return evaluate(ee.getState(), e.getOperator(), o0, o1); } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "binary logic"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.AND) return true; else if (operator == Operator.OR) return true; else return false; } private Object evaluate(EvaluatorState state, Operator operator, Object o0, Object o1) { assert o0 instanceof Boolean; boolean b0 = ((Boolean) o0).booleanValue(); assert o1 instanceof Boolean; boolean b1 = ((Boolean) o1).booleanValue(); if (operator == Operator.AND) { return (Boolean) (b0 && b1); } else if (operator == Operator.OR) { return (Boolean) (b0 || b1); } else throw new IllegalStateException(); } } private static class BinaryRelationEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { Operator operator = e.getOperator(); final int minCount = 2; final int maxCount = 2; if (checkOperandCount(e, minCount, maxCount)) { Object o0 = ee.evaluate(e.getOperand(0)); Object o1 = ee.evaluate(e.getOperand(1)); if (o0 instanceof Number) { Class<?> operandClass = Number.class; if (o1 instanceof Number) { return evaluate(ee.getState(), e, operator, (Number) o0, (Number) o1); } else if (checkCompatibleOperand(o1, operandClass)) { return evaluate(ee.getState(), e, operator, (Number) o0, (Number) convertCompatibleOperand(o1, operandClass)); } else { throw new IncompatibleOperandException(e, o1, operandClass); } } else if (o0 instanceof String) { Class<?> operandClass = String.class; if (o1 instanceof String) { return evaluate(ee.getState(), e, operator, (String) o0, (String) o1); } else if (checkCompatibleOperand(o1, operandClass)) { return evaluate(ee.getState(), e, operator, (String) o0, (String) convertCompatibleOperand(o1, operandClass)); } else { throw new IncompatibleOperandException(e, o1, operandClass); } } else if (o0 instanceof Boolean) { Class<?> operandClass = Boolean.class; if (o1 instanceof Boolean) { return evaluate(ee.getState(), e, operator, (Boolean) o0, (Boolean) o1); } else if (checkCompatibleOperand(o1, operandClass)) { return evaluate(ee.getState(), e, operator, (Boolean) o0, (Boolean) convertCompatibleOperand(o1, operandClass)); } else { throw new IncompatibleOperandException(e, o1, operandClass); } } else { throw new IllegalStateException(); } } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "binary arithmetic"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.EQ) return true; else if (operator == Operator.GEQ) return true; else if (operator == Operator.GT) return true; else if (operator == Operator.LEQ) return true; else if (operator == Operator.LT) return true; else if (operator == Operator.NEQ) return true; else return false; } private Object evaluate(EvaluatorState state, Expression e, Operator operator, Number n0, Number n1) { Double d0 = Double.valueOf(n0.doubleValue()); Double d1 = Double.valueOf(n1.doubleValue()); int d = d0.compareTo(d1); if (operator == Operator.EQ) { return (Boolean) (d == 0); } else if (operator == Operator.GEQ) { return (Boolean) (d >= 0); } else if (operator == Operator.GT) { return (Boolean) (d > 0); } else if (operator == Operator.LEQ) { return (Boolean) (d <= 0); } else if (operator == Operator.LT) { return (Boolean) (d < 0); } else if (operator == Operator.NEQ) { return (Boolean) (d != 0); } else throw new IllegalStateException(); } private Object evaluate(EvaluatorState state, Expression e, Operator operator, String s0, String s1) { int d = s0.compareTo(s1); if (operator == Operator.EQ) { return (Boolean) (d == 0); } else if (operator == Operator.GEQ) { return (Boolean) (d >= 0); } else if (operator == Operator.GT) { return (Boolean) (d > 0); } else if (operator == Operator.LEQ) { return (Boolean) (d <= 0); } else if (operator == Operator.LT) { return (Boolean) (d < 0); } else if (operator == Operator.NEQ) { return (Boolean) (d != 0); } else throw new IllegalStateException(); } private Object evaluate(EvaluatorState state, Expression e, Operator operator, Boolean b0, Boolean b1) { if (operator == Operator.EQ) { return (Boolean) b0.equals(b1); } else if (operator == Operator.GEQ) { throw new IncompatibleOperatorException(e, operator, Boolean.class); } else if (operator == Operator.GT) { throw new IncompatibleOperatorException(e, operator, Boolean.class); } else if (operator == Operator.LEQ) { throw new IncompatibleOperatorException(e, operator, Boolean.class); } else if (operator == Operator.LT) { throw new IncompatibleOperatorException(e, operator, Boolean.class); } else if (operator == Operator.NEQ) { return (Boolean) !b0.equals(b1); } else throw new IllegalStateException(); } } private static class GroupEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int count = 1; if (checkOperandCount(e, count)) { return ee.evaluate(e.getOperand(0)); } else throw new BadOperandCountException(e, count, count); } else throw new BadOperatorException(e, "group"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.GROUP) return true; else return false; } } private static class LiteralEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int count = 1; if (checkOperandCount(e, count)) { Object o0 = e.getOperand(0); if (!(o0 instanceof Token)) throw new IncompatibleOperandException(e, o0, Token.class); else return evaluate(ee.getState(), e.getOperator(), o0); } else throw new BadOperandCountException(e, count, count); } else throw new BadOperatorException(e, "literal"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.LITERAL) return true; else return false; } private Object evaluate(EvaluatorState state, Operator operator, Object o0) { assert o0 instanceof Token; Token t = (Token) o0; if (operator == Operator.LITERAL) { assert t.isLiteral(); return t.getLiteralValue(state); } else throw new IllegalStateException(); } } private static class UnaryArithmeticEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int minCount = 1; final int maxCount = 1; if (checkOperandCount(e, minCount, maxCount)) { Class<?> operandClass = Number.class; Object o0 = ee.evaluate(e.getOperand(0)); if (!checkCompatibleOperand(o0, operandClass)) throw new IncompatibleOperandException(e, o0, operandClass); else o0 = convertCompatibleOperand(o0, operandClass); return evaluate(ee.getState(), e.getOperator(), o0); } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "unary arithmetic"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.MINUS) return true; else if (operator == Operator.PLUS) return true; else return false; } private Object evaluate(EvaluatorState state, Operator operator, Object o0) { assert o0 instanceof Number; double d0 = ((Number) o0).doubleValue(); if (operator == Operator.MINUS) { return (Double) (-d0); } else if (operator == Operator.PLUS) { return (Double) (+d0); } else throw new IllegalStateException(); } } private static class UnaryLogicEvaluator implements Evaluator { public Object evaluate(ExpressionEvaluator ee, Expression e) { if (checkOperator(e)) { final int minCount = 1; final int maxCount = 1; if (checkOperandCount(e, minCount, maxCount)) { Class<?> operandClass = Boolean.class; Object o0 = ee.evaluate(e.getOperand(0)); if (!checkCompatibleOperand(o0, operandClass)) throw new IncompatibleOperandException(e, o0, operandClass); else o0 = convertCompatibleOperand(o0, operandClass); return evaluate(ee.getState(), e.getOperator(), o0); } else throw new BadOperandCountException(e, minCount, maxCount); } else throw new BadOperatorException(e, "unary logic"); } private boolean checkOperator(Expression e) { Operator operator = e.getOperator(); if (operator == Operator.NOT) return true; else return false; } private Object evaluate(EvaluatorState state, Operator operator, Object o0) { assert o0 instanceof Boolean; boolean b0 = ((Boolean) o0).booleanValue(); if (operator == Operator.NOT) { return (Boolean) (!b0); } else throw new IllegalStateException(); } } private static class MediaFunction implements EvaluatorFunction { private Map<String,Object> parameters; public MediaFunction(Map<String,Object> parameters) { assert parameters != null; this.parameters = parameters; } public Object apply(EvaluatorState state, List<Object> arguments) { if (arguments.size() < 1) throw new BadOperandCountException(arguments.size(), 1, 1); else { Class<?> operandClass = String.class; Object o0 = arguments.get(0); if (checkCompatibleOperand(o0, operandClass)) { o0 = convertCompatibleOperand(o0, operandClass); assert o0 instanceof String; String query = (String) o0; try { MediaQuery mq = MediaQuery.valueOf(query); return mq.evaluate(parameters); } catch (MediaQuery.ParserException e) { throw new BuiltinFunctionException(e); } } else throw new IncompatibleOperandException(o0, operandClass); } } } private static class ParameterFunction implements EvaluatorFunction { private Map<String,Object> parameters; public ParameterFunction(Map<String,Object> parameters) { assert parameters != null; this.parameters = parameters; } public Object apply(EvaluatorState state, List<Object> arguments) { if (arguments.size() < 1) throw new BadOperandCountException(arguments.size(), 1, 1); else { Class<?> operandClass = String.class; Object o0 = arguments.get(0); if (checkCompatibleOperand(o0, operandClass)) { o0 = convertCompatibleOperand(o0, operandClass); assert o0 instanceof String; String name = (String) o0; if (parameters.containsKey(name)) return parameters.get(name); else throw new UnknownParameterException(name); } else throw new IncompatibleOperandException(o0, operandClass); } } } private static class SupportsFunction implements EvaluatorFunction { private Set<String> features; public SupportsFunction(Set<String> features) { assert features != null; this.features = features; } public Object apply(EvaluatorState state, List<Object> arguments) { if (arguments.size() < 1) throw new BadOperandCountException(arguments.size(), 1, 1); else { Class<?> operandClass = String.class; Object o0 = arguments.get(0); if (checkCompatibleOperand(o0, operandClass)) { o0 = convertCompatibleOperand(o0, operandClass); assert o0 instanceof String; String designator = (String) o0; return Boolean.valueOf(features.contains(designator)); } else throw new IncompatibleOperandException(o0, operandClass); } } } private static boolean checkOperandCount(Expression e, int count) { return checkOperandCount(e, count, count); } private static boolean checkOperandCount(Expression e, int minCount, int maxCount) { List<Object> operands = e.getOperands(); if (operands.size() < minCount) { return false; } else if ((maxCount >= 0) && (operands.size() > maxCount)) { return false; } else { return true; } } public static boolean checkCompatibleOperand(Object o, Class<?> compatibleClass) { if (o instanceof Number) { if (compatibleClass == Number.class) return true; else if (compatibleClass == String.class) return true; else if (compatibleClass == Boolean.class) return false; else throw new IllegalArgumentException(); } else if (o instanceof String) { if (compatibleClass == Number.class) { try { Double d = Double.valueOf((String) o); return !d.isNaN(); } catch (NumberFormatException e) { return false; } } else if (compatibleClass == String.class) { return true; } else if (compatibleClass == Boolean.class) { String s = (String) o; if (s.equals("true")) return true; else if (s.equals("false")) return true; else return false; } else throw new IllegalArgumentException(); } else if (o instanceof Boolean) { if (compatibleClass == Number.class) return false; else if (compatibleClass == String.class) return true; else if (compatibleClass == Boolean.class) return true; else throw new IllegalArgumentException(); } else throw new IllegalStateException(); } public static Object convertCompatibleOperand(Object o, Class<?> compatibleClass) { if (o == null) throw new IllegalStateException(); else if (o instanceof Number) return convertCompatibleOperand((Number) o, compatibleClass); else if (o instanceof String) return convertCompatibleOperand((String) o, compatibleClass); else if (o instanceof Boolean) return convertCompatibleOperand((Boolean) o, compatibleClass); else throw new IllegalStateException(); } private static Object convertCompatibleOperand(Number n, Class<?> compatibleClass) { if (n == null) { throw new IllegalStateException(); } else if (compatibleClass == Number.class) { return n; } else if (compatibleClass == String.class) { return n.toString(); } else { throw new IllegalStateException(); } } private static Object convertCompatibleOperand(String s, Class<?> compatibleClass) { if (s == null) { throw new IllegalStateException(); } else if (compatibleClass == Number.class) { try { return Double.valueOf(s); } catch (NumberFormatException e) { throw new IllegalStateException(); } } else if (compatibleClass == String.class) { return s; } else if (s.equals("true")) { return Boolean.TRUE; } else if (s.equals("false")) { return Boolean.FALSE; } else { throw new IllegalStateException(); } } private static Object convertCompatibleOperand(Boolean b, Class<?> compatibleClass) { if (b == null) { throw new IllegalStateException(); } else if (compatibleClass == String.class) { return b.toString(); } else if (compatibleClass == Boolean.class) { return b; } else { throw new IllegalStateException(); } } public static class UnexpectedTokenException extends ParserException { static final long serialVersionUID = 0; UnexpectedTokenException(CharBuffer cb, Token expected, Token actual) { this(cb, (expected != null) ? expected.toString() : null, (actual != null) ? actual.toString() : null); } UnexpectedTokenException(CharBuffer cb, String expected, String actual) { super(makeMessage(cb, expected, actual)); } private static String makeMessage(CharBuffer cb, String expected, String actual) { StringBuffer sb = new StringBuffer(); if (expected != null) sb.append("expected " + expected); if (sb.length() > 0) sb.append(", "); if (actual != null) sb.append("got " + actual); if (sb.length() > 0) sb.append(", "); if (cb != null) sb.append("remaining input \"" + cb + "\""); return sb.toString(); } } public static class ParserException extends RuntimeException { static final long serialVersionUID = 0; public ParserException(String message) { super(message); } } public static class ParserStateException extends ParserException { static final long serialVersionUID = 0; ParserStateException(String message) { super(message); } } public static class EvaluatorException extends RuntimeException { static final long serialVersionUID = 0; public EvaluatorException(String message) { super(message); } } public static class BadOperatorException extends EvaluatorException { static final long serialVersionUID = 0; public BadOperatorException(Expression e, String expectation) { this(e.getOperator(), expectation); } public BadOperatorException(Operator operator, String expectation) { super("expected " + expectation + ", got" + operator); } } public static class BadOperandCountException extends EvaluatorException { static final long serialVersionUID = 0; public BadOperandCountException(Expression e, int minCount, int maxCount) { this(e.getOperandCount(), minCount, maxCount); } public BadOperandCountException(int count, int minCount, int maxCount) { super("expected from " + minCount + " to " + (maxCount >= 0 ? Integer.toString(maxCount) : "...") + " operands, got" + count); } } public static class IncompatibleOperatorException extends EvaluatorException { static final long serialVersionUID = 0; public IncompatibleOperatorException(Expression e, Operator operator, Class<?> operandClass) { super("operator " + operator + " incompabitle with " + operandClass); } } public static class IncompatibleOperandException extends EvaluatorException { static final long serialVersionUID = 0; public IncompatibleOperandException(Object operand, Class<?> operandClass) { this(null, operand, operandClass); } public IncompatibleOperandException(Expression e, Object operand, Class<?> operandClass) { super("operand " + operand + " incompabitle with " + operandClass); } } public static class BuiltinFunctionException extends EvaluatorException { static final long serialVersionUID = 0; public BuiltinFunctionException(Exception e) { this(e.getMessage()); } public BuiltinFunctionException(String message) { super(message); } } public static class UnknownParameterException extends BuiltinFunctionException { static final long serialVersionUID = 0; public UnknownParameterException(String name) { super("unknown parameter '" + name + "'"); } } }