DocxStamperConfiguration.java

package pro.verron.officestamper.core;


import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.lang.NonNull;
import pro.verron.officestamper.api.*;
import pro.verron.officestamper.api.CustomFunction.NeedsBiFunctionImpl;
import pro.verron.officestamper.api.CustomFunction.NeedsFunctionImpl;
import pro.verron.officestamper.core.functions.BiFunctionBuilder;
import pro.verron.officestamper.core.functions.FunctionBuilder;
import pro.verron.officestamper.core.functions.TriFunctionBuilder;
import pro.verron.officestamper.preset.EvaluationContextConfigurers;
import pro.verron.officestamper.preset.ExceptionResolvers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

/// The [DocxStamperConfiguration] class represents the configuration for the [DocxStamper] class.
/// It provides methods to customize the behavior of the stamper.
///
/// @author Joseph Verron
/// @author Tom Hombergs
/// @version ${version}
/// @since 1.0.3
public class DocxStamperConfiguration
        implements OfficeStamperConfiguration {
    private final Map<Class<?>, Function<ParagraphPlaceholderReplacer, CommentProcessor>> commentProcessors;
    private final List<ObjectResolver> resolvers;
    private final Map<Class<?>, Object> expressionFunctions;
    private final List<PreProcessor> preprocessors;
    private final List<PostProcessor> postprocessors;
    private final List<CustomFunction> functions;
    private String lineBreakPlaceholder;
    private EvaluationContextConfigurer evaluationContextConfigurer;
    private boolean failOnUnresolvedExpression;
    private boolean leaveEmptyOnExpressionError;
    private boolean replaceUnresolvedExpressions;
    private String unresolvedExpressionsDefaultValue;
    private SpelParserConfiguration spelParserConfiguration;
    private ExceptionResolver exceptionResolver;

    /**
     * Constructs a new instance of the {@code DocxStamperConfiguration} class
     * and initializes its default configuration settings.
     *
     * This constructor sets up internal structures and default behaviors
     * for managing document stamping configurations, including:
     * - Initializing collections for processors, resolvers, and functions.
     * - Setting default values for expression handling and evaluation.
     * - Creating and configuring a default {@code SpelParserConfiguration}.
     * - Establishing resolvers and exception handling strategies.
     */
    public DocxStamperConfiguration() {
        commentProcessors = new HashMap<>();
        resolvers = new ArrayList<>();
        expressionFunctions = new HashMap<>();
        preprocessors = new ArrayList<>();
        postprocessors = new ArrayList<>();
        functions = new ArrayList<>();
        evaluationContextConfigurer = EvaluationContextConfigurers.defaultConfigurer();
        lineBreakPlaceholder = "\n";
        failOnUnresolvedExpression = true;
        leaveEmptyOnExpressionError = false;
        replaceUnresolvedExpressions = false;
        unresolvedExpressionsDefaultValue = null;
        spelParserConfiguration = new SpelParserConfiguration();
        exceptionResolver = computeExceptionResolver();
    }

    private ExceptionResolver computeExceptionResolver() {
        if (failOnUnresolvedExpression) return ExceptionResolvers.throwing();
        if (replaceWithDefaultOnError()) return ExceptionResolvers.defaulting(replacementDefault());
        return ExceptionResolvers.passing();
    }

    private boolean replaceWithDefaultOnError() {
        return isLeaveEmptyOnExpressionError() || isReplaceUnresolvedExpressions();
    }

    private String replacementDefault() {
        return isLeaveEmptyOnExpressionError() ? "" : getUnresolvedExpressionsDefaultValue();
    }

    /// Resets all processors in the configuration.
    public void resetCommentProcessors() {
        this.commentProcessors.clear();
    }

    /// Resets all resolvers in the configuration.
    public void resetResolvers() {
        this.resolvers.clear();
    }

    @Deprecated(since = "2.5", forRemoval = true)
    @Override
    public boolean isFailOnUnresolvedExpression() {
        return failOnUnresolvedExpression;
    }

    /// If true, stamper throws an [OfficeStamperException] if an expression within the document can’t be resolved.
    /// Set to `TRUE` by default.
    ///
    /// @param failOnUnresolvedExpression a boolean
    ///
    /// @return the same [DocxStamperConfiguration] object
    @Deprecated(since = "2.5", forRemoval = true)
    @Override
    public DocxStamperConfiguration setFailOnUnresolvedExpression(boolean failOnUnresolvedExpression) {
        this.failOnUnresolvedExpression = failOnUnresolvedExpression;
        this.exceptionResolver = computeExceptionResolver();
        return this;
    }

    @Override
    public boolean isLeaveEmptyOnExpressionError() {
        return leaveEmptyOnExpressionError;
    }

    @Override
    public boolean isReplaceUnresolvedExpressions() {
        return replaceUnresolvedExpressions;
    }

    @Override
    public String getUnresolvedExpressionsDefaultValue() {
        return unresolvedExpressionsDefaultValue;
    }

    /// Default value to use for expressions that doesn't resolve.
    ///
    /// @param unresolvedExpressionsDefaultValue value to use instead for expression that doesn't resolve
    ///
    /// @return a [DocxStamperConfiguration] object
    ///
    /// @see DocxStamperConfiguration#replaceUnresolvedExpressions
    @Deprecated(since = "2.5", forRemoval = true)
    @Override
    public DocxStamperConfiguration unresolvedExpressionsDefaultValue(String unresolvedExpressionsDefaultValue) {
        this.unresolvedExpressionsDefaultValue = unresolvedExpressionsDefaultValue;
        this.exceptionResolver = computeExceptionResolver();
        return this;
    }

    /// Indicates if a default value should replace expressions that don't resolve.
    ///
    /// @param replaceUnresolvedExpressions true to replace expression with resolved value `null`
    ///
    ///
    ///                                                                         false to leave the expression as is.
    ///
    /// @return a [DocxStamperConfiguration] object
    @Deprecated(since = "2.5", forRemoval = true)
    @Override
    public DocxStamperConfiguration replaceUnresolvedExpressions(boolean replaceUnresolvedExpressions) {
        this.replaceUnresolvedExpressions = replaceUnresolvedExpressions;
        this.exceptionResolver = computeExceptionResolver();
        return this;
    }

    /// Indicate if expressions failing during evaluation needs removal.
    ///
    /// @param leaveEmpty true to replace expressions with empty string when an error occurs during evaluation.
    ///
    /// @return a [DocxStamperConfiguration] object
    @Deprecated(since = "2.5", forRemoval = true)
    @Override
    public DocxStamperConfiguration leaveEmptyOnExpressionError(
            boolean leaveEmpty
    ) {
        this.leaveEmptyOnExpressionError = leaveEmpty;
        this.exceptionResolver = computeExceptionResolver();
        return this;
    }

    /// Exposes all methods of a given interface to the expression language.
    ///
    /// @param interfaceClass the interface holding methods to expose in the expression language.
    /// @param implementation the implementation to call to evaluate invocations of those methods.
    ///
    ///                       Must implement the
    ///                                             mentioned interface.
    ///
    /// @return a [DocxStamperConfiguration] object
    @Override
    public DocxStamperConfiguration exposeInterfaceToExpressionLanguage(
            Class<?> interfaceClass,
            Object implementation
    ) {
        this.expressionFunctions.put(interfaceClass, implementation);
        return this;
    }

    /// Registers the specified ICommentProcessor as an implementation of the specified interface.
    ///
    /// @param interfaceClass          the interface, implemented by the commentProcessor.
    /// @param commentProcessorFactory the commentProcessor factory generating instances of the specified interface.
    ///
    /// @return a [DocxStamperConfiguration] object
    @Override
    public DocxStamperConfiguration addCommentProcessor(
            Class<?> interfaceClass,
            Function<ParagraphPlaceholderReplacer, CommentProcessor> commentProcessorFactory
    ) {
        this.commentProcessors.put(interfaceClass, commentProcessorFactory);
        return this;
    }

    /// Adds a preprocessor to the configuration.
    ///
    /// @param preprocessor the preprocessor to add.
    @Override
    public void addPreprocessor(PreProcessor preprocessor) {
        preprocessors.add(preprocessor);
    }


    @Override
    public String getLineBreakPlaceholder() {
        return lineBreakPlaceholder;
    }

    /// String to replace with a line break when stamping a document.
    /// By default, `\\n` is the placeholder.
    ///
    /// @param lineBreakPlaceholder string to replace with line breaks during stamping.
    ///
    /// @return the configuration object for chaining.
    @Override
    public DocxStamperConfiguration setLineBreakPlaceholder(@NonNull String lineBreakPlaceholder) {
        this.lineBreakPlaceholder = lineBreakPlaceholder;
        return this;
    }

    @Override
    public EvaluationContextConfigurer getEvaluationContextConfigurer() {
        return evaluationContextConfigurer;
    }

    /// Provides an [EvaluationContextConfigurer] which may change the configuration of a Spring
    /// [EvaluationContext] used for evaluating expressions in comments and text.
    ///
    /// @param evaluationContextConfigurer the configurer to use.
    ///
    /// @return the configuration object for chaining.
    @Override
    public DocxStamperConfiguration setEvaluationContextConfigurer(
            EvaluationContextConfigurer evaluationContextConfigurer
    ) {
        this.evaluationContextConfigurer = evaluationContextConfigurer;
        return this;
    }

    @Override
    public SpelParserConfiguration getSpelParserConfiguration() {
        return spelParserConfiguration;
    }

    /// Sets the [SpelParserConfiguration] used for expression parsing.
    /// Note that this configuration is the same for all expressions in the document, including expressions in comments.
    ///
    /// @param spelParserConfiguration the configuration to use.
    ///
    /// @return the configuration object for chaining.
    @Override
    public DocxStamperConfiguration setSpelParserConfiguration(
            SpelParserConfiguration spelParserConfiguration
    ) {
        this.spelParserConfiguration = spelParserConfiguration;
        return this;
    }

    @Override
    public Map<Class<?>, Object> getExpressionFunctions() {
        return expressionFunctions;
    }

    @Override
    public Map<Class<?>, Function<ParagraphPlaceholderReplacer, CommentProcessor>> getCommentProcessors() {
        return commentProcessors;
    }

    @Override
    public List<PreProcessor> getPreprocessors() {
        return preprocessors;
    }

    @Override
    public List<ObjectResolver> getResolvers() {
        return resolvers;
    }

    /// Sets resolvers for resolving objects in the DocxStamperConfiguration.
    ///
    /// This method is the evolution of the method `addTypeResolver`,
    /// and the order in which the resolvers are ordered is determinant - the first resolvers
    /// in the list will be tried first. If a fallback resolver is desired, it should be placed last in the list.
    ///
    /// @param resolvers The list of ObjectResolvers to be set.
    ///
    /// @return the configuration object for chaining.
    @Override
    public DocxStamperConfiguration setResolvers(List<ObjectResolver> resolvers) {
        this.resolvers.clear();
        this.resolvers.addAll(resolvers);
        return this;
    }

    /// Adds a resolver to the list of resolvers in the `DocxStamperConfiguration` object.
    /// Resolvers are used to resolve objects during the stamping process.
    ///
    /// @param resolver The resolver to be added. This resolver should implement the `ObjectResolver` interface.
    ///
    /// @return The modified `DocxStamperConfiguration` object, with the resolver added to the beginning of the
    /// resolver list.
    @Override
    public DocxStamperConfiguration addResolver(ObjectResolver resolver) {
        resolvers.addFirst(resolver);
        return this;
    }

    @Override
    public ExceptionResolver getExceptionResolver() {
        return exceptionResolver;
    }

    @Override
    public DocxStamperConfiguration setExceptionResolver(ExceptionResolver exceptionResolver) {
        this.exceptionResolver = exceptionResolver;
        return this;
    }

    @Override
    public List<CustomFunction> customFunctions() {
        return functions;
    }

    @Override
    public void addCustomFunction(String name, Supplier<?> implementation) {
        this.addCustomFunction(new CustomFunction(name, List.of(), args -> implementation.get()));
    }

    public void addCustomFunction(CustomFunction function) {
        this.functions.add(function);
    }

    @Override
    public <T> NeedsFunctionImpl<T> addCustomFunction(String name, Class<T> class0) {
        return new FunctionBuilder<>(this, name, class0);
    }

    @Override
    public <T, U> NeedsBiFunctionImpl<T, U> addCustomFunction(String name, Class<T> class0, Class<U> class1) {
        return new BiFunctionBuilder<>(this, name, class0, class1);
    }

    @Override
    public <T, U, V> CustomFunction.NeedsTriFunctionImpl<T, U, V> addCustomFunction(
            String name,
            Class<T> class0,
            Class<U> class1,
            Class<V> class2
    ) {
        return new TriFunctionBuilder<>(this, name, class0, class1, class2);
    }

    @Override
    public List<PostProcessor> getPostprocessors() {
        return postprocessors;
    }

    @Override
    public void addPostprocessor(PostProcessor postprocessor) {
        postprocessors.add(postprocessor);
    }
}