ExpressionResolver.java

package org.jbehave.core.expressions;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExpressionResolver {

    private static final Pattern GREEDY_EXPRESSION_PATTERN = Pattern.compile("#\\{((?:(?!#\\{|\\$\\{).)*)}",
            Pattern.DOTALL);
    private static final Pattern RELUCTANT_EXPRESSION_PATTERN = Pattern.compile(
            "#\\{((?:(?![#{])[^)](?![()]))*?|(?:(?!#\\{|\\$\\{).)*?\\)|(?:(?!#\\{|\\$\\{).)*?)}",
            Pattern.DOTALL);
    private static final List<Pattern> PATTERNS = Arrays.asList(RELUCTANT_EXPRESSION_PATTERN,
            GREEDY_EXPRESSION_PATTERN);

    private static final String REPLACEMENT_PATTERN = "\\#\\{%s\\}";

    private final Set<ExpressionProcessor<?>> expressionProcessors;
    private final ExpressionResolverMonitor expressionResolverMonitor;

    public ExpressionResolver(Set<ExpressionProcessor<?>> expressionProcessors,
            ExpressionResolverMonitor expressionResolverMonitor) {
        this.expressionProcessors = expressionProcessors;
        this.expressionResolverMonitor = expressionResolverMonitor;
    }

    /**
     * Evaluates expressions including nested ones.
     * <br>
     * Syntax:
     * <br>
     * <code>
     * #{expression(arguments...)}
     * #{expression(arguments..., #{expression(arguments...)})}
     * #{expression(arguments..., #{expression})}
     * </code>
     * <br>
     * Example:
     * <br>
     * <code>
     * #{shiftDate("1942-12-02T01:23:40+04:00", "yyyy-MM-dd'T'HH:mm:ssz", "P43Y4M3W3D")}
     * <br>
     * #{encodeToBase64(#{fromEpochSecond(-523641111)})}
     * </code>
     *
     * @param stringWithExpressions the string with expressions to evaluate
     * @return the resulting string with expression placeholders replaced with expressions evaluation results
     */
    public Object resolveExpressions(boolean dryRun, String stringWithExpressions) {
        if (dryRun) {
            return stringWithExpressions;
        }
        try {
            return resolveExpressions(stringWithExpressions, PATTERNS.iterator());
        } catch (RuntimeException e) {
            expressionResolverMonitor.onExpressionProcessingError(stringWithExpressions, e);
            throw e;
        }
    }

    private Object resolveExpressions(String value, Iterator<Pattern> expressionPatterns) {
        String processedValue = value;
        Matcher expressionMatcher = expressionPatterns.next().matcher(processedValue);
        boolean expressionFound = false;
        while (expressionMatcher.find()) {
            expressionFound = true;
            String expression = expressionMatcher.group(1);
            Object expressionResult = apply(expression);
            if (!(expressionResult instanceof String) && ("#{" + expression + "}").equals(processedValue)) {
                return expressionResult;
            }
            if (!expressionResult.equals(expression)) {
                String regex = String.format(REPLACEMENT_PATTERN, Pattern.quote(expression));
                processedValue = processedValue.replaceFirst(regex,
                        Matcher.quoteReplacement(String.valueOf(expressionResult)));
                expressionFound = false;
                expressionMatcher.reset(processedValue);
            }
        }
        if (expressionFound && expressionPatterns.hasNext()) {
            return resolveExpressions(processedValue, expressionPatterns);
        }
        return processedValue;
    }

    private Object apply(String expression) {
        for (ExpressionProcessor<?> processor : expressionProcessors) {
            Optional<?> optional = processor.execute(expression);
            if (optional.isPresent()) {
                return optional.get();
            }
        }
        return expression;
    }
}