DocxStamperConfiguration.java
package pro.verron.officestamper.core;
import org.springframework.expression.EvaluationContext;
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.CommentProcessorFactory;
import pro.verron.officestamper.preset.EvaluationContextConfigurers;
import pro.verron.officestamper.preset.ExceptionResolvers;
import pro.verron.officestamper.preset.Resolvers;
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 {@link DocxStamperConfiguration} class represents the configuration for
* the {@link 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 =
new HashMap<>();
private final List<ObjectResolver> resolvers = new ArrayList<>();
private final Map<Class<?>, Object> expressionFunctions = new HashMap<>();
private final List<PreProcessor> preprocessors = new ArrayList<>();
private final List<CustomFunction> functions = new ArrayList<>();
private String lineBreakPlaceholder = "\n";
private EvaluationContextConfigurer evaluationContextConfigurer = EvaluationContextConfigurers.defaultConfigurer();
private boolean failOnUnresolvedExpression = true;
private boolean leaveEmptyOnExpressionError = false;
private boolean replaceUnresolvedExpressions = false;
private String unresolvedExpressionsDefaultValue = null;
private SpelParserConfiguration spelParserConfiguration = new SpelParserConfiguration();
private ExceptionResolver exceptionResolver = computeExceptionResolver();
/**
* Creates a new configuration with default values.
*/
public DocxStamperConfiguration() {
CommentProcessorFactory pf = new CommentProcessorFactory(this);
commentProcessors.put(CommentProcessorFactory.IRepeatProcessor.class, pf::repeat);
commentProcessors.put(CommentProcessorFactory.IParagraphRepeatProcessor.class, pf::repeatParagraph);
commentProcessors.put(CommentProcessorFactory.IRepeatDocPartProcessor.class, pf::repeatDocPart);
commentProcessors.put(CommentProcessorFactory.ITableResolver.class, pf::tableResolver);
commentProcessors.put(CommentProcessorFactory.IDisplayIfProcessor.class, pf::displayIf);
commentProcessors.put(CommentProcessorFactory.IReplaceWithProcessor.class, pf::replaceWith);
resolvers.addAll(List.of(Resolvers.image(),
Resolvers.legacyDate(),
Resolvers.isoDate(),
Resolvers.isoTime(),
Resolvers.isoDateTime(),
Resolvers.nullToEmpty(),
Resolvers.fallback()));
}
/**
* Resets all the comment processors in the configuration. This method clears the
* map of comment processors, effectively removing all registered comment processors.
* Comment processors are used to process comments within the document.
*/
public void resetCommentProcessors() {
this.commentProcessors.clear();
}
/**
* Resets all the resolvers in the DocxStamperConfiguration object.
* This method clears the list of resolvers, effectively removing all registered resolvers.
* Resolvers are used to resolve objects during the stamping process.
*/
public void resetResolvers() {
this.resolvers.clear();
}
/**
* <p>isFailOnUnresolvedExpression.</p>
*
* @return a boolean
*/
@Deprecated(since = "2.5", forRemoval = true) @Override public boolean isFailOnUnresolvedExpression() {
return failOnUnresolvedExpression;
}
/**
* If set to true, stamper will throw an {@link OfficeStamperException}
* if a variable expression or processor expression within the document or within the comments is encountered that
* cannot be resolved. Is set to true by default.
*
* @param failOnUnresolvedExpression a boolean
*
* @return a {@link DocxStamperConfiguration} object
*/
@Deprecated(since = "2.5", forRemoval = true) @Override
public DocxStamperConfiguration setFailOnUnresolvedExpression(boolean failOnUnresolvedExpression) {
this.failOnUnresolvedExpression = failOnUnresolvedExpression;
this.exceptionResolver = computeExceptionResolver();
return this;
}
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();
}
/**
* <p>isLeaveEmptyOnExpressionError.</p>
*
* @return a boolean
*/
@Override public boolean isLeaveEmptyOnExpressionError() {
return leaveEmptyOnExpressionError;
}
/**
* <p>isReplaceUnresolvedExpressions.</p>
*
* @return a boolean
*/
@Override public boolean isReplaceUnresolvedExpressions() {
return replaceUnresolvedExpressions;
}
/**
* <p>Getter for the field <code>unresolvedExpressionsDefaultValue</code>.</p>
*
* @return a {@link String} object
*/
@Override public String getUnresolvedExpressionsDefaultValue() {
return unresolvedExpressionsDefaultValue;
}
/**
* Indicates the default value to use for expressions that doesn't resolve.
*
* @param unresolvedExpressionsDefaultValue value to use instead for expression that doesn't resolve
*
* @return a {@link 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 null value expression with resolved value (which is null),
* false to leave the expression as is
*
* @return a {@link DocxStamperConfiguration} object
*/
@Deprecated(since = "2.5", forRemoval = true) @Override
public DocxStamperConfiguration replaceUnresolvedExpressions(boolean replaceUnresolvedExpressions) {
this.replaceUnresolvedExpressions = replaceUnresolvedExpressions;
this.exceptionResolver = computeExceptionResolver();
return this;
}
/**
* If an error is caught while evaluating an expression, the expression will be replaced with an empty string
* instead
* of leaving the original expression in the document.
*
* @param leaveEmpty true to replace expressions with empty string when an error is caught while evaluating
*
* @return a {@link 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 whose methods should be exposed in the expression language.
* @param implementation the implementation that should be called to evaluate invocations of the interface methods
* within the expression language. Must implement the interface above.
*
* @return a {@link 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 which is implemented by the commentProcessor.
* @param commentProcessorFactory the commentProcessor factory generating the specified interface.
*
* @return a {@link 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);
}
/**
* <p>Getter for the field <code>lineBreakPlaceholder</code>.</p>
*
* @return a {@link String} object
*/
@Override public String getLineBreakPlaceholder() {
return lineBreakPlaceholder;
}
/**
* The String provided as lineBreakPlaceholder will be replaced with a line break
* when stamping a document. If no lineBreakPlaceholder is provided, no replacement
* will take place.
*
* @param lineBreakPlaceholder the String that should be replaced with line breaks during stamping.
*
* @return the configuration object for chaining.
*/
@Override public DocxStamperConfiguration setLineBreakPlaceholder(@NonNull String lineBreakPlaceholder) {
this.lineBreakPlaceholder = lineBreakPlaceholder;
return this;
}
/**
* <p>Getter for the field <code>evaluationContextConfigurer</code>.</p>
*
* @return a {@link EvaluationContextConfigurer} object
*/
@Override public EvaluationContextConfigurer getEvaluationContextConfigurer() {
return evaluationContextConfigurer;
}
/**
* Provides an {@link EvaluationContextConfigurer} which may change the configuration of a Spring
* {@link EvaluationContext} which is used for evaluating expressions
* in comments and text.
*
* @param evaluationContextConfigurer the configurer to use.
*
* @return a {@link DocxStamperConfiguration} object
*/
@Override public DocxStamperConfiguration setEvaluationContextConfigurer(
EvaluationContextConfigurer evaluationContextConfigurer
) {
this.evaluationContextConfigurer = evaluationContextConfigurer;
return this;
}
/**
* <p>Getter for the field <code>spelParserConfiguration</code>.</p>
*
* @return a {@link SpelParserConfiguration} object
*/
@Override public SpelParserConfiguration getSpelParserConfiguration() {
return spelParserConfiguration;
}
/**
* Sets the {@link SpelParserConfiguration} to use for expression parsing.
* <p>
* Note that this configuration will be used for all expressions in the document, including expressions in comments!
* </p>
*
* @param spelParserConfiguration the configuration to use.
*
* @return a {@link DocxStamperConfiguration} object
*/
@Override public DocxStamperConfiguration setSpelParserConfiguration(
SpelParserConfiguration spelParserConfiguration
) {
this.spelParserConfiguration = spelParserConfiguration;
return this;
}
/**
* <p>Getter for the field <code>expressionFunctions</code>.</p>
*
* @return a {@link Map} object
*/
@Override public Map<Class<?>, Object> getExpressionFunctions() {
return expressionFunctions;
}
/**
* <p>Getter for the field <code>commentProcessors</code>.</p>
*
* @return a {@link Map} object
*/
@Override public Map<Class<?>, Function<ParagraphPlaceholderReplacer, CommentProcessor>> getCommentProcessors() {
return commentProcessors;
}
/**
* <p>Getter for the field <code>preprocessors</code>.</p>
*
* @return a {@link List} object
*/
@Override public List<PreProcessor> getPreprocessors() {
return preprocessors;
}
/**
* Retrieves the list of resolvers.
*
* @return The list of object resolvers.
*/
@Override public List<ObjectResolver> getResolvers() {
return resolvers;
}
/**
* Sets the resolvers for resolving objects in the DocxStamperConfiguration.
* <p>
* This method is the evolution of the method {@code 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 updated DocxStamperConfiguration instance.
*/
@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;
}
public void addCustomFunction(CustomFunction function) {
this.functions.add(function);
}
@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()));
}
@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);
}
}