de.unkrig.commons.text.expression.ExpressionUtil Maven / Gradle / Ivy
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2011, 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.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.unkrig.commons.lang.protocol.Mapping;
import de.unkrig.commons.lang.protocol.Mappings;
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.text.expression.Scanner.TokenType;
import de.unkrig.commons.text.parser.ParseException;
import de.unkrig.commons.text.scanner.AbstractScanner.Token;
import de.unkrig.commons.text.scanner.ScanException;
import de.unkrig.commons.text.scanner.StringScanner;
/**
* Utility class for {@link Expression}.
*/
public final
class ExpressionUtil {
private
ExpressionUtil() {}
/**
* @return An {@link Expression} that evaluates to a constant value;
* {@link Expression#TRUE} iff the expression is constant and evaluates to {@code true};
* {@link Expression#FALSE} iff the expression is constant and evaluates to {@code false};
* {@link Expression#NULL} iff the expression is constant and evaluates to {@code null}
*/
public static Expression
constantExpression(@Nullable final Object value) {
if (Boolean.TRUE.equals(value)) return Expression.TRUE;
if (Boolean.FALSE.equals(value)) return Expression.FALSE;
if (value == null) return Expression.NULL;
return ExpressionUtil.constantExpression2(value);
}
/**
* Internal implementation of {@link #constantExpression(Object)}; does not optimize special values
*/
static Expression
constantExpression2(@Nullable final Object value) {
return new AbstractExpression() {
@Override @Nullable public Object
evaluate(Mapping variables) { return value; }
@Override public String
toString() {
return (
value instanceof String ? "\"" + value + '"' :
value instanceof Character ? "'" + value + '\'' :
value instanceof Long ? value + "L" :
value instanceof Float ? value + "F" :
value instanceof Double ? value + "D" :
String.valueOf(value)
);
}
};
}
private static Expression
concat(final List expressions) {
if (expressions.size() == 1) return expressions.get(0);
return new AbstractExpression() {
@Override public Object
evaluate(Mapping variables) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : expressions) {
sb.append(expression.evaluate(variables));
}
return sb.toString();
}
@Override public String
toString() {
StringBuilder sb = new StringBuilder();
for (Expression expression : expressions) {
sb.append('#').append(expression).append('#');
}
return sb.toString();
}
};
}
/**
* Turns the given string into an expression. If the string contains '#' characters, then the text between two
* '#' characters is parsed as an expression.
*
* @param s The string to expand
* @param variableNames All contained variable names can be used in the expression
* @return The expanded string
*/
public static Expression
expand(String s, String... variableNames) throws ParseException {
return ExpressionUtil.expand(s, new HashSet(Arrays.asList(variableNames)));
}
/**
* Turns the given string into an expression. If the string contains '#' characters, then the text between two
* '#' characters is parsed as an expression.
*
* @param s The string to expand
* @param variableNames All contained variable names can be used in the expression
* @return The expanded string
*/
public static Expression
expand(String s, Set variableNames) throws ParseException {
return ExpressionUtil.expand(s, PredicateUtil.contains(variableNames));
}
/**
* Turns the given string into an expression. If the string contains '#' characters, then the text between two
* '#' characters is parsed as an expression.
*
* @param s The string to expand
* @param isValidVariableName Evalutaes to whether the subject is a valid variable name
* @return The expanded string
*/
public static Expression
expand(String s, Predicate super String> isValidVariableName) throws ParseException {
// Does the string contain a '#...#' expression at all?
int idx = s.indexOf('#');
if (idx == -1) return ExpressionUtil.constantExpression(s);
List expressions = new ArrayList();
if (idx != 0) expressions.add(ExpressionUtil.constantExpression(s.substring(0, idx)));
for (;;) {
// 'idx' points to the opening '#'.
final String input = s.substring(idx + 1);
final StringScanner stringScanner = Scanner.stringScanner().setInput(input);
Expression exp = new ExpressionEvaluator(isValidVariableName).parse(
new ProducerWhichThrows, ScanException>() {
@Override @Nullable public Token
produce() throws ScanException {
if (input.charAt(stringScanner.getOffset()) == '#') return null;
return stringScanner.produce();
}
@Override @Nullable public String
toString() {
return stringScanner.toString();
}
}
);
expressions.add(exp);
idx += 1 + stringScanner.getOffset() + 1;
if (idx >= s.length()) break;
int idx2 = s.indexOf('#', idx);
if (idx2 == -1) {
expressions.add(ExpressionUtil.constantExpression(s.substring(idx)));
break;
}
expressions.add(ExpressionUtil.constantExpression(s.substring(idx, idx2)));
idx = idx2;
}
return ExpressionUtil.concat(expressions);
}
/**
* @return The result of the evaluation, or a verbose HTML comment reporting the error
*/
public static String
evaluateLeniently(Expression expression, Mapping variables) {
try {
return String.valueOf(expression.evaluate(variables));
} catch (EvaluationException ee) {
return "";
} catch (Exception e) {
return "";
}
}
/**
* @return The result of the evaluation, or a verbose HTML comment reporting the error
*/
public static String
evaluateLeniently(Expression expression, Object... variableNamesAndValues) {
return ExpressionUtil.evaluateLeniently(expression, Mappings.mapping(variableNamesAndValues));
}
/**
* @return The evaluated value
* @throws EvaluationException The expression evaluates to {@code null}
* @throws EvaluationException A problem occurred during evaluation, e.g. a class could not be loaded, or a type
* cast could not be performed
*/
@Nullable public static T
evaluateTo(Expression expression, Class targetClass, Mapping variables) throws EvaluationException {
return ExpressionEvaluator.to(expression.evaluate(variables), targetClass);
}
/**
* @return The evaluated value
* @throws EvaluationException The expression evaluates to {@code null}
* @throws EvaluationException A problem occurred during evaluation, e.g. a class could not be loaded, or a type
* cast could not be performed
*/
@Nullable public static T
evaluateTo(Expression expression, Class targetClass, Object... variableNamesAndValues)
throws EvaluationException {
return ExpressionEvaluator.to(expression.evaluate(variableNamesAndValues), targetClass);
}
/**
* Scans, parses and evaluates an expression, and converts the result to {@code }.
*
* @param onDemandImports Fully qualified package names
* @param variables The variables that the expression can use
* @return The evaluated value
*/
@Nullable public static T
evaluateExpressionTo(
@Nullable String[] onDemandImports,
String input,
Class targetType,
Mapping variables
) throws ParseException, EvaluationException {
ExpressionEvaluator ee = new ExpressionEvaluator(PredicateUtil.containsKey(variables));
if (onDemandImports != null) ee.addOnDemandImports(onDemandImports);
return ee.evaluateTo(input, targetType, variables);
}
/**
* @param The type of the predicate's subject
* @param parameterName The name under which the predicate subject is accessible for the expression
* @return A {@link Predicate} which evaluates to the value of the given expression;
* {@link Expression#TRUE} iff the expression is constant and evaluates to
* {@code true};
* {@link Expression#FALSE} iff the expression is constant and evaluates to
* {@code false}
*/
public static Predicate
toPredicate(final Expression expression, final String parameterName) {
if (expression == Expression.TRUE) return PredicateUtil.always();
if (expression == Expression.FALSE) return PredicateUtil.never();
return new Predicate() {
@Override public boolean
evaluate(T subject) {
Object result;
try {
result = expression.evaluate(parameterName, subject);
} catch (EvaluationException ee) {
throw new RuntimeException(ee);
}
return ExpressionEvaluator.toBoolean(result);
}
@Override public String
toString() { return expression.toString(); }
};
}
/**
* @return An {@link Expression} which evaluates to the result of the given predicate, where the subject
* to the predicate is the value of the named variable of the expression
*/
public static Expression
fromPredicate(final Predicate super String> predicate, final String variableName) {
return new AbstractExpression() {
@Override @Nullable public Object
evaluate(Mapping variables) throws EvaluationException {
Object variableValue = variables.get(variableName);
if (variableValue == null) throw new EvaluationException("Variable '" + variableName + "' not set");
if (!(variableValue instanceof String)) {
throw new EvaluationException(
"Variable '"
+ variableName
+ "' has unexpected type '"
+ variableValue.getClass()
+ "'"
);
}
return predicate.evaluate((String) variableValue);
}
};
}
/**
* @return An {@link Expression} which evaluates the operand1, and, if that is {@code true} the
* operand2
*/
public static Expression
logicalAnd(final Expression operand1, final Expression operand2) {
if (operand1 == Expression.FALSE) return Expression.FALSE;
if (operand1 == Expression.TRUE) return operand2;
if (operand2 == Expression.TRUE) return operand1;
return new AbstractExpression() {
@Override @Nullable public Object
evaluate(Mapping variables) throws EvaluationException {
return (
ExpressionEvaluator.toBoolean(operand1.evaluate(variables))
? operand2.evaluate(variables)
: false
);
}
};
}
/**
* @return An {@link Expression} which evaluates the operand1, and, if that is {@code false} the
* operand2
*/
public static Expression
logicalOr(final Expression operand1, final Expression operand2) {
if (operand1 == Expression.TRUE) return Expression.TRUE;
if (operand1 == Expression.FALSE) return operand2;
if (operand2 == Expression.FALSE) return operand1;
return new AbstractExpression() {
@Override @Nullable public Object
evaluate(Mapping variables) throws EvaluationException {
return (
ExpressionUtil.evaluateTo(operand1, boolean.class, variables)
? true
: operand2.evaluate(variables)
);
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy