Engine.java

package pro.verron.officestamper.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelParseException;
import pro.verron.officestamper.api.ExceptionResolver;
import pro.verron.officestamper.api.Insert;
import pro.verron.officestamper.api.OfficeStamperException;
import pro.verron.officestamper.api.ProcessorContext;

/// The core engine of OfficeStamper, responsible for processing expressions.
public class Engine {
    private static final Logger log = LoggerFactory.getLogger(Engine.class);

    private final ExpressionParser expressionParser;
    private final ExceptionResolver exceptionResolver;
    private final ObjectResolverRegistry objectResolverRegistry;
    private final ProcessorContext processorContext;

    /// Constructs an Engine.
    ///
    /// @param expressionParser the expression parser.
    /// @param exceptionResolver the exception resolver.
    /// @param objectResolverRegistry the object resolver registry.
    /// @param processorContext the processor context.
    public Engine(
            ExpressionParser expressionParser,
            ExceptionResolver exceptionResolver,
            ObjectResolverRegistry objectResolverRegistry,
            ProcessorContext processorContext
    ) {
        this.expressionParser = expressionParser;
        this.exceptionResolver = exceptionResolver;
        this.objectResolverRegistry = objectResolverRegistry;
        this.processorContext = processorContext;
    }

    /// Processes the provided evaluation context against the expression defined in the processor context.
    ///
    /// The method attempts to resolve an expression using the given evaluation context.
    ///
    /// If successful, the process completes and logs a debug message.
    ///
    /// Otherwise, on failure ([SpelEvaluationException] or [SpelParseException]), it handles the exception by invoking
    /// the exceptionResolver and logs an error.
    ///
    /// @param evaluationContext the evaluation context for processing the expression.
    ///
    /// @return true if the processing was successful, otherwise false
    public boolean process(EvaluationContext evaluationContext) {
        var expression = processorContext.expression();
        try {
            var parsedExpression = expressionParser.parseExpression(expression);
            parsedExpression.getValue(evaluationContext);
            log.debug("Processed '{}' successfully.", expression);
            return true;
        } catch (SpelEvaluationException | SpelParseException e) {
            var message = "Processing '%s' failed.".formatted(expression);
            exceptionResolver.resolve(expression, message, e);
            return false;
        }
    }

    /// Resolves an [Insert] object by processing the provided evaluation context using the current processor context.
    /// Combines the processor context's part and expression with various resolvers to achieve the resolution.
    ///
    /// @param evaluationContext the evaluation context for processing the expression.
    ///
    /// @return an [Insert] object representing the resolved result of the expression within the context.
    public Insert resolve(EvaluationContext evaluationContext) {
        var part = processorContext.part();
        var expression = processorContext.expression();
        try {

            var parsedExpression = expressionParser.parseExpression(expression);
            var resolution = parsedExpression.getValue(evaluationContext);
            return objectResolverRegistry.resolve(part, expression, resolution);
        } catch (SpelEvaluationException | SpelParseException | OfficeStamperException e) {
            var msgTemplate = "Expression %s could not be resolved against context '%s'";
            var message = msgTemplate.formatted(expression, evaluationContext);
            return exceptionResolver.resolve(expression, message, e);
        }
    }
}