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

org.thymeleaf.standard.expression.ExpressionParsingUtil Maven / Gradle / Ivy

The newest version!
/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2013, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   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 org.thymeleaf.standard.expression;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.thymeleaf.util.ArrayUtils;
import org.thymeleaf.util.StringUtils;
import org.thymeleaf.util.Validate;



/**
 * 
 * @author Daniel Fernández
 * @since 2.1.0
 *
 */
final class ExpressionParsingUtil {


    /*
     *  PARSING STANDARD EXPRESSIONS IS DONE IN TWO PHASES:
     *     1.   Decomposing the expression into its components, by replacing all simple expressions
     *          (${...}, *{...}, @{...}, #{...}, literals and tokens) by placeholders and moving these
     *          expressions apart to their own "parsing nodes" (elements in the ExpressionParsingNode list
     *          contained at the ExpressionParsingState).
     *     1.b. (normally executed at the same time as (1)): Decomposing parenthesis, so that the order of
     *          execution is correctly established.
     *     2.   Composing expressions, by parsing each node in the ExpressionParsingState resulting from steps
     *          1 and 1b, into Expressions objects, which substitute their unparsed Strings in the list of
     *          expression parsing nodes as they are being parsed. The objective is to get an Expression object
     *          (instead of a String) at the position 0 of the node list, which will mean everything has been
     *          correctly parsed.
     *
     *  NOTE 1: At any moment, decomposition or decomposition operations can return null, meaning the expression
     *          is syntactically incorrect.
     *
     *  NOTE 2: The approaches taken for decomposing and composing expressions are different: whereas decomposition is
     *          entirely made by this class, which knows the format of every possible simple expression enough,
     *          composition is delegated to each of the ComplexExpression implementations in turn until one of them
     *          considers the passed expression parsed. This allows a fast simple expression identification and
     *          parsing (simple expressions are the most common case) whereas allows enough flexibility for the
     *          addition of new complex expression types.
     */




    private static final String[] PROTECTED_TOKENS;



    static {
        /*
         * We must add here every expression operator that could be mistaken by a token
         * ("and", "ne", "eq", etc.)
         */
        final List protectedTokenList = new ArrayList(30);
        protectedTokenList.addAll(Arrays.asList(AndExpression.OPERATORS));
        protectedTokenList.addAll(Arrays.asList(EqualsNotEqualsExpression.OPERATORS));
        protectedTokenList.addAll(Arrays.asList(GreaterLesserExpression.OPERATORS));
        protectedTokenList.addAll(Arrays.asList(MultiplicationDivisionRemainderExpression.OPERATORS));
        protectedTokenList.addAll(Arrays.asList(NegationExpression.OPERATORS));
        protectedTokenList.addAll(Arrays.asList(OrExpression.OPERATORS));
        PROTECTED_TOKENS = protectedTokenList.toArray(new String[protectedTokenList.size()]);
    }



    public static ExpressionParsingState decompose(
            final String input, final ExpressionParsingDecompositionConfig config) {
        // Just before starting decomposing simple expressions, we perform the processing of literal substitutions...
        final ExpressionParsingState state =
                decomposeSimpleExpressions(LiteralSubstitutionUtil.performLiteralSubstitution(input), config);
        return (config.getUnnest()? decomposeNestingParenthesis(state, 0) : state);
    }




    private static ExpressionParsingState decomposeSimpleExpressions(
            final String input, final ExpressionParsingDecompositionConfig config) {

        if (input == null) {
            return null;
        }

        final ExpressionParsingState state = new ExpressionParsingState();

        if (StringUtils.isEmptyOrWhitespace(input)) {
            state.addNode(input);
            return state;
        }

        final StringBuilder decomposedInput = new StringBuilder();
        final StringBuilder currentFragment = new StringBuilder();
        int currentIndex = 1;

        int expLevel = 0;
        boolean inLiteral = false;
        boolean inToken = false;
        boolean inNothing = true;

        final int inputLen = input.length();
         for (int i = 0; i < inputLen; i++) {

            /*
             * First, we check for finishing tokens
             */
            if (inToken && !Token.isTokenChar(input, i)) {

                if (finishCurrentToken(currentIndex, state, decomposedInput, currentFragment, config) != null) {
                    // If it's not null, it means the token was really accepted as such, and an expression object
                    // was created for it. So we should increment the index.
                    currentIndex++;
                }

                inToken = false;
                inNothing = true;

            }


            /*
             * Once token end has been checked, process the current character
             */

            final char c = input.charAt(i);

            if (inNothing && c == TextLiteralExpression.DELIMITER && !TextLiteralExpression.isDelimiterEscaped(input, i)) {
                // We are opening a literal

                finishCurrentFragment(decomposedInput, currentFragment);

                currentFragment.append(c);

                inLiteral = true;
                inNothing = false;

            } else if (inLiteral && c == TextLiteralExpression.DELIMITER && !TextLiteralExpression.isDelimiterEscaped(input, i)) {
                // We are closing a literal

                currentFragment.append(c);

                if (config.getDecomposeTextLiterals()) {

                    final TextLiteralExpression expr =
                            TextLiteralExpression.parseTextLiteral(currentFragment.toString());
                    if (addExpressionAtIndex(expr, currentIndex++, state, decomposedInput, currentFragment) == null) {
                        return null;
                    }

                } else {

                    finishCurrentFragment(decomposedInput, currentFragment);

                }

                inLiteral = false;
                inNothing = true;

            } else if (inLiteral) {
                // Nothing else to check, we are in literal and we know we are not closing it. Just add char.

                currentFragment.append(c);

            } else if (inNothing &&
                        (c == VariableExpression.SELECTOR ||
                         c == SelectionVariableExpression.SELECTOR ||
                         c == MessageExpression.SELECTOR ||
                         c == LinkExpression.SELECTOR) &&
                        (i + 1 < inputLen && input.charAt(i+1) == SimpleExpression.EXPRESSION_START_CHAR)) {
                // We are opening an expression

                finishCurrentFragment(decomposedInput, currentFragment);

                currentFragment.append(c);
                currentFragment.append(SimpleExpression.EXPRESSION_START_CHAR);
                i++; // We already know what's at i+1

                expLevel = 1;
                inNothing = false;

            } else if (expLevel == 1 && c == SimpleExpression.EXPRESSION_END_CHAR) {
                // We are closing an expression

                currentFragment.append(SimpleExpression.EXPRESSION_END_CHAR);

                final char expSelectorChar = currentFragment.charAt(0);

                final boolean shouldParseExpression;
                switch (expSelectorChar) {
                    case VariableExpression.SELECTOR:
                        shouldParseExpression = config.getDecomposeVariableExpressions(); break;
                    case SelectionVariableExpression.SELECTOR:
                        shouldParseExpression = config.getDecomposeSelectionVariableExpressions(); break;
                    case MessageExpression.SELECTOR:
                        shouldParseExpression = config.getDecomposeMessageExpressions(); break;
                    case LinkExpression.SELECTOR:
                        shouldParseExpression = config.getDecomposeLinkExpressions(); break;
                    default:
                        return null;
                }

                if (shouldParseExpression) {

                    final Expression expr;
                    switch (expSelectorChar) {
                        case VariableExpression.SELECTOR:
                            expr = VariableExpression.parseVariable(currentFragment.toString()); break;
                        case SelectionVariableExpression.SELECTOR:
                            expr = SelectionVariableExpression.parseSelectionVariable(currentFragment.toString()); break;
                        case MessageExpression.SELECTOR:
                            expr = MessageExpression.parseMessage(currentFragment.toString()); break;
                        case LinkExpression.SELECTOR:
                            expr = LinkExpression.parseLink(currentFragment.toString()); break;
                        default:
                            return null;
                    }

                    if (addExpressionAtIndex(expr, currentIndex++, state, decomposedInput, currentFragment) == null) {
                        return null;
                    }

                } else {

                    finishCurrentFragment(decomposedInput, currentFragment);

                }

                expLevel = 0;
                inNothing = true;

            } else if (expLevel > 0 && c == SimpleExpression.EXPRESSION_START_CHAR) {
                // We are in an expression. This is needed for correct nesting/unnesting of expressions

                expLevel++;
                currentFragment.append(SimpleExpression.EXPRESSION_START_CHAR);

            } else if (expLevel > 1 && c == SimpleExpression.EXPRESSION_END_CHAR) {
                // We are in an expression. This is needed for correct nesting/unnesting of expressions

                expLevel--;
                currentFragment.append(SimpleExpression.EXPRESSION_END_CHAR);

            } else if (expLevel > 0) {
                // We are in an expression and not closing it, so just add the char

                currentFragment.append(c);

            } else if (inNothing && Token.isTokenChar(input, i)) {
                // We are opening a token

                finishCurrentFragment(decomposedInput, currentFragment);

                currentFragment.append(c);

                inToken = true;
                inNothing = false;

            } else {
                // We might be in a token or not. Doesn't matter. If we are,
                // at this point we already know c is a valid token char.

                currentFragment.append(c);

            }


        }

        if (inLiteral || expLevel > 0) {
            return null;
        }

        if (inToken) {
            // las part was a token, add it

            if (finishCurrentToken(currentIndex++, state, decomposedInput, currentFragment, config) != null) {
                // If it's not null, it means the token was really accepted as such, and an expression object
                // was created for it. So we should increment the index.
                currentIndex++;
            }

        }

        decomposedInput.append(currentFragment);

        state.insertNode(0, decomposedInput.toString());

        return state;

    }



    private static Expression addExpressionAtIndex(final Expression expression, final int index,
            final ExpressionParsingState state, final StringBuilder decomposedInput,
            final StringBuilder currentFragment) {

        if (expression == null) {
            return null;
        }

        decomposedInput.append(Expression.PARSING_PLACEHOLDER_CHAR);
        decomposedInput.append(String.valueOf(index));
        decomposedInput.append(Expression.PARSING_PLACEHOLDER_CHAR);
        state.addNode(expression);
        currentFragment.setLength(0);

        return expression;

    }



    private static void finishCurrentFragment(
            final StringBuilder decomposedInput, final StringBuilder currentFragment) {
        decomposedInput.append(currentFragment);
        currentFragment.setLength(0);
    }



    private static Expression finishCurrentToken(
            final int currentIndex, final ExpressionParsingState state, final StringBuilder decomposedInput,
            final StringBuilder currentFragment, final ExpressionParsingDecompositionConfig config) {

        final String token = currentFragment.toString();

        final Expression expr = parseAsToken(token, config);
        if (addExpressionAtIndex(expr, currentIndex, state, decomposedInput, currentFragment) == null) {
            // Token was not considered as such, so we just push the fragment into the input string
            decomposedInput.append(currentFragment);
            currentFragment.setLength(0);
            return null;
        }

        return expr;

    }




    private static Expression parseAsToken(final String token, final ExpressionParsingDecompositionConfig config) {

        if (ArrayUtils.contains(PROTECTED_TOKENS, token.toLowerCase())) {
            // If token is protected, we should just do nothing. Returning null should force the caller
            // to push the fragment back into the input string and continue parsing
            return null;
        }

        if (config.getDecomposeNumberTokens()) {
            final NumberTokenExpression numberTokenExpr = NumberTokenExpression.parseNumberToken(token);
            if (numberTokenExpr != null) {
                return numberTokenExpr;
            }
        }

        if (config.getDecomposeBooleanTokens()) {
            final BooleanTokenExpression booleanTokenExpr = BooleanTokenExpression.parseBooleanToken(token);
            if (booleanTokenExpr != null) {
                return booleanTokenExpr;
            }
        }

        if (config.getDecomposeNullTokens()) {
            final NullTokenExpression nullTokenExpr = NullTokenExpression.parseNullToken(token);
            if (nullTokenExpr != null) {
                return nullTokenExpr;
            }
        }

        if (config.getDecomposeGenericTokens()) {
            final GenericTokenExpression genericTokenExpr = GenericTokenExpression.parseGenericToken(token);
            if (genericTokenExpr != null) {
                return genericTokenExpr;
            }
        }

        return null;

    }














    public static ExpressionParsingState unnest(final ExpressionParsingState state) {
        Validate.notNull(state, "Parsing state cannot be null");
        return decomposeNestingParenthesis(state, 0);
    }



    private static ExpressionParsingState decomposeNestingParenthesis(
            final ExpressionParsingState state, final int nodeIndex) {

        if (state == null || nodeIndex >= state.size()) {
            return null;
        }

        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        final String input = state.get(nodeIndex).getInput();

        final StringBuilder decomposedString = new StringBuilder();
        final StringBuilder currentFragment = new StringBuilder();
        int currentIndex = state.size();
        final List nestedInputs = new ArrayList(6);

        int parLevel = 0;

        final int inputLen = input.length();
        for (int i = 0; i < inputLen; i++) {

            final char c = input.charAt(i);

            if (c == Expression.NESTING_START_CHAR) {

                if (parLevel == 0) {
                    // starting nested
                    decomposedString.append(currentFragment);
                    currentFragment.setLength(0);
                } else {
                    currentFragment.append(Expression.NESTING_START_CHAR);
                }

                parLevel++;


            } else if (c == Expression.NESTING_END_CHAR) {

                parLevel--;

                if (parLevel < 0) {
                    return null;
                }

                if (parLevel == 0) {
                    // ending nested
                    final int nestedIndex = currentIndex++;
                    nestedInputs.add(Integer.valueOf(nestedIndex));
                    decomposedString.append(Expression.PARSING_PLACEHOLDER_CHAR);
                    decomposedString.append(String.valueOf(nestedIndex));
                    decomposedString.append(Expression.PARSING_PLACEHOLDER_CHAR);
                    state.addNode(currentFragment.toString());
                    currentFragment.setLength(0);
                } else {
                    currentFragment.append(Expression.NESTING_END_CHAR);
                }

            } else {

                currentFragment.append(c);

            }

        }

        if (parLevel > 0) {
            return null;
        }

        decomposedString.append(currentFragment);

        state.setNode(nodeIndex, decomposedString.toString());


        for (final Integer nestedInput : nestedInputs) {
            if (decomposeNestingParenthesis(state, nestedInput.intValue()) == null) {
                return null;
            }
        }

        return state;

    }











    public static ExpressionParsingState compose(final ExpressionParsingState state) {
        return compose(state, 0);
    }




    static ExpressionParsingState compose(final ExpressionParsingState state, final int nodeIndex) {

        if (state == null || nodeIndex >= state.size()) {
            return null;
        }

        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        final String input = state.get(nodeIndex).getInput();

        if (StringUtils.isEmptyOrWhitespace(input)) {
            return null;
        }

        /*
         * STEP 1: Check whether node is just an expression placeholder (like '%123%')
         *         and, if it is, simply substitute it by its referenced expression.
         */

        final int parsedIndex = parseAsSimpleIndexPlaceholder(input);
        if (parsedIndex != -1) {
            if (compose(state, parsedIndex) == null) {
                return null;
            }
            if (!state.hasExpressionAt(parsedIndex)) {
                return null;
            }
            state.setNode(nodeIndex, state.get(parsedIndex).getExpression());
            return state;
        }

        /*
         * STEP 2: Try composing this node as a conditional expression
         */

        if (ConditionalExpression.composeConditionalExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 3: Try composing this node as a default expression
         */

        if (DefaultExpression.composeDefaultExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 4: Try composing this node as an OR expression
         */

        if (OrExpression.composeOrExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 5: Try composing this node as an AND expression
         */

        if (AndExpression.composeAndExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 6: Try composing this node as an EQUALS or NOT EQUALS expression
         */

        if (EqualsNotEqualsExpression.composeEqualsNotEqualsExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 7: Try composing this node as a GREATER or LESSER expression
         */

        if (GreaterLesserExpression.composeGreaterLesserExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 8: Try composing this node as a ADDITION or SUBTRACTION expression
         */

        if (AdditionSubtractionExpression.composeAdditionSubtractionExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 9: Try composing this node as a MULTIPLICATION, DIVISION or REMAINDER expression
         */

        if (MultiplicationDivisionRemainderExpression.composeMultiplicationDivisionRemainderExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 10: Try composing this node as a MINUS expression
         */

        if (MinusExpression.composeMinusExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        /*
         * STEP 11: Try composing this node as a NEGATION expression
         */

        if (NegationExpression.composeNegationExpression(state, nodeIndex) == null) {
            return null;
        }
        if (state.hasExpressionAt(nodeIndex)) {
            return state;
        }

        return null;

    }





    public static int parseAsSimpleIndexPlaceholder(final String placeholder) {
        // Input should never be null
        final String str = placeholder.trim();
        final int strLen = str.length();
        if (strLen <= 2) {
            return -1;
        }
        if (str.charAt(0) != Expression.PARSING_PLACEHOLDER_CHAR || str.charAt(strLen-1) != Expression.PARSING_PLACEHOLDER_CHAR) {
            return -1;
        }
        for (int i = 1; i < strLen-1; i++) {
            if (!Character.isDigit(str.charAt(i))) {
                return -1;
            }
        }
        return Integer.parseInt(str.substring(1, strLen - 1));
    }




    static Expression parseAndCompose(final ExpressionParsingState state, final String parseTarget) {
        /*
         * Takes a String (probably a substring of a parsing node), checks whether it is a reference to
         * another node or not and, if not, performs a composition operation on it. Then just composes
         * the target.
         */
        int index = parseAsSimpleIndexPlaceholder(parseTarget);
        if (index == -1) {
            // parseTarget is not a mere index placeholder, so add it and compose it
            index = state.size();
            state.addNode(parseTarget);
        }
        if (compose(state, index) == null || !state.hasExpressionAt(index)) {
            return null;
        }
        return state.get(index).getExpression();
    }




    private ExpressionParsingUtil() {
        super();
    }




}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy