<commit_before>/*
 * Copyright 2006-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.consol.citrus.util;

import com.consol.citrus.exceptions.CitrusRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * Parses boolean expression strings and evaluates to boolean result.
 *
 * @author <NAME>
 */
@SuppressWarnings("unchecked")
public final class BooleanExpressionParser {

    /**
     * List of known operators
     */
    private static final List<String> OPERATORS = new ArrayList<String>(
            CollectionUtils.arrayToList(new String[]{"=", "and", "or", "lt", "lt=", "gt", "gt="}));

    /**
     * List of known boolean values
     */
    private static final List<String> BOOLEAN_VALUES = new ArrayList<String>(
            CollectionUtils.arrayToList(new String[]{"true", "false"}));

    /**
     * SeparatorToken is an explicit type to identify different kinds of separators.
     */
    private enum SeparatorToken {
        SPACE(' '),
        OPEN_PARENTHESIS('('),
        CLOSE_PARENTHESIS(')');

        private final Character value;

        SeparatorToken(final Character value) {
            this.value = value;
        }
    }

    /**
     * Logger
     */
    private static Logger log = LoggerFactory.getLogger(BooleanExpressionParser.class);

    /**
     * Prevent instantiation.
     */
    private BooleanExpressionParser() {
    }

    /**
     * Perform evaluation of boolean expression string.
     *
     * @param expression The expression to evaluate
     * @return boolean result
     * @throws CitrusRuntimeException
     */
    public static boolean evaluate(final String expression) {
        final Stack<String> operators = new Stack<>();
        final Stack<String> values = new Stack<>();
        boolean result;

        char currentCharacter;

        try {
            for (int currentCharacterIndex = 0; currentCharacterIndex < expression.length(); currentCharacterIndex++) {
                currentCharacter = expression.charAt(currentCharacterIndex);

                if (SeparatorToken.OPEN_PARENTHESIS.value == currentCharacter) {
                    operators.push(SeparatorToken.OPEN_PARENTHESIS.value.toString());
                } else if (SeparatorToken.SPACE.value == currentCharacter) {
                    continue; //ignore
                } else if (SeparatorToken.CLOSE_PARENTHESIS.value == currentCharacter) {
                    String operator = operators.pop();
                    while (!(operator).equals(SeparatorToken.OPEN_PARENTHESIS.value.toString())) {
                        values.push(getBooleanResultAsString(operator, values.pop(), values.pop()));
                        operator = operators.pop();
                    }
                } else if (!Character.isDigit(currentCharacter)) {
                    final StringBuffer operatorBuffer = new StringBuffer();

                    int subExpressionIndex = currentCharacterIndex;
                    do {
                        operatorBuffer.append(currentCharacter);
                        subExpressionIndex++;

                        if (subExpressionIndex < expression.length()) {
                            currentCharacter = expression.charAt(subExpressionIndex);
                        }
                    } while (subExpressionIndex < expression.length() && !Character.isDigit(currentCharacter) && !isSeparatorToken(currentCharacter));

                    currentCharacterIndex = subExpressionIndex - 1;

                    if (BOOLEAN_VALUES.contains(operatorBuffer.toString())) {
                        values.push(Boolean.valueOf(operatorBuffer.toString()) ? "1" : "0");
                    } else {
                        operators.push(validateOperator(operatorBuffer.toString()));
                    }
                } else if (Character.isDigit(currentCharacter)) {
                    final StringBuffer digitBuffer = new StringBuffer();

                    int subExpressionIndex = currentCharacterIndex;
                    do {
                        digitBuffer.append(currentCharacter);
                        subExpressionIndex++;

                        if (subExpressionIndex < expression.length()) {
                            currentCharacter = expression.charAt(subExpressionIndex);
                        }
                    } while (subExpressionIndex < expression.length() && Character.isDigit(currentCharacter));

                    currentCharacterIndex = subExpressionIndex - 1;

                    values.push(digitBuffer.toString());
                }
            }

            while (!operators.isEmpty()) {
                values.push(getBooleanResultAsString(operators.pop(), values.pop(), values.pop()));
            }

            String value = values.pop();

            if (value.equals("0")) {
                value = "false";
            } else if (value.equals("1")) {
                value = "true";
            }

            result = Boolean.valueOf(value);

            if (log.isDebugEnabled()) {
                log.debug("Boolean expression " + expression + " evaluates to " + result);
            }
        } catch (final EmptyStackException e) {
            throw new CitrusRuntimeException("Unable to parse boolean expression '" + expression + "'. Maybe expression is incomplete!", e);
        }

        return result;
    }

    /**
     * This method reads digit characters from a given string, starting at a given index.
     * It will read till the end of the string or up until it encounters a non-digit character
     *
     * @param expression The string to parse
     * @param startIndex The start index from where to parse
     * @return The parsed substring
     */
    private static String parseDigits(final String expression, final int startIndex) {
        final StringBuffer digitBuffer = new StringBuffer();

        char currentCharacter = expression.charAt(startIndex);
        int subExpressionIndex = startIndex;
<commit_msg>Added separate methods to evaluate expressions<commit_after>/*
 * Copyright 2006-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.consol.citrus.util;

import com.consol.citrus.exceptions.CitrusRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Stack;

/**
 * Parses boolean expression strings and evaluates to boolean result.
 *
 * @author <NAME>
 */
@SuppressWarnings("unchecked")
public final class BooleanExpressionParser {

    /**
     * List of known operators
     */
    private static final List<String> OPERATORS = new ArrayList<String>(
            CollectionUtils.arrayToList(new String[]{"=", "and", "or", "lt", "lt=", "gt", "gt="}));

    /**
     * List of known boolean values
     */
    private static final List<String> BOOLEAN_VALUES = new ArrayList<String>(
            CollectionUtils.arrayToList(new String[]{"true", "false"}));

    /**
     * SeparatorToken is an explicit type to identify different kinds of separators.
     */
    private enum SeparatorToken {
        SPACE(' '),
        OPEN_PARENTHESIS('('),
        CLOSE_PARENTHESIS(')');

        private final Character value;

        SeparatorToken(final Character value) {
            this.value = value;
        }
    }

    /**
     * Logger
     */
    private static Logger log = LoggerFactory.getLogger(BooleanExpressionParser.class);

    /**
     * Prevent instantiation.
     */
    private BooleanExpressionParser() {
    }

    /**
     * Perform evaluation of boolean expression string.
     *
     * @param expression The expression to evaluate
     * @return boolean result
     * @throws CitrusRuntimeException
     */
    public static boolean evaluate(final String expression) {
        final Stack<String> operators = new Stack<>();
        final Stack<String> values = new Stack<>();
        boolean result;

        char currentCharacter;

        try {
            for (int currentCharacterIndex = 0; currentCharacterIndex < expression.length(); currentCharacterIndex++) {
                currentCharacter = expression.charAt(currentCharacterIndex);

                if (SeparatorToken.OPEN_PARENTHESIS.value == currentCharacter) {
                    operators.push(SeparatorToken.OPEN_PARENTHESIS.value.toString());
                } else if (SeparatorToken.SPACE.value == currentCharacter) {
                    continue; //ignore
                } else if (SeparatorToken.CLOSE_PARENTHESIS.value == currentCharacter) {
                    String operator = operators.pop();
                    while (!(operator).equals(SeparatorToken.OPEN_PARENTHESIS.value.toString())) {
                        values.push(getBooleanResultAsString(operator, values.pop(), values.pop()));
                        operator = operators.pop();
                    }
                } else if (!Character.isDigit(currentCharacter)) {
                    final StringBuffer operatorBuffer = new StringBuffer();

                    int subExpressionIndex = currentCharacterIndex;
                    do {
                        operatorBuffer.append(currentCharacter);
                        subExpressionIndex++;

                        if (subExpressionIndex < expression.length()) {
                            currentCharacter = expression.charAt(subExpressionIndex);
                        }
                    } while (subExpressionIndex < expression.length() && !Character.isDigit(currentCharacter) && !isSeparatorToken(currentCharacter));

                    currentCharacterIndex = subExpressionIndex - 1;

                    if (BOOLEAN_VALUES.contains(operatorBuffer.toString())) {
                        values.push(Boolean.valueOf(operatorBuffer.toString()) ? "1" : "0");
                    } else {
                        operators.push(validateOperator(operatorBuffer.toString()));
                    }
                } else if (Character.isDigit(currentCharacter)) {
                    final StringBuffer digitBuffer = new StringBuffer();

                    int subExpressionIndex = currentCharacterIndex;
                    do {
                        digitBuffer.append(currentCharacter);
                        subExpressionIndex++;

                        if (subExpressionIndex < expression.length()) {
                            currentCharacter = expression.charAt(subExpressionIndex);
                        }
                    } while (subExpressionIndex < expression.length() && Character.isDigit(currentCharacter));

                    currentCharacterIndex = subExpressionIndex - 1;

                    values.push(digitBuffer.toString());
                }
            }

            while (!operators.isEmpty()) {
                values.push(getBooleanResultAsString(operators.pop(), values.pop(), values.pop()));
            }

            String value = values.pop();

            if (value.equals("0")) {
                value = "false";
            } else if (value.equals("1")) {
                value = "true";
            }

            result = Boolean.valueOf(value);

            if (log.isDebugEnabled()) {
                log.debug("Boolean expression " + expression + " evaluates to " + result);
            }
        } catch (final EmptyStackException e) {
            throw new CitrusRuntimeException("Unable to parse boolean expression '" + expression + "'. Maybe expression is incomplete!", e);
        }

        return result;
    }

    /**
     * This method takes stacks of operators and values and evaluates possible expressions
     * This is done by popping one operator and two values, applying the operator to the values and pushing the result back onto the value stack
     * @param operators Operators to apply
     * @param values Values
     * @return The final result popped of the values stack
     */
    private static String evaluateExpressionStack(final Stack<String> operators, final Stack<String> values) {
        while (!operators.isEmpty()) {
            values.push(getBooleanResultAsString(operators.pop(), values.pop(), values.pop()));
        }
        return replaceIntegerStringByBooleanRepresentation(values.pop());
    }

    /**
     * Evaluates a sub expression within a pair of parentheses and pushes its result onto the stack of values
     * @param operators Stack of operators
     * @param values Stack of values
     */
    private static void evaluateSubexpression(final Stack<String> operators, final Stack<String> values) {
        String operator = operators.pop();
        while (!(operator).equals(SeparatorToken.OPEN_PARENTHESIS.value.toString())) {
            values.push(getBooleanResultAsString(operator,
                    replaceBooleanStringByIntegerRepresentation(values.pop()),
                    replaceBooleanStringByIntegerRepresentation(values.pop())));
            operator = operators.pop();
        }
    }

    /**
     * This method reads digit characters from a given string, starting at a given index.
     * It will read till the end of the string or up until it encounters a non-digit character
     *
     * @param expression The string to parse
     * @param startIndex The start index from where to parse
     * @return The parsed substring
     */
    private static String parseDigits(final String expression, final int startIndex) {
        final StringBuffer digitBuffer = new StringBuffer();

        char currentCharacter = expression.charAt(startIndex);
        int subExpressionIndex = startIndex;

{'fraction_non_alphanumeric': 0.09291019581363943, 'fraction_numerical': 0.002835921674544227, 'mean_word_length': 2.1674508126603933, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4048824', 'n_tokens_mistral': 3547, 'n_tokens_neox': 3370, 'n_words': 992}