CommentProcessorRegistry.java

1
package pro.verron.officestamper.core;
2
3
import org.docx4j.XmlUtils;
4
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
5
import org.docx4j.wml.Comments;
6
import org.docx4j.wml.P;
7
import org.docx4j.wml.R;
8
import org.slf4j.Logger;
9
import org.slf4j.LoggerFactory;
10
import org.springframework.expression.spel.SpelEvaluationException;
11
import org.springframework.expression.spel.SpelParseException;
12
import org.springframework.lang.Nullable;
13
import pro.verron.officestamper.api.Comment;
14
import pro.verron.officestamper.api.CommentProcessor;
15
import pro.verron.officestamper.api.DocxPart;
16
import pro.verron.officestamper.api.ExceptionResolver;
17
18
import java.math.BigInteger;
19
import java.util.*;
20
21
import static pro.verron.officestamper.core.CommentCollectorWalker.collectComments;
22
import static pro.verron.officestamper.core.CommentUtil.getCommentString;
23
24
/**
25
 * Allows registration of {@link CommentProcessor} objects. Each registered
26
 * ICommentProcessor must implement an interface which has to be specified at
27
 * registration time. Provides several getter methods to access the registered
28
 * {@link CommentProcessor}.
29
 *
30
 * @author Joseph Verron
31
 * @author Tom Hombergs
32
 * @version ${version}
33
 * @since 1.0.0
34
 */
35
public class CommentProcessorRegistry {
36
37
    private static final Logger logger = LoggerFactory.getLogger(CommentProcessorRegistry.class);
38
    private final DocxPart source;
39
    private final Map<Class<?>, ?> commentProcessors;
40
    private final ExpressionResolver expressionResolver;
41
    private final ExceptionResolver exceptionResolver;
42
43
    /**
44
     * Constructs a new CommentProcessorRegistry.
45
     *
46
     * @param source the source part of the Word document.
47
     * @param expressionResolver the resolver for evaluating expressions.
48
     * @param commentProcessors map of comment processor instances keyed by their respective class types.
49
     * @param exceptionResolver the resolver for handling exceptions during processing.
50
     */
51
    public CommentProcessorRegistry(
52
            DocxPart source,
53
            ExpressionResolver expressionResolver,
54
            Map<Class<?>, ?> commentProcessors,
55
            ExceptionResolver exceptionResolver
56
    ) {
57
        this.source = source;
58
        this.expressionResolver = expressionResolver;
59
        this.commentProcessors = commentProcessors;
60
        this.exceptionResolver = exceptionResolver;
61
    }
62
63
    public <T> void runProcessors(T expressionContext) {
64
        var proceedComments = new ArrayList<Comment>();
65
66
        source.streamParagraphs()
67
              .map(P::getContent)
68
              .flatMap(Collection::stream)
69
              .map(XmlUtils::unwrap)
70
              .filter(R.class::isInstance)
71
              .map(R.class::cast)
72 1 1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED
              .forEach(run -> {
73
                  var comments = collectComments(source);
74
                  var runParent = (P) run.getParent();
75
                  var optional = runProcessorsOnRunComment(comments, expressionContext, run, runParent);
76 1 1. lambda$runProcessors$0 : negated conditional → KILLED
                  if (optional.isPresent()) {
77
                      var comment = optional.get();
78
                      for (Object processor : commentProcessors.values()) {
79
                          var commentProcessor = (CommentProcessor) processor;
80 1 1. lambda$runProcessors$0 : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → SURVIVED
                          commentProcessor.commitChanges(source);
81 1 1. lambda$runProcessors$0 : removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED
                          commentProcessor.reset();
82
                      }
83
                      proceedComments.add(comment);
84
                  }
85
              });
86
        // we run the paragraph afterward so that the comments inside work before the whole paragraph comments
87
        source.streamParagraphs()
88 1 1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED
              .forEach(p -> {
89
                  var document = source.document();
90
                  var comments = collectComments(source);
91
                  var optional = runProcessorsOnParagraphComment(document, comments, expressionContext, p);
92 1 1. lambda$runProcessors$1 : negated conditional → KILLED
                  if (optional.isPresent()) {
93
                      for (Object processor : commentProcessors.values()) {
94
                          var commentProcessor = (CommentProcessor) processor;
95 1 1. lambda$runProcessors$1 : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED
                          commentProcessor.commitChanges(source);
96 1 1. lambda$runProcessors$1 : removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED
                          commentProcessor.reset();
97
                      }
98
                      proceedComments.add(optional.get());
99
                  }
100
              });
101
        source.streamParagraphs()
102 2 1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED
2. lambda$runProcessors$2 : removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnInlineContent → KILLED
              .forEach(paragraph -> runProcessorsOnInlineContent(expressionContext, paragraph));
103
        for (Comment comment : proceedComments) {
104 1 1. runProcessors : removed call to pro/verron/officestamper/core/CommentUtil::deleteComment → KILLED
            CommentUtil.deleteComment(comment);
105
        }
106
    }
107
108
    private <T> Optional<Comment> runProcessorsOnRunComment(
109
            Map<BigInteger, Comment> comments,
110
            T expressionContext,
111
            R run,
112
            P paragraph
113
    ) {
114 1 1. runProcessorsOnRunComment : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnRunComment → SURVIVED
        return CommentUtil
115
                .getCommentAround(run, source.document())
116 1 1. lambda$runProcessorsOnRunComment$3 : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnRunComment$3 → SURVIVED
                .flatMap(c -> runCommentProcessors(
117
                        comments,
118
                        expressionContext,
119
                        c,
120
                        paragraph, run
121
                ));
122
    }
123
124
    /**
125
     * Takes the first comment on the specified paragraph and tries to evaluate
126
     * the string within the comment against all registered
127
     * {@link CommentProcessor}s.
128
     *
129
     * @param document          the Word document.
130
     * @param comments          the comments within the document.
131
     * @param expressionContext the context root object
132
     * @param <T>               the type of the context root object.
133
     */
134
    private <T> Optional<Comment> runProcessorsOnParagraphComment(
135
            WordprocessingMLPackage document,
136
            Map<BigInteger, Comment> comments,
137
            T expressionContext,
138
            P paragraph
139
    ) {
140 1 1. runProcessorsOnParagraphComment : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnParagraphComment → KILLED
        return CommentUtil
141
                .getCommentFor(paragraph, document)
142 1 1. lambda$runProcessorsOnParagraphComment$4 : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnParagraphComment$4 → KILLED
                .flatMap(c -> runCommentProcessors(
143
                        comments,
144
                        expressionContext,
145
                        c,
146
                        paragraph,
147
                        null
148
                ));
149
    }
150
151
    /**
152
     * Finds all processor expressions within the specified paragraph and tries
153
     * to evaluate it against all registered {@link CommentProcessor}s.
154
     *
155
     * @param context   the context root object against which evaluation is done
156
     * @param paragraph the paragraph to process.
157
     * @param <T>       type of the context root object
158
     */
159
    private <T> void runProcessorsOnInlineContent(
160
            T context,
161
            P paragraph
162
    ) {
163
        var paragraphWrapper = new StandardParagraph(paragraph);
164
        var text = paragraphWrapper.asString();
165
        var placeholders = Placeholders.findProcessors(text);
166
167
        for (var placeholder : placeholders) {
168
            for (var processor : commentProcessors.values()) {
169 1 1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED
                ((CommentProcessor) processor).setParagraph(paragraph);
170
            }
171
172
            try {
173 1 1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED
                expressionResolver.setContext(context);
174
                expressionResolver.resolve(placeholder);
175 1 1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/core/StandardParagraph::replace → KILLED
                paragraphWrapper.replace(placeholder, RunUtil.create(""));
176
                logger.debug("Placeholder '{}' successfully processed by a comment processor.", placeholder);
177
            } catch (SpelEvaluationException | SpelParseException e) {
178
                var message = "Placeholder '%s' failed to process.".formatted(placeholder);
179
                exceptionResolver.resolve(placeholder, message, e);
180
            }
181
            for (var processor : commentProcessors.values()) {
182 1 1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED
                ((CommentProcessor) processor).commitChanges(source);
183
            }
184
        }
185
    }
186
187
    private <T> Optional<Comment> runCommentProcessors(
188
            Map<BigInteger, Comment> comments,
189
            T context,
190
            Comments.Comment comment,
191
            P paragraph,
192
            @Nullable R run
193
    ) {
194
        Comment commentWrapper = comments.get(comment.getId());
195
196 1 1. runCommentProcessors : negated conditional → KILLED
        if (Objects.isNull(commentWrapper)) {
197
            // no comment to process
198
            return Optional.empty();
199
        }
200
201
        var placeholder = getCommentString(comment);
202
203
        for (final Object processor : commentProcessors.values()) {
204 1 1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED
            ((CommentProcessor) processor).setParagraph(paragraph);
205 1 1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentRun → KILLED
            ((CommentProcessor) processor).setCurrentRun(run);
206 1 1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentCommentWrapper → KILLED
            ((CommentProcessor) processor).setCurrentCommentWrapper(commentWrapper);
207
        }
208
209
        try {
210 1 1. runCommentProcessors : removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED
            expressionResolver.setContext(context);
211
            expressionResolver.resolve(placeholder);
212
            comments.remove(comment.getId());
213
            logger.debug("Comment '{}' successfully processed by a comment processor.", placeholder.expression());
214 1 1. runCommentProcessors : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runCommentProcessors → KILLED
            return Optional.of(commentWrapper);
215
        } catch (SpelEvaluationException | SpelParseException e) {
216
            var message = "Comment '%s' failed to process.".formatted(placeholder.expression());
217
            exceptionResolver.resolve(placeholder, message, e);
218
            return Optional.empty();
219
        }
220
    }
221
}

Mutations

72

1.1
Location : runProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#30]
removed call to java/util/stream/Stream::forEach → KILLED

76

1.1
Location : lambda$runProcessors$0
Killed by : pro.verron.officestamper.test.ResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ResolutionTest]/[test-template:testCustomResolution(java.lang.String, boolean, boolean, boolean, boolean, boolean, java.lang.String, boolean, java.lang.String)]/[test-template-invocation:#17]
negated conditional → KILLED

80

1.1
Location : lambda$runProcessors$0
Killed by : none
removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → SURVIVED
Covering tests

81

1.1
Location : lambda$runProcessors$0
Killed by : pro.verron.officestamper.test.MultiStampTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.MultiStampTest]/[method:expressionsAreResolvedOnMultiStamp()]
removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED

88

1.1
Location : runProcessors
Killed by : pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest]/[method:fails()]
removed call to java/util/stream/Stream::forEach → KILLED

92

1.1
Location : lambda$runProcessors$1
Killed by : pro.verron.officestamper.test.ResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ResolutionTest]/[test-template:testCustomResolution(java.lang.String, boolean, boolean, boolean, boolean, boolean, java.lang.String, boolean, java.lang.String)]/[test-template-invocation:#17]
negated conditional → KILLED

95

1.1
Location : lambda$runProcessors$1
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#38]
removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED

96

1.1
Location : lambda$runProcessors$1
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#7]
removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED

102

1.1
Location : runProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
removed call to java/util/stream/Stream::forEach → KILLED

2.2
Location : lambda$runProcessors$2
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnInlineContent → KILLED

104

1.1
Location : runProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#30]
removed call to pro/verron/officestamper/core/CommentUtil::deleteComment → KILLED

114

1.1
Location : runProcessorsOnRunComment
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnRunComment → SURVIVED
Covering tests

116

1.1
Location : lambda$runProcessorsOnRunComment$3
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnRunComment$3 → SURVIVED
Covering tests

140

1.1
Location : runProcessorsOnParagraphComment
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#38]
replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnParagraphComment → KILLED

142

1.1
Location : lambda$runProcessorsOnParagraphComment$4
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#38]
replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnParagraphComment$4 → KILLED

169

1.1
Location : runProcessorsOnInlineContent
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#20]
removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED

173

1.1
Location : runProcessorsOnInlineContent
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED

175

1.1
Location : runProcessorsOnInlineContent
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
removed call to pro/verron/officestamper/core/StandardParagraph::replace → KILLED

182

1.1
Location : runProcessorsOnInlineContent
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#20]
removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED

196

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest]/[method:fails()]
negated conditional → KILLED

204

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#22]
removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED

205

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#30]
removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentRun → KILLED

206

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#14]
removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentCommentWrapper → KILLED

210

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#30]
removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED

214

1.1
Location : runCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#30]
replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runCommentProcessors → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0