Engine.java

1
package pro.verron.officestamper.core;
2
3
import org.slf4j.Logger;
4
import org.slf4j.LoggerFactory;
5
import org.springframework.expression.EvaluationContext;
6
import org.springframework.expression.TypedValue;
7
import org.springframework.expression.spel.*;
8
import org.springframework.expression.spel.standard.SpelExpressionParser;
9
import pro.verron.officestamper.api.DocxPart;
10
import pro.verron.officestamper.api.ExceptionResolver;
11
import pro.verron.officestamper.api.Insert;
12
import pro.verron.officestamper.api.ProcessorContext;
13
14
import java.util.Objects;
15
16
/// The core engine of OfficeStamper, responsible for processing expressions.
17
public class Engine {
18
    private static final Logger log = LoggerFactory.getLogger(Engine.class);
19
20
    private final SpelParserConfiguration parserConfiguration;
21
    private final ExceptionResolver exceptionResolver;
22
    private final ObjectResolverRegistry objectResolverRegistry;
23
    private final String expression;
24
    private final DocxPart docxPart;
25
    private final SpelExpressionParser expressionParser;
26
27
    /// Constructs an Engine.
28
    ///
29
    /// @param parserConfiguration the parser configuration.
30
    /// @param exceptionResolver the exception resolver.
31
    /// @param objectResolverRegistry the object resolver registry.
32
    /// @param processorContext the processor context.
33
    public Engine(
34
            SpelParserConfiguration parserConfiguration,
35
            ExceptionResolver exceptionResolver,
36
            ObjectResolverRegistry objectResolverRegistry,
37
            ProcessorContext processorContext
38
    ) {
39
        this.parserConfiguration = parserConfiguration;
40
        this.expressionParser = new SpelExpressionParser(parserConfiguration);
41
        this.exceptionResolver = exceptionResolver;
42
        this.objectResolverRegistry = objectResolverRegistry;
43
        this.expression = processorContext.expression();
44
        this.docxPart = processorContext.part();
45
    }
46
47
    /// Processes the provided evaluation context against the expression defined in the processor context.
48
    ///
49
    /// The method attempts to resolve an expression using the given evaluation context.
50
    ///
51
    /// If successful, the process completes and logs a debug message.
52
    ///
53
    /// Otherwise, on failure ([SpelEvaluationException] or [SpelParseException]), it handles the exception by invoking
54
    /// the exceptionResolver and logs an error.
55
    ///
56
    /// @param evaluationContext the evaluation context for processing the expression.
57
    ///
58
    /// @return true if the processing was successful, otherwise false
59
    public boolean process(EvaluationContext evaluationContext) {
60
        SpelNode spelNode;
61
        try {
62
            spelNode = parseAST(expressionParser, expression);
63
        } catch (SpelParseException e) {
64
            var msgTemplate = "Expression %s could not be parsed successfully.";
65
            var message = msgTemplate.formatted(expression, evaluationContext);
66
            exceptionResolver.resolve(expression, message, e);
67 1 1. process : replaced boolean return with true for pro/verron/officestamper/core/Engine::process → SURVIVED
            return false;
68
        }
69
70
        var expressionState = buildExpressionState(evaluationContext);
71
        try {
72
            spelNode.getValue(expressionState);
73
            log.debug("Processed '{}' successfully.", expression);
74
        } catch (SpelEvaluationException e) {
75
            var msgTemplate = "Expression %s could not be processed against context '%s'";
76
            var message = msgTemplate.formatted(expression, evaluationContext);
77
            exceptionResolver.resolve(expression, message, e);
78 1 1. process : replaced boolean return with true for pro/verron/officestamper/core/Engine::process → SURVIVED
            return false;
79
        }
80
81 1 1. process : replaced boolean return with false for pro/verron/officestamper/core/Engine::process → KILLED
        return true;
82
    }
83
84
    private static SpelNode parseAST(SpelExpressionParser parser, String expression) {
85
        var parsedExpression = parser.parseRaw(expression);
86
        log.trace("Parsed '{}' successfully.", expression);
87 1 1. parseAST : replaced return value with null for pro/verron/officestamper/core/Engine::parseAST → KILLED
        return parsedExpression.getAST();
88
    }
89
90
    private ExpressionState buildExpressionState(EvaluationContext evaluationContext) {
91
        var contextBranchTypedValue = evaluationContext.getRootObject();
92
        var contextBranch = (ContextBranch) Objects.requireNonNull(contextBranchTypedValue.getValue());
93
        var rootObject = contextBranch.root();
94
        var rootObjectTypedValue = new TypedValue(rootObject);
95
        var expressionState = new ExpressionState(evaluationContext, rootObjectTypedValue, parserConfiguration);
96
        for (Object o : contextBranch) {
97 1 1. buildExpressionState : removed call to org/springframework/expression/spel/ExpressionState::pushActiveContextObject → KILLED
            expressionState.pushActiveContextObject(new TypedValue(o));
98 1 1. buildExpressionState : removed call to org/springframework/expression/spel/ExpressionState::enterScope → KILLED
            expressionState.enterScope();
99
        }
100 1 1. buildExpressionState : replaced return value with null for pro/verron/officestamper/core/Engine::buildExpressionState → KILLED
        return expressionState;
101
    }
102
103
    /// Resolves an [Insert] object by processing the provided evaluation context using the current processor context.
104
    /// Combines the processor context's part and expression with various resolvers to achieve the resolution.
105
    ///
106
    /// @param evaluationContext the evaluation context for processing the expression.
107
    ///
108
    /// @return an [Insert] object representing the resolved result of the expression within the context.
109
    public Insert resolve(EvaluationContext evaluationContext) {
110
        SpelNode spelNode;
111
        try {
112
            spelNode = parseAST(expressionParser, expression);
113
        } catch (SpelParseException e) {
114
            var msgTemplate = "Expression %s could not be parsed successfully.";
115
            var message = msgTemplate.formatted(expression, evaluationContext);
116 1 1. resolve : replaced return value with null for pro/verron/officestamper/core/Engine::resolve → NO_COVERAGE
            return exceptionResolver.resolve(expression, message, e);
117
        }
118
119
        var expressionState = buildExpressionState(evaluationContext);
120
        Object javaResolution;
121
        try {
122
            javaResolution = spelNode.getValue(expressionState);
123
            log.debug("Resolved '{}' successfully.", expression);
124
        } catch (SpelEvaluationException e) {
125
            var msgTemplate = "Expression %s could not be resolved against context '%s'";
126
            var message = msgTemplate.formatted(expression, evaluationContext);
127 1 1. resolve : replaced return value with null for pro/verron/officestamper/core/Engine::resolve → KILLED
            return exceptionResolver.resolve(expression, message, e);
128
        }
129
130
        try {
131
            var docxResolution = objectResolverRegistry.resolve(docxPart, expression, javaResolution);
132
            log.debug("Converted '{}' to docx ({}) successfully.", expression, docxResolution);
133 1 1. resolve : replaced return value with null for pro/verron/officestamper/core/Engine::resolve → KILLED
            return docxResolution;
134
        } catch (SpelEvaluationException e) {
135
            var msgTemplate = "Expression %s could not be converted to docx inserts.";
136
            var message = msgTemplate.formatted(expression, evaluationContext);
137 1 1. resolve : replaced return value with null for pro/verron/officestamper/core/Engine::resolve → NO_COVERAGE
            return exceptionResolver.resolve(expression, message, e);
138
        }
139
    }
140
}

Mutations

67

1.1
Location : process
Killed by : none
replaced boolean return with true for pro/verron/officestamper/core/Engine::process → SURVIVED
Covering tests

78

1.1
Location : process
Killed by : none
replaced boolean return with true for pro/verron/officestamper/core/Engine::process → SURVIVED
Covering tests

81

1.1
Location : process
Killed by : pro.verron.officestamper.test.ProcessorRepeatDocPart_BadPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ProcessorRepeatDocPart_BadPlaceholderTest]/[test-template:testBadExpressionShouldNotBlockCallerThread(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced boolean return with false for pro/verron/officestamper/core/Engine::process → KILLED

87

1.1
Location : parseAST
Killed by : pro.verron.officestamper.test.SpelInjectionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.SpelInjectionTest]/[test-template:spelInjectionTest(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced return value with null for pro/verron/officestamper/core/Engine::parseAST → KILLED

97

1.1
Location : buildExpressionState
Killed by : pro.verron.officestamper.test.ProcessorRepeatDocPart_BadPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ProcessorRepeatDocPart_BadPlaceholderTest]/[test-template:testBadExpressionShouldNotBlockCallerThread(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
removed call to org/springframework/expression/spel/ExpressionState::pushActiveContextObject → KILLED

98

1.1
Location : buildExpressionState
Killed by : pro.verron.officestamper.test.RegressionTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.RegressionTests]/[test-template:test52(pro.verron.officestamper.test.RegressionTests$Conditions, java.lang.String)]/[test-template-invocation:#2]
removed call to org/springframework/expression/spel/ExpressionState::enterScope → KILLED

100

1.1
Location : buildExpressionState
Killed by : pro.verron.officestamper.test.SpelInjectionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.SpelInjectionTest]/[test-template:spelInjectionTest(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced return value with null for pro/verron/officestamper/core/Engine::buildExpressionState → KILLED

116

1.1
Location : resolve
Killed by : none
replaced return value with null for pro/verron/officestamper/core/Engine::resolve → NO_COVERAGE

127

1.1
Location : resolve
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, org.docx4j.openpackaging.packages.WordprocessingMLPackage, java.lang.String)]/[test-template-invocation:#13]
replaced return value with null for pro/verron/officestamper/core/Engine::resolve → KILLED

133

1.1
Location : resolve
Killed by : pro.verron.officestamper.test.NullPointerResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.NullPointerResolutionTest]/[test-template:nullPointerResolutionTest_testThrowingCase(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#1]
replaced return value with null for pro/verron/officestamper/core/Engine::resolve → KILLED

137

1.1
Location : resolve
Killed by : none
replaced return value with null for pro/verron/officestamper/core/Engine::resolve → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.1