All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.commons.text.expression.ExpressionEvaluator Maven / Gradle / Ivy

There is a newer version: 1.2.19
Show newest version

/*
 * de.unkrig.commons - A general-purpose Java class library
 *
 * Copyright (c) 2013, Arno Unkrig
 * 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.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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 de.unkrig.commons.text.expression;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import de.unkrig.commons.lang.ExceptionUtil;
import de.unkrig.commons.lang.ObjectUtil;
import de.unkrig.commons.lang.StringUtil;
import de.unkrig.commons.lang.protocol.Mapping;
import de.unkrig.commons.lang.protocol.Predicate;
import de.unkrig.commons.lang.protocol.PredicateUtil;
import de.unkrig.commons.lang.protocol.ProducerWhichThrows;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.reflect.ReflectUtil;
import de.unkrig.commons.text.Notations;
import de.unkrig.commons.text.expression.Parser.BinaryOperator;
import de.unkrig.commons.text.expression.Parser.UnaryOperator;
import de.unkrig.commons.text.expression.Scanner.TokenType;
import de.unkrig.commons.text.parser.ParseException;
import de.unkrig.commons.text.pattern.Glob;
import de.unkrig.commons.text.pattern.Pattern2;
import de.unkrig.commons.text.scanner.AbstractScanner.Token;
import de.unkrig.commons.text.scanner.ScanException;

/**
 * Supports two operation modes:
 * 
    *
  • Scans, parses and evaluates an expression immediately (see {@link #evaluate(String, Mapping)})
  • *
  • * Scans and parses an expression (see {@link #parse(String)}) into an {@link Expression} object for repeated * evaluation (see {@link Expression#evaluate(Mapping)}). *
  • *
*/ public class ExpressionEvaluator { private String[] imports = new String[] { "java.lang" }; private ClassLoader classLoader = this.getClass().getClassLoader(); private final Predicate isValidVariableName; /** * @param isValidVariableName Evaluates whether a string is a valid variable name; if not, then the parser will * throw a {@link ParseException} */ public ExpressionEvaluator(Predicate isValidVariableName) { this.isValidVariableName = isValidVariableName; } /** * @param variableNames Contains all valid variable names */ public ExpressionEvaluator(Collection variableNames) { this.isValidVariableName = PredicateUtil.contains(variableNames); } /** * @param variableNames The names of the variables that can be referred to in an expression */ public ExpressionEvaluator(String... variableNames) { this.isValidVariableName = PredicateUtil.contains(Arrays.asList(variableNames)); } /** * @return The currently configured imports */ public String[] getImports() { return this.imports.clone(); } /** * @param imports Names of imported packages */ public ExpressionEvaluator setImports(String[] imports) { this.imports = imports.clone(); return this; } /** * @return The currently configured {@link ClassLoader} */ public ClassLoader getClassLoader() { return this.classLoader; } /** * @param classLoader Used to load classes named in the expression; by default the ClassLoader which loaded * the {@link ExpressionEvaluator} class. */ public ExpressionEvaluator setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; return this; } /** * Parses an expression. * * @param spec The text to be parsed * @see Parser The expression syntax */ public Expression parse(String spec) throws ParseException { return this.parser(Scanner.stringScanner().setInput(spec)).parse(); } /** * Parses an expression from a {@code tokenProducer}. * * @see Parser The expression syntax */ public Expression parse(ProducerWhichThrows, ? extends ScanException> tokenProducer) throws ParseException { return this.parser(tokenProducer).parse(); } /** * @param spec The text to be parsed * @param Irrelevant * @return A {@link Parser} for expression parsing * @see Parser The expression syntax */ public Parser parser(String spec) { return this.parser(Scanner.stringScanner().setInput(spec)); } /** * @param tokenProducer The source of tokens to be parsed, e.g. {@link Scanner#stringScanner()} * @param Irrelevant * @return A {@link Parser} for expression parsing * @see Parser The expression syntax */ @SuppressWarnings("null") public Parser parser(ProducerWhichThrows, ? extends ScanException> tokenProducer) { return new Parser(tokenProducer) { @Override protected Expression conditional(final Expression lhs, final Expression mhs, final Expression rhs) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.conditional( lhs.evaluate(variables), mhs.evaluate(variables), rhs.evaluate(variables) ); } @Override public String toString() { return lhs + " ? " + mhs + " : " + rhs; } }; } @Override protected Expression unaryOperation(final UnaryOperator operator, final Expression operand) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.unaryOperation(operator, operand.evaluate(variables)); } @Override public String toString() { return operator.toString() + operand; } }; } @Override protected Expression binaryOperation(final Expression lhs, final BinaryOperator op, final Expression rhs) { // "ExpressionUtil.logicalAnd()/logicalOr()" implement valueable optimizations. switch (op) { case LOGICAL_AND: return ExpressionUtil.logicalAnd(lhs, rhs); case LOGICAL_OR: return ExpressionUtil.logicalOr(lhs, rhs); default: ; } return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.binaryOperation( lhs.evaluate(variables), op, rhs.evaluate(variables) ); } @Override public String toString() { return lhs + " " + op + ' ' + rhs; } }; } @Override protected Expression methodInvocation(final Expression target, final String methodName, final List arguments) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { // Determine arguments' values. List argumentValues = new ArrayList(arguments.size()); for (Expression argument : arguments) { argumentValues.add(argument.evaluate(variables)); } return ExpressionEvaluator.invokeMethod( target.evaluate(variables), methodName, argumentValues ); } @Override public String toString() { return target + "." + methodName + '.' + '(' + StringUtil.join(arguments, ", ") + ')'; } }; } @Override protected Expression staticMethodInvocation(final Class target, final String methodName, final List arguments) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { // Determine arguments' values. List argumentValues = new ArrayList(arguments.size()); for (Expression argument : arguments) argumentValues.add(argument.evaluate(variables)); return ExpressionEvaluator.invokeStaticMethod(target, methodName, argumentValues); } @Override public String toString() { return target.getName() + "." + methodName + '.' + '(' + StringUtil.join(arguments, ", ") + ')'; } }; } @Override protected Expression fieldReference(final Expression target, final String fieldName) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.getAttributeValue( target.evaluate(variables), fieldName ); } @Override public String toString() { return target + "." + fieldName; } }; } @Override protected Expression staticFieldReference(final Class type, final String fieldName) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.getStaticAttributeValue( type, fieldName ); } @Override public String toString() { return type.getName() + '.' + fieldName; } }; } @Override protected Expression parenthesized(final Expression exp) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return exp.evaluate(variables); } @Override public String toString() { return '(' + exp.toString() + ')'; } }; } @Override protected Expression instanceoF(final Expression lhs, final Class rhs) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.isInstanceOf(lhs.evaluate(variables), rhs); } @Override public String toString() { return lhs + " instanceof " + rhs; } }; } @Override protected Expression newClass(final Class clasS, final List arguments) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { List argumentValues = new ArrayList(); for (Expression argument : arguments) argumentValues.add(argument.evaluate(variables)); return ExpressionEvaluator.instantiateClass(clasS, argumentValues); } @Override public String toString() { return "new " + clasS.getName() + '(' + StringUtil.join(arguments, ", ") + ')'; } }; } @Override protected Expression newArray(final Class clasS, final List dimensions) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { int n = dimensions.size(); int[] dimensionValues = new int[n]; for (int i = 0; i < n; i++) { dimensionValues[i] = ExpressionUtil.evaluateTo( dimensions.get(i), variables, Integer.class ); } return ExpressionEvaluator.newArrayInstance(clasS, dimensionValues); } @Override public String toString() { int brackets = 0; Class c = clasS; for (; c.isArray(); c = c.getComponentType()) brackets++; StringBuilder sb = new StringBuilder(c.getName()); for (Expression dimension : dimensions) sb.append('[').append(dimension).append(']'); for (int i = 0; i < brackets; i++) sb.append("[]"); return sb.toString(); } }; } @Override protected Expression cast(final Class targetClass, final Expression rhs) { return new AbstractExpression() { @Override @Nullable public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.cast(targetClass, rhs.evaluate(variables)); } @Override public String toString() { return '(' + targetClass.getName() + ") " + rhs; } }; } @Override protected Expression literal(@Nullable Object o) { return ExpressionUtil.constantExpression(o); } @Override protected Expression variableReference(final String variableName) throws ParseException { if (!ExpressionEvaluator.this.isValidVariableName.evaluate(variableName)) { throw new ParseException("Unknown variable '" + variableName + "'"); } return new AbstractExpression() { @Override public Object evaluate(Mapping variables) { Object value = variables.get(variableName); if (value == null && !variables.containsKey(variableName)) { throw new IllegalStateException("Variable '" + variableName + "' missing"); } return value; } @Override public String toString() { return variableName; } }; } @Override protected Expression arrayAccess(final Expression lhs, final Expression rhs) { return new AbstractExpression() { @Override public Object evaluate(Mapping variables) throws EvaluationException { return ExpressionEvaluator.arrayAccess(lhs.evaluate(variables), rhs.evaluate(variables)); } @Override public String toString() { return lhs.toString() + '[' + rhs.toString() + ']'; } }; } }.setImports(this.imports); } /** * Scans, parses and evaluates an expression. * * @throws ParseException {@code spec} refers to a variable which is not contained in {@code variables} * @throws ParseException Any other parse error * @see Parser The expression syntax */ @SuppressWarnings("null") @Nullable public Object evaluate(String spec, final Mapping variables) throws ParseException, EvaluationException { return new Parser(spec) { @Override protected Object conditional(Object lhs, Object mhs, Object rhs) throws EvaluationException { return ExpressionEvaluator.conditional(lhs, mhs, rhs); } @Override protected Object unaryOperation(UnaryOperator operator, Object operand) throws EvaluationException { return ExpressionEvaluator.unaryOperation(operator, operand); } @Override protected Object binaryOperation(Object lhs, BinaryOperator op, Object rhs) throws EvaluationException { return ExpressionEvaluator.binaryOperation(lhs, op, rhs); } @Override protected Object methodInvocation(Object target, String methodName, List arguments) throws EvaluationException { return ExpressionEvaluator.invokeMethod(target, methodName, arguments); } @Override protected Object staticMethodInvocation( Class target, String methodName, List arguments ) throws EvaluationException { return ExpressionEvaluator.invokeStaticMethod(target, methodName, arguments); } @Override protected Object fieldReference(Object target, String fieldName) throws EvaluationException { return ExpressionEvaluator.getAttributeValue(target, fieldName); } @Override protected Object staticFieldReference(Class type, String fieldName) throws EvaluationException { return ExpressionEvaluator.getStaticAttributeValue(type, fieldName); } @Override protected Object parenthesized(Object exp) { return exp; } @Override protected Object instanceoF(@Nullable Object lhs, Class rhs) { return ExpressionEvaluator.isInstanceOf(lhs, rhs); } @Override protected Object newClass(Class clasS, List arguments) throws EvaluationException { return ExpressionEvaluator.instantiateClass(clasS, arguments); } @Override protected Object newArray(Class clasS, List dimensions) { int n = dimensions.size(); int[] dimensionValues = new int[n]; for (int i = 0; i < n; i++) dimensionValues[i] = (Integer) dimensions.get(i); return ExpressionEvaluator.newArrayInstance(clasS, dimensionValues); } @Override protected Object cast(Class targetClass, Object rhs) throws EvaluationException { return ExpressionEvaluator.cast(targetClass, rhs); } @Override protected Object literal(Object o) { return o; } @Override protected Object variableReference(String variableName) throws ParseException { Object value = variables.get(variableName); if (value == null && !variables.containsKey(variableName)) { throw new ParseException("Unknown variable '" + variableName + "'"); } return value; } @Override protected Object arrayAccess(Object lhs, Object rhs) throws EvaluationException { return ExpressionEvaluator.arrayAccess(lhs, rhs); } }.setImports(this.imports).parse(); } private static Object newArrayInstance(Class type, int[] dimensionValues) { return Array.newInstance(type, dimensionValues); } private static boolean isInstanceOf(@Nullable Object value, Class type) { return value != null && type.isAssignableFrom(value.getClass()); } private static Object instantiateClass(Class clasS, List argumentValues) throws EvaluationException { try { // Find most specific constructor. Constructor mostSpecificConstructor = ReflectUtil.getMostSpecificConstructor( clasS, ReflectUtil.getTypes(argumentValues) ); // Create instance. return mostSpecificConstructor.newInstance(argumentValues.toArray()); } catch (Exception e) { throw ExceptionUtil.wrap("Instantiating " + clasS, e, EvaluationException.class); } } @Nullable private static Object cast(Class targetClass, @Nullable Object operand) throws EvaluationException { if (operand == null) return null; if (!targetClass.isAssignableFrom(operand.getClass())) { throw new EvaluationException( "Cannot cast '" + operand.getClass().getName() + "' to '" + targetClass.getName() + "'" ); } return operand; } @Nullable private static Object arrayAccess(Object lhs, Object rhs) throws EvaluationException { Object lhsv = ExpressionEvaluator.to(lhs, Object.class); Integer rhsv = ExpressionEvaluator.toPrimitive(rhs, int.class); return Array.get(lhsv, rhsv); } /** * @return {@code subject.toString()} or {@code null} iff {@code subject == null} */ public static String toString(@Nullable Object subject) { return subject == null ? "" : subject.toString(); } @Nullable private static Object invokeMethod(@Nullable Object lhsv, String methodName, List argumentValues) throws EvaluationException { if (lhsv == null) return null; // Determine arguments' types. Class[] argumentTypes = ReflectUtil.getTypes(argumentValues); // Determine target class. Class clasS = lhsv.getClass(); // Find the most specific method Method mostSpecificMethod; try { mostSpecificMethod = ReflectUtil.getMostSpecificMethod(clasS, methodName, argumentTypes); } catch (NoSuchMethodException nsme) { throw new EvaluationException(nsme); } // Invoke the method. try { return mostSpecificMethod.invoke(lhsv, argumentValues.toArray()); } catch (Exception e) { throw new EvaluationException(e); } } @Nullable private static Object invokeStaticMethod(Class clasS, String methodName, List argumentValues) throws EvaluationException { // Determine arguments' types. Class[] argumentTypes = ReflectUtil.getTypes(argumentValues); // Find the most specific method Method mostSpecificMethod; try { mostSpecificMethod = ReflectUtil.getMostSpecificMethod(clasS, methodName, argumentTypes); } catch (NoSuchMethodException nsme) { throw new EvaluationException(nsme); } if (!Modifier.isStatic(mostSpecificMethod.getModifiers())) { throw new EvaluationException( "Cannot invoke non-static method '" + clasS.getName() + '.' + methodName + "()' in static context" ); } // Invoke the method. try { return mostSpecificMethod.invoke(null, argumentValues.toArray()); } catch (Exception e) { throw new EvaluationException(e); } } /** * Return the value of the given attribute of the given {@code target} object. An attribute is either a PUBLIC * field, or it is retrieved by invoking a getter ("xyz()" or "getXyz()"). */ @Nullable private static Object getAttributeValue(@Nullable Object target, String attributeName) throws EvaluationException { if (target == null) return null; Class clasS = target.getClass(); try { try { return clasS.getField(attributeName).get(target); } catch (NoSuchFieldException nsfe) {} try { return clasS.getMethod(attributeName).invoke(target); } catch (NoSuchMethodException nsme) {} try { return clasS.getMethod( Notations.fromCamelCase(attributeName).prepend("get").toLowerCamelCase() ).invoke(target); } catch (NoSuchMethodException nsme) {} } catch (Exception e) { throw ExceptionUtil.wrap( "Retrieving attribute '" + attributeName + "' of '" + clasS.getName() + "'", e, EvaluationException.class ); } throw new EvaluationException( "'" + clasS.getName() + "' has no field '" + attributeName + "' nor a method '" + attributeName + "()' or 'get" + attributeName + "()' method" ); } /** * Return the value of the given attribute of the given {@code target} object. An attribute is either a PUBLIC * field, or it is retrieved by invoking a getter ("xyz()" or "getXyz()"). */ @Nullable private static Object getStaticAttributeValue(Class target, String attributeName) throws EvaluationException { try { try { return target.getField(attributeName).get(null); } catch (NoSuchFieldException nsfe) {} try { return target.getMethod(attributeName).invoke(null); } catch (NoSuchMethodException nsme) {} try { return target.getMethod( Notations.fromCamelCase(attributeName).prepend("get").toLowerCamelCase() ).invoke(null); } catch (NoSuchMethodException nsme) {} } catch (NullPointerException npe) { throw new EvaluationException( // SUPPRESS CHECKSTYLE AvoidHidingCause "Cannot retrieve nonstatic attribute '" + attributeName + "' in static context" ); } catch (Exception e) { throw ExceptionUtil.wrap( "Retrieving attribute '" + attributeName + "' of '" + target.getName() + "'", e, EvaluationException.class ); } throw new EvaluationException( "'" + target.getName() + "' has no field '" + attributeName + "' nor a method '" + attributeName + "()' or 'get" + ExpressionEvaluator.capitalizeFirstCharacter(attributeName) + "()'" ); } private static String capitalizeFirstCharacter(String s) { return Character.toUpperCase(s.charAt(0)) + s.substring(1); } @Nullable private static Object conditional(@Nullable Object lhs, @Nullable Object mhs, @Nullable Object rhs) throws EvaluationException { return ExpressionEvaluator.to(lhs, Boolean.class) ? mhs : rhs; } @Nullable private static Object unaryOperation(UnaryOperator operator, @Nullable Object operand) throws EvaluationException { switch (operator) { case LOGICAL_COMPLEMENT: return !ExpressionEvaluator.toBoolean(operand); case MINUS: { operand = ExpressionEvaluator.unaryNumericPromotion(operand); if (operand == null) return null; Class clazz = operand.getClass(); if (clazz == Integer.class) return -(Integer) operand; if (clazz == Long.class) return -(Long) operand; if (clazz == Float.class) return -(Float) operand; if (clazz == Double.class) return -(Double) operand; throw new EvaluationException("'" + clazz.getName() + "' operand cannot be negated"); } case BITWISE_COMPLEMENT: { operand = ExpressionEvaluator.unaryNumericPromotion(operand); if (operand == null) return null; Class clazz = operand.getClass(); if (clazz == Integer.class) return ~(Integer) operand; if (clazz == Long.class) return ~(Long) operand; throw new EvaluationException("'" + clazz.getName() + "' operand cannot be complemented"); } default: throw new IllegalStateException(); } } @Nullable private static Object binaryOperation(@Nullable Object lhsv, BinaryOperator op, @Nullable Object rhsv) throws EvaluationException { lhsv = ExpressionEvaluator.binaryNumericPromotion(lhsv, rhsv); rhsv = ExpressionEvaluator.binaryNumericPromotion(rhsv, lhsv); Class lhsc = lhsv == null ? null : lhsv.getClass(); Class rhsc = rhsv == null ? null : rhsv.getClass(); // Check for ARITHMETIC operations first. if (lhsc == Integer.class && rhsc == Integer.class) { @SuppressWarnings("null") int lhsi = (Integer) lhsv, rhsi = (Integer) rhsv; switch (op) { case BITWISE_OR: return lhsi | rhsi; case BITWISE_XOR: return lhsi ^ rhsi; case BITWISE_AND: return lhsi & rhsi; case LEFT_SHIFT: return lhsi << rhsi; case RIGHT_SHIFT: return lhsi >> rhsi; case RIGHT_USHIFT: return lhsi >>> rhsi; case MULTIPLY: return lhsi * rhsi; case DIVIDE: return lhsi / rhsi; case MODULO: return lhsi % rhsi; case PLUS: return lhsi + rhsi; case MINUS: return lhsi - rhsi; default: ; } } else if (lhsc == Long.class && rhsc == Long.class) { @SuppressWarnings("null") long lhsl = (Long) lhsv, rhsl = (Long) rhsv; switch (op) { case BITWISE_OR: return lhsl | rhsl; case BITWISE_XOR: return lhsl ^ rhsl; case BITWISE_AND: return lhsl & rhsl; case LEFT_SHIFT: return lhsl << rhsl; case RIGHT_SHIFT: return lhsl >> rhsl; case RIGHT_USHIFT: return lhsl >>> rhsl; case MULTIPLY: return lhsl * rhsl; case DIVIDE: return lhsl / rhsl; case MODULO: return lhsl % rhsl; case PLUS: return lhsl + rhsl; case MINUS: return lhsl - rhsl; default: ; } } else if (lhsc == Float.class && rhsc == Float.class) { @SuppressWarnings("null") float lhsf = (Float) lhsv, rhsf = (Float) rhsv; switch (op) { case MULTIPLY: return lhsf * rhsf; case DIVIDE: return lhsf / rhsf; case MODULO: return lhsf % rhsf; case PLUS: return lhsf + rhsf; case MINUS: return lhsf - rhsf; default: ; } } else if (lhsc == Double.class && rhsc == Double.class) { @SuppressWarnings("null") double lhsd = (Double) lhsv, rhsd = (Double) rhsv; switch (op) { case MULTIPLY: return lhsd * rhsd; case DIVIDE: return lhsd / rhsd; case MODULO: return lhsd % rhsd; case PLUS: return lhsd + rhsd; case MINUS: return lhsd - rhsd; default: ; } } // Now for the NON-ARITHMETIC operations. switch (op) { case GLOB: // =* return Glob.compile( ExpressionEvaluator.toString(rhsv), Glob.INCLUDES_EXCLUDES | Glob.REPLACEMENT | Pattern2.WILDCARD ).replace(ExpressionEvaluator.toString(lhsv)); case REGEX: // =~ return Glob.compile( ExpressionEvaluator.toString(rhsv), Glob.REPLACEMENT | Pattern.DOTALL ).replace(ExpressionEvaluator.toString(lhsv)); case LOGICAL_AND: // PERLish behavior. return ExpressionEvaluator.toBoolean(lhsv) ? rhsv : Boolean.FALSE; case LOGICAL_OR: // PERLish behavior. return ExpressionEvaluator.toBoolean(lhsv) ? lhsv : rhsv; case BITWISE_OR: // Boolean logical operator '|'. return ExpressionEvaluator.toBoolean(lhsv) | ExpressionEvaluator.toBoolean(rhsv); case BITWISE_XOR: // Boolean logical operator '^'. return ExpressionEvaluator.toBoolean(lhsv) ^ ExpressionEvaluator.toBoolean(rhsv); case BITWISE_AND: // Boolean logical operator '&'. return ExpressionEvaluator.toBoolean(lhsv) & ExpressionEvaluator.toBoolean(rhsv); case LEFT_SHIFT: case RIGHT_SHIFT: case RIGHT_USHIFT: throw new EvaluationException( "Incompatible types for operator '" + op + "' ('" + ExpressionEvaluator.className(lhsc) + "' and '" + ExpressionEvaluator.className(rhsc) + "')" ); case EQUAL: return ObjectUtil.equals(lhsv, rhsv); // VALUE comparison case NOT_EQUAL: return !ObjectUtil.equals(lhsv, rhsv); // VALUE comparison case LESS: return ExpressionEvaluator.compare(lhsv, PredicateUtil.less(0), rhsv); case LESS_EQUAL: return ExpressionEvaluator.compare(lhsv, PredicateUtil.lessEqual(0), rhsv); case GREATER: return ExpressionEvaluator.compare(lhsv, PredicateUtil.greater(0), rhsv); case GREATER_EQUAL: return ExpressionEvaluator.compare(lhsv, PredicateUtil.greaterEqual(0), rhsv); case PLUS: return ExpressionEvaluator.toString(lhsv) + ExpressionEvaluator.toString(rhsv); case MINUS: case MULTIPLY: case DIVIDE: case MODULO: throw new EvaluationException( "Incompatible types for operator '" + op + "' ('" + ExpressionEvaluator.className(lhsc) + "' and '" + ExpressionEvaluator.className(rhsc) + "')" ); default: throw new IllegalStateException(); } } private static String className(@Nullable Class clasS) { return clasS == null ? "null" : clasS.getName(); } @SuppressWarnings({ "unchecked", "rawtypes" }) private static boolean compare( @Nullable Object lhsv, Predicate condition, @Nullable Object rhsv ) throws EvaluationException { if (lhsv == null || rhsv == null) return false; if (lhsv instanceof Comparable && rhsv instanceof Comparable) { return condition.evaluate(((Comparable) lhsv).compareTo(rhsv)); } if (lhsv instanceof CharSequence && rhsv instanceof CharSequence) { return condition.evaluate(StringUtil.compareTo((CharSequence) lhsv, (CharSequence) rhsv)); } throw new EvaluationException( "Operands '" + lhsv.getClass().getName() + "' and '" + rhsv.getClass().getName() + "' cannot be lexicographically compared" ); } @Nullable private static Object unaryNumericPromotion(@Nullable Object value) { if (value == null) return null; Class valueClass = value.getClass(); if (valueClass == Byte.class) return Integer.valueOf(((Byte) value)); if (valueClass == Short.class) return Integer.valueOf(((Short) value)); if (valueClass == Character.class) return Integer.valueOf(((Character) value)); return value; } /** * Converts and returns the {@code value} to {@link Integer}, {@link Long}, {@link Float}, {@link Double} or {@link * String} as appropriate for comparison with {@code other}. * *

Returns the original {@code value} iff the {@code value} is incomparable with {@code other}. * *

Examples: *

* * * * * * * * *
{@code value}{@code other}Result
nullanynull
anynullvalue
non-primitiveanyvalue
anynon-primitivevalue
ByteShortInteger
LongShortLong
ByteStringString
*/ @Nullable protected static Object binaryNumericPromotion(@Nullable Object value, @Nullable Object other) { if (value == null || other == null) return value; Class valueClass = value.getClass(); Class otherClass = other.getClass(); if (valueClass == Byte.class) { byte byteValue = ((Byte) value); if (otherClass == Byte.class) return Integer.valueOf(byteValue); if (otherClass == Short.class) return Integer.valueOf(byteValue); if (otherClass == Integer.class) return Integer.valueOf(byteValue); if (otherClass == Long.class) return Long.valueOf(byteValue); if (otherClass == Float.class) return Float.valueOf(byteValue); if (otherClass == Double.class) return Double.valueOf(byteValue); if (otherClass == String.class) return String.valueOf(byteValue); } else if (valueClass == Short.class) { short shortValue = ((Short) value); if (otherClass == Byte.class) return Integer.valueOf(shortValue); if (otherClass == Short.class) return Integer.valueOf(shortValue); if (otherClass == Integer.class) return Integer.valueOf(shortValue); if (otherClass == Long.class) return Long.valueOf(shortValue); if (otherClass == Float.class) return Float.valueOf(shortValue); if (otherClass == Double.class) return Double.valueOf(shortValue); if (otherClass == String.class) return String.valueOf(shortValue); } else if (valueClass == Character.class) { char charValue = ((Character) value); if (otherClass == Byte.class) return Integer.valueOf(charValue); if (otherClass == Short.class) return Integer.valueOf(charValue); if (otherClass == Integer.class) return Integer.valueOf(charValue); if (otherClass == Long.class) return Long.valueOf(charValue); if (otherClass == Float.class) return Float.valueOf(charValue); if (otherClass == Double.class) return Double.valueOf(charValue); if (otherClass == String.class) return String.valueOf(charValue); } else if (valueClass == Integer.class) { int intValue = ((Integer) value); if (otherClass == Long.class) return Long.valueOf(intValue); if (otherClass == Float.class) return Float.valueOf(intValue); if (otherClass == Double.class) return Double.valueOf(intValue); if (otherClass == String.class) return String.valueOf(intValue); } else if (valueClass == Long.class) { long longValue = ((Long) value); if (otherClass == Float.class) return Float.valueOf(longValue); if (otherClass == Double.class) return Double.valueOf(longValue); if (otherClass == String.class) return String.valueOf(longValue); } else if (valueClass == Float.class) { float floatValue = ((Float) value); if (otherClass == Double.class) return Double.valueOf(floatValue); if (otherClass == String.class) return String.valueOf(floatValue); } else if (valueClass == Double.class) { double doubleValue = ((Double) value); if (otherClass == String.class) return String.valueOf(doubleValue); } return value; } /** * Scans, parses, evaluates and returns an expression. * * @return An object of type {@code T} * @throws EvaluationException The expression evaluates to {@code null} * @throws EvaluationException The value is not assignable to {@code T} * @see Parser The expression syntax */ public T evaluateTo(String spec, Mapping variables, Class targetType) throws ParseException, EvaluationException { Object o = this.evaluate(spec, variables); if (o == null) throw new EvaluationException("Evaluates to null"); if (!targetType.isAssignableFrom(o.getClass())) { throw new EvaluationException("'" + o + "' (type " + o.getClass() + ") is not a " + targetType); } @SuppressWarnings("unchecked") T result = (T) o; return result; } /** * Scans, parses and evaluates an expression. * * @throws EvaluationException The expression evaluates to {@code null} (and the targetType is not boolean.class) * @throws EvaluationException The expression value cannot be converted to the given targetType * @return The (wrapped) expression value * @see Parser The expression syntax */ public Object evaluateToPrimitive(String spec, Mapping variables, Class targetType) throws ParseException, EvaluationException { return ExpressionEvaluator.toPrimitive(this.evaluate(spec, variables), targetType); } /** * Scans, parses and evaluates an expression. * * @return {@code null} iff the {@code spec} is {@code null}; otherwise the expresasion value * @see Parser The expression syntax */ public boolean evaluateToBoolean(@Nullable String spec, Mapping variables) throws ParseException, EvaluationException { return spec != null && ExpressionEvaluator.toBoolean(this.evaluate(spec, variables)); } /** * Converts the given {@code subject} to the given {@code targetType}. * Special processing applies for target types {@link String} and {@link Boolean}. * * @see #toBoolean(Object) * @see #toString(Object) */ @Nullable @SuppressWarnings("unchecked") public static T to(@Nullable Object subject, Class targetType) throws EvaluationException { if (targetType == Boolean.class || targetType == boolean.class) { return (T) (ExpressionEvaluator.FALSES.contains(subject) ? Boolean.FALSE : Boolean.TRUE); } if (subject == null) return null; if (targetType == String.class) { return (T) subject.toString(); } Class sourceClass = subject.getClass(); if (targetType.isAssignableFrom(sourceClass)) return (T) subject; throw new EvaluationException( "Cannot convert '" + sourceClass.getName() + "' to '" + targetType.getName() + "'" ); } /** * Converts the given {@code subject} to the given primitive target type. Special processing applies for target * type {@code boolean.class}, see {@link #toBoolean(Object)}. * * @throws EvaluationException The {@code subject} is {@code null} (and the {@code targetType} is not {@code * boolean.class}) * @throws EvaluationException The {@code subject} cannot be converted to the given {@code targetType} */ @SuppressWarnings("unchecked") public static T toPrimitive(@Nullable Object subject, Class targetType) throws EvaluationException { if (!targetType.isPrimitive()) throw new AssertionError(targetType); if (targetType == boolean.class) return (T) Boolean.valueOf(ExpressionEvaluator.toBoolean(subject)); if (subject == null) { throw new EvaluationException("Cannot convert 'null' to '" + targetType.getName() + "'"); } Class wrapperType = ( targetType == char.class ? Character.class : targetType == byte.class ? Byte.class : targetType == short.class ? Short.class : targetType == int.class ? Integer.class : targetType == long.class ? Long.class : targetType == float.class ? Float.class : targetType == double.class ? Double.class : ExceptionUtil.>throwAssertionError(targetType) ); if (subject.getClass() != wrapperType) { throw new EvaluationException("Cannot convert '" + subject.getClass() + "' to '" + targetType + "'"); } return (T) subject; } /** * @return Whether the {@code subject} equals on of the {@link #FALSES} */ public static boolean toBoolean(@Nullable Object subject) { return !ExpressionEvaluator.FALSES.contains(subject); } /** * All values the are implicitly regarded als {@code false}: *
    *
  • {@code null} *
  • {@code ""} *
  • {@link Boolean#FALSE} *
  • {@code (byte) 0} *
  • {@code (short) 0} *
  • {@code (integer) 0} *
  • {@code (long) 0} *
*/ public static final Set FALSES = new HashSet(); static { ExpressionEvaluator.FALSES.add(null); ExpressionEvaluator.FALSES.add(""); ExpressionEvaluator.FALSES.add(Boolean.FALSE); ExpressionEvaluator.FALSES.add(Byte.valueOf((byte) 0)); ExpressionEvaluator.FALSES.add(Short.valueOf((short) 0)); ExpressionEvaluator.FALSES.add(Integer.valueOf(0)); ExpressionEvaluator.FALSES.add(Long.valueOf(0)); } }