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

de.unkrig.commons.text.expression.ExpressionUtil 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) 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.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.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.
     */
    public static Expression
    constantExpression(@Nullable final Object value) {

        return new AbstractExpression() {

            @Nullable @Override 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, final Set variableNames) throws ParseException {

        return ExpressionUtil.expand(s, new Predicate() {
            @Override public boolean evaluate(String subject) { return variableNames.contains(subject); }
        });
    }

    /**
     * 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 {@code subject} is a valid variable name
     * @return                    The expanded string
     */
    public static Expression
    expand(String s, Predicate 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 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, Mapping variables, Class targetClass) throws EvaluationException {
        return ExpressionEvaluator.to(expression.evaluate(variables), targetClass);
    }

    /**
     * @param parameterName The name under which the predicate subject is accessible for the {@code expression}
     * @return              A {@link Predicate} which evaluates to the value of the given {@code expression}
     */
    public static  Predicate
    toPredicate(final Expression expression, final String parameterName) {

        return new Predicate() {

            @Override public boolean
            evaluate(T subject) {

                Object result;
                try {
                    result = expression.evaluate(Mappings.mapping(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 {@code predicate}, where the subject
     *         to the predicate is the value of the named variable of the expression
     */
    public static Expression
    fromPredicate(final Predicate 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 {@code operand1}, and, if that is {@code true} the
     *         {@code 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 {@code operand1}, and, if that is {@code false} the
     *         {@code 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, variables, boolean.class)
                    ? true
                    : operand2.evaluate(variables)
                );
            }
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy