1 | package pro.verron.officestamper.preset; | |
2 | ||
3 | import jakarta.xml.bind.JAXBElement; | |
4 | import org.docx4j.XmlUtils; | |
5 | import org.docx4j.jaxb.Context; | |
6 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
7 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
8 | import org.docx4j.wml.*; | |
9 | import org.jvnet.jaxb2_commons.ppp.Child; | |
10 | import org.slf4j.Logger; | |
11 | import org.slf4j.LoggerFactory; | |
12 | import org.springframework.lang.Nullable; | |
13 | import pro.verron.officestamper.api.*; | |
14 | import pro.verron.officestamper.core.*; | |
15 | ||
16 | import java.io.IOException; | |
17 | import java.io.OutputStream; | |
18 | import java.io.PipedInputStream; | |
19 | import java.io.PipedOutputStream; | |
20 | import java.math.BigInteger; | |
21 | import java.util.*; | |
22 | import java.util.concurrent.CopyOnWriteArrayList; | |
23 | import java.util.concurrent.Executors; | |
24 | import java.util.concurrent.ThreadFactory; | |
25 | import java.util.concurrent.atomic.AtomicReference; | |
26 | import java.util.function.BiFunction; | |
27 | import java.util.function.Consumer; | |
28 | import java.util.function.Function; | |
29 | import java.util.function.Supplier; | |
30 | ||
31 | import static java.lang.String.format; | |
32 | import static java.util.Collections.emptyList; | |
33 | import static java.util.Objects.requireNonNull; | |
34 | import static java.util.stream.Collectors.toMap; | |
35 | import static org.docx4j.TextUtils.getText; | |
36 | import static pro.verron.officestamper.core.DocumentUtil.walkObjectsAndImportImages; | |
37 | ||
38 | /** | |
39 | * Factory class to create the correct comment processor for a given comment. | |
40 | * | |
41 | * @author Joseph Verron | |
42 | * @version ${version} | |
43 | * @since 1.6.4 | |
44 | */ | |
45 | public class CommentProcessorFactory { | |
46 | private static final Logger log = LoggerFactory.getLogger(CommentProcessorFactory.class); | |
47 | private final OfficeStamperConfiguration configuration; | |
48 | ||
49 | /** | |
50 | * Creates a new CommentProcessorFactory. | |
51 | * | |
52 | * @param configuration the configuration to use for the created processors. | |
53 | */ | |
54 | public CommentProcessorFactory(OfficeStamperConfiguration configuration) { | |
55 | this.configuration = configuration; | |
56 | } | |
57 | ||
58 | private static Tbl parentTable(P p) { | |
59 |
1
1. parentTable : negated conditional → KILLED |
if (parentRow(p).getParent() instanceof Tbl table) |
60 |
1
1. parentTable : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::parentTable → KILLED |
return table; |
61 | throw new OfficeStamperException(format("Paragraph is not within a table! : %s", getText(p))); | |
62 | } | |
63 | ||
64 | private static Tr parentRow(P p) { | |
65 |
1
1. parentRow : negated conditional → KILLED |
if (parentCell(p).getParent() instanceof Tr row) |
66 |
1
1. parentRow : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::parentRow → KILLED |
return row; |
67 | throw new OfficeStamperException(format("Paragraph is not within a row! : %s", getText(p))); | |
68 | } | |
69 | ||
70 | private static Tc parentCell(P p) { | |
71 |
1
1. parentCell : negated conditional → KILLED |
if (p.getParent() instanceof Tc cell) |
72 |
1
1. parentCell : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::parentCell → KILLED |
return cell; |
73 | throw new OfficeStamperException(format("Paragraph is not within a cell! : %s", getText(p))); | |
74 | } | |
75 | ||
76 | /** | |
77 | * Creates a new CommentProcessorFactory with default configuration. | |
78 | * | |
79 | * @param pr a {@link PlaceholderReplacer} object | |
80 | * | |
81 | * @return a {@link CommentProcessor} object | |
82 | */ | |
83 | public CommentProcessor repeatParagraph(ParagraphPlaceholderReplacer pr) { | |
84 |
1
1. repeatParagraph : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::repeatParagraph → KILLED |
return ParagraphRepeatProcessor.newInstance(pr); |
85 | } | |
86 | ||
87 | /** | |
88 | * Creates a new CommentProcessorFactory with default configuration. | |
89 | * | |
90 | * @param pr a {@link PlaceholderReplacer} object | |
91 | * | |
92 | * @return a {@link CommentProcessor} object | |
93 | */ | |
94 | public CommentProcessor repeatDocPart(ParagraphPlaceholderReplacer pr) { | |
95 |
1
1. repeatDocPart : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::repeatDocPart → KILLED |
return RepeatDocPartProcessor.newInstance(pr, getStamper()); |
96 | } | |
97 | ||
98 | private OfficeStamper<WordprocessingMLPackage> getStamper() { | |
99 |
2
1. lambda$getStamper$0 : removed call to pro/verron/officestamper/core/DocxStamper::stamp → TIMED_OUT 2. getStamper : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::getStamper → KILLED |
return (template, context, output) -> new DocxStamper(configuration).stamp(template, context, output); |
100 | } | |
101 | ||
102 | /** | |
103 | * Creates a new CommentProcessorFactory with default configuration. | |
104 | * | |
105 | * @param pr a {@link PlaceholderReplacer} object | |
106 | * | |
107 | * @return a {@link CommentProcessor} object | |
108 | */ | |
109 | public CommentProcessor repeat(ParagraphPlaceholderReplacer pr) { | |
110 |
1
1. repeat : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::repeat → KILLED |
return RepeatProcessor.newInstance(pr); |
111 | } | |
112 | ||
113 | /** | |
114 | * Creates a new CommentProcessorFactory with default configuration. | |
115 | * | |
116 | * @param pr a {@link PlaceholderReplacer} object | |
117 | * | |
118 | * @return a {@link CommentProcessor} object | |
119 | */ | |
120 | public CommentProcessor tableResolver(ParagraphPlaceholderReplacer pr) { | |
121 |
1
1. tableResolver : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::tableResolver → KILLED |
return TableResolver.newInstance(pr); |
122 | } | |
123 | ||
124 | /** | |
125 | * Creates a new CommentProcessorFactory with default configuration. | |
126 | * | |
127 | * @param pr a {@link PlaceholderReplacer} object | |
128 | * | |
129 | * @return a {@link CommentProcessor} object | |
130 | */ | |
131 | public CommentProcessor displayIf(ParagraphPlaceholderReplacer pr) { | |
132 |
1
1. displayIf : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::displayIf → KILLED |
return DisplayIfProcessor.newInstance(pr); |
133 | } | |
134 | ||
135 | /** | |
136 | * Creates a new CommentProcessorFactory with default configuration. | |
137 | * | |
138 | * @param pr a {@link PlaceholderReplacer} object | |
139 | * | |
140 | * @return a {@link CommentProcessor} object | |
141 | */ | |
142 | public CommentProcessor replaceWith(ParagraphPlaceholderReplacer pr) { | |
143 |
1
1. replaceWith : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory::replaceWith → KILLED |
return ReplaceWithProcessor.newInstance(pr); |
144 | } | |
145 | ||
146 | /** | |
147 | * This interface is used to resolve a table in the template document. | |
148 | * The table is passed to the resolveTable method and will be used to fill an existing Tbl object in the document. | |
149 | * | |
150 | * @author Joseph Verron | |
151 | * @version ${version} | |
152 | * @since 1.6.2 | |
153 | */ | |
154 | public interface ITableResolver { | |
155 | /** | |
156 | * Resolves the given table by manipulating the given table in the template | |
157 | * | |
158 | * @param table the table to resolve. | |
159 | */ | |
160 | void resolveTable(StampTable table); | |
161 | } | |
162 | ||
163 | /** | |
164 | * Interface for processors that replace a single word with an expression defined | |
165 | * in a comment. | |
166 | * | |
167 | * @author Joseph Verron | |
168 | * @author Tom Hombergs | |
169 | * @version ${version} | |
170 | * @since 1.0.8 | |
171 | */ | |
172 | public interface IReplaceWithProcessor { | |
173 | ||
174 | /** | |
175 | * May be called to replace a single word inside a paragraph with an expression | |
176 | * defined in a comment. The comment must be applied to a single word for the | |
177 | * replacement to take effect! | |
178 | * | |
179 | * @param expression the expression to replace the text with | |
180 | */ | |
181 | void replaceWordWith(@Nullable String expression); | |
182 | } | |
183 | ||
184 | /** | |
185 | * Implementations of this interface are responsible for processing the repeat paragraph instruction. | |
186 | * The repeat paragraph instruction is a comment that contains the following text: | |
187 | * <p> | |
188 | * <code> | |
189 | * repeatParagraph(...) | |
190 | * </code> | |
191 | * <p> | |
192 | * Where the three dots represent an expression that evaluates to a list of objects. | |
193 | * The processor then copies the paragraph once for each object in the list and evaluates all expressions | |
194 | * within each copy against the respective object. | |
195 | * | |
196 | * @author Joseph Verron | |
197 | * @author Romain Lamarche | |
198 | * @version ${version} | |
199 | * @since 1.0.0 | |
200 | */ | |
201 | public interface IParagraphRepeatProcessor { | |
202 | ||
203 | /** | |
204 | * May be called to mark a paragraph to be copied once for each element in the passed-in list. | |
205 | * Within each copy of the row, all expressions are evaluated against one of the objects in the list. | |
206 | * | |
207 | * @param objects the objects which serve as context root for expressions found in the template table row. | |
208 | */ | |
209 | void repeatParagraph(List<Object> objects); | |
210 | } | |
211 | ||
212 | /** | |
213 | * Interface for processors which may be called to mark a document part to be copied once for each element in the | |
214 | * passed-in list. | |
215 | * Within each copy of the row, all expressions are evaluated against one of the objects in the list. | |
216 | * | |
217 | * @author Joseph Verron | |
218 | * @author Artem Medvedev | |
219 | * @version ${version} | |
220 | * @since 1.0.0 | |
221 | */ | |
222 | public interface IRepeatDocPartProcessor { | |
223 | ||
224 | /** | |
225 | * May be called to mark a document part to be copied once for each element in the passed-in list. | |
226 | * Within each copy of the row, all expressions are evaluated against one of the objects in the list. | |
227 | * | |
228 | * @param objects the objects which serve as context root for expressions found in the template table row. | |
229 | * | |
230 | * @throws Exception if the processing fails. | |
231 | */ | |
232 | void repeatDocPart(@Nullable List<Object> objects) | |
233 | throws Exception; | |
234 | } | |
235 | ||
236 | /** | |
237 | * Interface for processors that can repeat a table row. | |
238 | * | |
239 | * @author Joseph Verron | |
240 | * @author Tom Hombergs | |
241 | * @version ${version} | |
242 | * @since 1.0.0 | |
243 | */ | |
244 | public interface IRepeatProcessor { | |
245 | ||
246 | /** | |
247 | * May be called to mark a table row to be copied once for each element in the passed-in list. | |
248 | * Within each copy of the row, all expressions are evaluated against one of the objects in the list. | |
249 | * | |
250 | * @param objects the objects which serve as context root for expressions found in the template table row. | |
251 | */ | |
252 | void repeatTableRow(List<Object> objects); | |
253 | ||
254 | } | |
255 | ||
256 | /** | |
257 | * Interface for processors that may be used to delete commented paragraphs or tables from the document, depending | |
258 | * on a given condition. | |
259 | * | |
260 | * @author Joseph Verron | |
261 | * @author Tom Hombergs | |
262 | * @version ${version} | |
263 | * @since 1.0.0 | |
264 | */ | |
265 | public interface IDisplayIfProcessor { | |
266 | ||
267 | /** | |
268 | * May be called to delete the commented paragraph or not, depending on the given boolean condition. | |
269 | * | |
270 | * @param condition if true, the commented paragraph will remain in the document. If false, the commented | |
271 | * paragraph | |
272 | * will be deleted at stamping. | |
273 | */ | |
274 | void displayParagraphIf(Boolean condition); | |
275 | ||
276 | /** | |
277 | * May be called to delete the commented paragraph or not, depending on the presence of the given data. | |
278 | * | |
279 | * @param condition if non-null, the commented paragraph will remain in | |
280 | * the document. If null, the commented paragraph | |
281 | * will be deleted at stamping. | |
282 | */ | |
283 | void displayParagraphIfPresent(@Nullable Object condition); | |
284 | ||
285 | /** | |
286 | * May be called to delete the table surrounding the commented paragraph, depending on the given boolean | |
287 | * condition. | |
288 | * | |
289 | * @param condition if true, the table row surrounding the commented paragraph will remain in the document. If | |
290 | * false, the table row | |
291 | * will be deleted at stamping. | |
292 | */ | |
293 | void displayTableRowIf(Boolean condition); | |
294 | ||
295 | /** | |
296 | * May be called to delete the table surrounding the commented paragraph, depending on the given boolean | |
297 | * condition. | |
298 | * | |
299 | * @param condition if true, the table surrounding the commented paragraph will remain in the document. If | |
300 | * false, the table | |
301 | * will be deleted at stamping. | |
302 | */ | |
303 | void displayTableIf(Boolean condition); | |
304 | ||
305 | } | |
306 | ||
307 | /** | |
308 | * TableResolver class. | |
309 | * | |
310 | * @author Joseph Verron | |
311 | * @version ${version} | |
312 | * @since 1.6.2 | |
313 | */ | |
314 | private static class TableResolver | |
315 | extends AbstractCommentProcessor | |
316 | implements ITableResolver { | |
317 | private final Map<Tbl, StampTable> cols = new HashMap<>(); | |
318 | private final Function<Tbl, List<Object>> nullSupplier; | |
319 | ||
320 | private TableResolver( | |
321 | ParagraphPlaceholderReplacer placeholderReplacer, | |
322 | Function<Tbl, List<Object>> nullSupplier | |
323 | ) { | |
324 | super(placeholderReplacer); | |
325 | this.nullSupplier = nullSupplier; | |
326 | } | |
327 | ||
328 | /** | |
329 | * Generate a new {@link TableResolver} instance where value is replaced by an empty list when <code>null</code> | |
330 | * | |
331 | * @param pr a {@link PlaceholderReplacer} instance | |
332 | * | |
333 | * @return a new {@link TableResolver} instance | |
334 | */ | |
335 | public static CommentProcessor newInstance(ParagraphPlaceholderReplacer pr) { | |
336 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::newInstance → KILLED |
return new TableResolver(pr, table -> Collections.emptyList()); |
337 | } | |
338 | ||
339 | /** | |
340 | * {@inheritDoc} | |
341 | */ | |
342 | @Override public void resolveTable(StampTable givenTable) { | |
343 | cols.put(parentTable(getParagraph()), givenTable); | |
344 | } | |
345 | ||
346 | /** | |
347 | * {@inheritDoc} | |
348 | */ | |
349 | @Override | |
350 | public void commitChanges(DocxPart document) { | |
351 | for (Map.Entry<Tbl, StampTable> entry : cols.entrySet()) { | |
352 | Tbl wordTable = entry.getKey(); | |
353 | ||
354 | StampTable stampedTable = entry.getValue(); | |
355 | ||
356 |
1
1. commitChanges : negated conditional → KILLED |
if (stampedTable != null) { |
357 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::replaceTableInplace → KILLED |
replaceTableInplace(wordTable, stampedTable); |
358 | } | |
359 | else { | |
360 | List<Object> tableParentContent = ((ContentAccessor) wordTable.getParent()).getContent(); | |
361 | int tablePosition = tableParentContent.indexOf(wordTable); | |
362 | List<Object> toInsert = nullSupplier.apply(wordTable); | |
363 | tableParentContent.set(tablePosition, toInsert); | |
364 | } | |
365 | } | |
366 | } | |
367 | ||
368 | @Override public void commitChanges(WordprocessingMLPackage document) { | |
369 | throw new OfficeStamperException("Should not be called, since deprecation"); | |
370 | } | |
371 | ||
372 | /** | |
373 | * {@inheritDoc} | |
374 | */ | |
375 | @Override public void reset() { | |
376 |
1
1. reset : removed call to java/util/Map::clear → SURVIVED |
cols.clear(); |
377 | } | |
378 | ||
379 | private void replaceTableInplace(Tbl wordTable, StampTable stampedTable) { | |
380 | var headers = stampedTable.headers(); | |
381 | ||
382 | var rows = wordTable.getContent(); | |
383 | var headerRow = (Tr) rows.get(0); | |
384 | var firstDataRow = (Tr) rows.get(1); | |
385 | ||
386 |
1
1. replaceTableInplace : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::growAndFillRow → KILLED |
growAndFillRow(headerRow, headers); |
387 | ||
388 |
1
1. replaceTableInplace : negated conditional → KILLED |
if (stampedTable.isEmpty()) rows.remove(firstDataRow); |
389 | else { | |
390 |
1
1. replaceTableInplace : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::growAndFillRow → KILLED |
growAndFillRow(firstDataRow, stampedTable.get(0)); |
391 | for (var rowContent : stampedTable.subList(1, stampedTable.size())) | |
392 | rows.add(copyRowFromTemplate(firstDataRow, rowContent)); | |
393 | } | |
394 | } | |
395 | ||
396 | private void growAndFillRow(Tr row, List<String> values) { | |
397 | List<Object> cellRowContent = row.getContent(); | |
398 | ||
399 | //Replace text in first cell | |
400 | JAXBElement<Tc> cell0 = (JAXBElement<Tc>) cellRowContent.get(0); | |
401 | Tc cell0tc = cell0.getValue(); | |
402 |
2
1. growAndFillRow : negated conditional → KILLED 2. growAndFillRow : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::setCellText → KILLED |
setCellText(cell0tc, values.isEmpty() ? "" : values.get(0)); |
403 | ||
404 |
2
1. growAndFillRow : changed conditional boundary → SURVIVED 2. growAndFillRow : negated conditional → KILLED |
if (values.size() > 1) { |
405 | //Copy the first cell and replace content for each remaining value | |
406 | for (String cellContent : values.subList(1, values.size())) { | |
407 | JAXBElement<Tc> xmlCell = XmlUtils.deepCopy(cell0); | |
408 |
1
1. growAndFillRow : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::setCellText → KILLED |
setCellText(xmlCell.getValue(), cellContent); |
409 | cellRowContent.add(xmlCell); | |
410 | } | |
411 | } | |
412 | } | |
413 | ||
414 | private Tr copyRowFromTemplate(Tr firstDataRow, List<String> rowContent) { | |
415 | Tr newXmlRow = XmlUtils.deepCopy(firstDataRow); | |
416 | List<Object> xmlRow = newXmlRow.getContent(); | |
417 |
2
1. copyRowFromTemplate : negated conditional → KILLED 2. copyRowFromTemplate : changed conditional boundary → KILLED |
for (int i = 0; i < rowContent.size(); i++) { |
418 | String cellContent = rowContent.get(i); | |
419 | Tc xmlCell = ((JAXBElement<Tc>) xmlRow.get(i)).getValue(); | |
420 |
1
1. copyRowFromTemplate : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::setCellText → KILLED |
setCellText(xmlCell, cellContent); |
421 | } | |
422 |
1
1. copyRowFromTemplate : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$TableResolver::copyRowFromTemplate → KILLED |
return newXmlRow; |
423 | } | |
424 | ||
425 | private void setCellText(Tc tableCell, String content) { | |
426 | var tableCellContent = tableCell.getContent(); | |
427 |
1
1. setCellText : removed call to java/util/List::clear → KILLED |
tableCellContent.clear(); |
428 | tableCellContent.add(ParagraphUtil.create(content)); | |
429 | } | |
430 | } | |
431 | ||
432 | /** | |
433 | * Processor that replaces the current run with the provided expression. | |
434 | * This is useful for replacing an expression in a comment with the result of the expression. | |
435 | * | |
436 | * @author Joseph Verron | |
437 | * @author Tom Hombergs | |
438 | * @version ${version} | |
439 | * @since 1.0.7 | |
440 | */ | |
441 | private static class ReplaceWithProcessor | |
442 | extends AbstractCommentProcessor | |
443 | implements IReplaceWithProcessor { | |
444 | ||
445 | private final Function<R, List<Object>> nullSupplier; | |
446 | ||
447 | private ReplaceWithProcessor( | |
448 | ParagraphPlaceholderReplacer placeholderReplacer, Function<R, List<Object>> nullSupplier | |
449 | ) { | |
450 | super(placeholderReplacer); | |
451 | this.nullSupplier = nullSupplier; | |
452 | } | |
453 | ||
454 | /** | |
455 | * Creates a new processor that replaces the current run with the result of the expression. | |
456 | * | |
457 | * @param pr the placeholder replacer to use | |
458 | * | |
459 | * @return the processor | |
460 | */ | |
461 | public static CommentProcessor newInstance(ParagraphPlaceholderReplacer pr) { | |
462 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$ReplaceWithProcessor::newInstance → KILLED |
return new ReplaceWithProcessor(pr, R::getContent); |
463 | } | |
464 | ||
465 | /** | |
466 | * {@inheritDoc} | |
467 | */ | |
468 | @Override | |
469 | public void commitChanges(DocxPart document) { | |
470 | // nothing to commit | |
471 | } | |
472 | ||
473 | /** | |
474 | * {@inheritDoc} | |
475 | */ | |
476 | @Override public void reset() { | |
477 | // nothing to reset | |
478 | } | |
479 | ||
480 | /** | |
481 | * {@inheritDoc} | |
482 | */ | |
483 | @Override public void replaceWordWith(@Nullable String expression) { | |
484 | R run = this.getCurrentRun(); | |
485 |
1
1. replaceWordWith : negated conditional → KILLED |
if (run == null) { |
486 | log.info(format("Impossible to put expression %s in a null run", expression)); | |
487 | return; | |
488 | } | |
489 | ||
490 | List<Object> target; | |
491 |
1
1. replaceWordWith : negated conditional → KILLED |
if (expression != null) { |
492 | target = List.of(RunUtil.createText(expression)); | |
493 | } | |
494 | else { | |
495 | target = nullSupplier.apply(run); | |
496 | } | |
497 | run.getContent() | |
498 |
1
1. replaceWordWith : removed call to java/util/List::clear → KILLED |
.clear(); |
499 | run.getContent() | |
500 | .addAll(target); | |
501 | } | |
502 | } | |
503 | ||
504 | /** | |
505 | * This class is used to repeat paragraphs and tables. | |
506 | * <p> | |
507 | * It is used internally by the DocxStamper and should not be instantiated by | |
508 | * clients. | |
509 | * | |
510 | * @author Joseph Verron | |
511 | * @author Youssouf Naciri | |
512 | * @version ${version} | |
513 | * @since 1.2.2 | |
514 | */ | |
515 | private static class ParagraphRepeatProcessor | |
516 | extends AbstractCommentProcessor | |
517 | implements IParagraphRepeatProcessor { | |
518 | private final Supplier<? extends List<? extends P>> nullSupplier; | |
519 | private Map<P, Paragraphs> pToRepeat = new HashMap<>(); | |
520 | ||
521 | /** | |
522 | * @param placeholderReplacer replaces placeholders with values | |
523 | * @param nullSupplier supplies a list of paragraphs if the list of objects to repeat is null | |
524 | */ | |
525 | private ParagraphRepeatProcessor( | |
526 | ParagraphPlaceholderReplacer placeholderReplacer, | |
527 | Supplier<? extends List<? extends P>> nullSupplier | |
528 | ) { | |
529 | super(placeholderReplacer); | |
530 | this.nullSupplier = nullSupplier; | |
531 | } | |
532 | ||
533 | /** | |
534 | * <p>newInstance.</p> | |
535 | * | |
536 | * @param placeholderReplacer replaces expressions with values | |
537 | * | |
538 | * @return a new instance of ParagraphRepeatProcessor | |
539 | */ | |
540 | public static CommentProcessor newInstance(ParagraphPlaceholderReplacer placeholderReplacer) { | |
541 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphRepeatProcessor::newInstance → KILLED |
return new ParagraphRepeatProcessor(placeholderReplacer, Collections::emptyList); |
542 | } | |
543 | ||
544 | /** | |
545 | * {@inheritDoc} | |
546 | */ | |
547 | @Override public void repeatParagraph(List<Object> objects) { | |
548 | P paragraph = getParagraph(); | |
549 | ||
550 | Deque<P> paragraphs = getParagraphsInsideComment(paragraph); | |
551 | ||
552 | Paragraphs toRepeat = new Paragraphs(); | |
553 | toRepeat.comment = getCurrentCommentWrapper(); | |
554 | toRepeat.data = new ArrayDeque<>(objects); | |
555 | toRepeat.paragraphs = paragraphs; | |
556 | toRepeat.sectionBreakBefore = SectionUtil.getPreviousSectionBreakIfPresent(paragraph, | |
557 | (ContentAccessor) paragraph.getParent()); | |
558 | toRepeat.firstParagraphSectionBreak = SectionUtil.getParagraphSectionBreak(paragraph); | |
559 | toRepeat.hasOddSectionBreaks = SectionUtil.isOddNumberOfSectionBreaks(new ArrayList<>(toRepeat.paragraphs)); | |
560 | ||
561 | var paragraphPPr = paragraph.getPPr(); | |
562 |
2
1. repeatParagraph : negated conditional → SURVIVED 2. repeatParagraph : negated conditional → KILLED |
if (paragraphPPr != null && paragraphPPr.getSectPr() != null) { |
563 | // we need to clear the first paragraph's section break to be able to control how to repeat it | |
564 |
1
1. repeatParagraph : removed call to org/docx4j/wml/PPr::setSectPr → NO_COVERAGE |
paragraphPPr.setSectPr(null); |
565 | } | |
566 | pToRepeat.put(paragraph, toRepeat); | |
567 | } | |
568 | ||
569 | /** | |
570 | * Returns all paragraphs inside the comment of the given paragraph. | |
571 | * <p> | |
572 | * If the paragraph is not inside a comment, the given paragraph is returned. | |
573 | * | |
574 | * @param paragraph the paragraph to analyze | |
575 | * | |
576 | * @return all paragraphs inside the comment of the given paragraph | |
577 | */ | |
578 | public static Deque<P> getParagraphsInsideComment(P paragraph) { | |
579 | BigInteger commentId = null; | |
580 | boolean foundEnd = false; | |
581 | ||
582 | Deque<P> paragraphs = new ArrayDeque<>(); | |
583 | paragraphs.add(paragraph); | |
584 | ||
585 | for (Object object : paragraph.getContent()) { | |
586 |
1
1. getParagraphsInsideComment : negated conditional → KILLED |
if (object instanceof CommentRangeStart crs) commentId = crs.getId(); |
587 |
2
1. getParagraphsInsideComment : negated conditional → KILLED 2. getParagraphsInsideComment : negated conditional → KILLED |
if (object instanceof CommentRangeEnd cre && Objects.equals(commentId, cre.getId())) foundEnd = true; |
588 | } | |
589 |
3
1. getParagraphsInsideComment : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphRepeatProcessor::getParagraphsInsideComment → KILLED 2. getParagraphsInsideComment : negated conditional → KILLED 3. getParagraphsInsideComment : negated conditional → KILLED |
if (foundEnd || commentId == null) return paragraphs; |
590 | ||
591 | Object parent = paragraph.getParent(); | |
592 |
1
1. getParagraphsInsideComment : negated conditional → KILLED |
if (parent instanceof ContentAccessor contentAccessor) { |
593 | var accessorContent = contentAccessor.getContent(); | |
594 | int index = accessorContent.indexOf(paragraph); | |
595 |
4
1. getParagraphsInsideComment : changed conditional boundary → SURVIVED 2. getParagraphsInsideComment : negated conditional → KILLED 3. getParagraphsInsideComment : negated conditional → KILLED 4. getParagraphsInsideComment : Replaced integer addition with subtraction → KILLED |
for (int i = index + 1; i < accessorContent.size() && !foundEnd; i++) { |
596 | var next = accessorContent.get(i); | |
597 |
2
1. getParagraphsInsideComment : negated conditional → NO_COVERAGE 2. getParagraphsInsideComment : negated conditional → KILLED |
if (next instanceof CommentRangeEnd cre && Objects.equals(commentId, cre.getId())) foundEnd = true; |
598 | else { | |
599 |
1
1. getParagraphsInsideComment : negated conditional → KILLED |
if (next instanceof P p) { |
600 | paragraphs.add(p); | |
601 | } | |
602 |
1
1. getParagraphsInsideComment : negated conditional → KILLED |
if (next instanceof ContentAccessor childContent) { |
603 | for (Object child : childContent.getContent()) { | |
604 |
2
1. getParagraphsInsideComment : negated conditional → KILLED 2. getParagraphsInsideComment : negated conditional → KILLED |
if (child instanceof CommentRangeEnd cre && Objects.equals(commentId, cre.getId())) { |
605 | foundEnd = true; | |
606 | break; | |
607 | } | |
608 | } | |
609 | } | |
610 | } | |
611 | } | |
612 | } | |
613 |
1
1. getParagraphsInsideComment : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphRepeatProcessor::getParagraphsInsideComment → KILLED |
return paragraphs; |
614 | } | |
615 | ||
616 | /** | |
617 | * {@inheritDoc} | |
618 | */ | |
619 | @Override | |
620 | public void commitChanges(DocxPart document) { | |
621 | for (Map.Entry<P, Paragraphs> entry : pToRepeat.entrySet()) { | |
622 | P currentP = entry.getKey(); | |
623 | ContentAccessor parent = (ContentAccessor) currentP.getParent(); | |
624 | List<Object> parentContent = parent.getContent(); | |
625 | int index = parentContent.indexOf(currentP); | |
626 |
2
1. commitChanges : changed conditional boundary → SURVIVED 2. commitChanges : negated conditional → KILLED |
if (index < 0) throw new OfficeStamperException("Impossible"); |
627 | ||
628 | Paragraphs paragraphsToRepeat = entry.getValue(); | |
629 | Deque<Object> expressionContexts = Objects.requireNonNull(paragraphsToRepeat).data; | |
630 | ||
631 |
1
1. commitChanges : negated conditional → KILLED |
Deque<P> collection = expressionContexts == null |
632 | ? new ArrayDeque<>(nullSupplier.get()) | |
633 | : generateParagraphsToAdd(document, paragraphsToRepeat, expressionContexts); | |
634 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphRepeatProcessor::restoreFirstSectionBreakIfNeeded → SURVIVED |
restoreFirstSectionBreakIfNeeded(paragraphsToRepeat, collection); |
635 | parentContent.addAll(index, collection); | |
636 | parentContent.removeAll(paragraphsToRepeat.paragraphs); | |
637 | } | |
638 | } | |
639 | ||
640 | private Deque<P> generateParagraphsToAdd( | |
641 | DocxPart document, | |
642 | Paragraphs paragraphs, | |
643 | Deque<Object> expressionContexts | |
644 | ) { | |
645 | Deque<P> paragraphsToAdd = new ArrayDeque<>(); | |
646 | ||
647 | Object lastExpressionContext = expressionContexts.peekLast(); | |
648 | P lastParagraph = paragraphs.paragraphs.peekLast(); | |
649 | ||
650 | for (Object expressionContext : expressionContexts) { | |
651 | for (P paragraphToClone : paragraphs.paragraphs) { | |
652 | P pClone = XmlUtils.deepCopy(paragraphToClone); | |
653 | ||
654 |
4
1. generateParagraphsToAdd : negated conditional → KILLED 2. generateParagraphsToAdd : negated conditional → KILLED 3. generateParagraphsToAdd : negated conditional → KILLED 4. generateParagraphsToAdd : negated conditional → KILLED |
if (paragraphs.sectionBreakBefore != null && paragraphs.hasOddSectionBreaks |
655 | && expressionContext != lastExpressionContext | |
656 | && paragraphToClone == lastParagraph) { | |
657 |
1
1. generateParagraphsToAdd : removed call to pro/verron/officestamper/core/SectionUtil::applySectionBreakToParagraph → KILLED |
SectionUtil.applySectionBreakToParagraph(paragraphs.sectionBreakBefore, pClone); |
658 | } | |
659 | ||
660 | var pCloneContent = pClone.getContent(); | |
661 | var commentId = paragraphs.comment | |
662 | .getComment() | |
663 | .getId(); | |
664 |
1
1. generateParagraphsToAdd : removed call to pro/verron/officestamper/core/CommentUtil::deleteCommentFromElements → KILLED |
CommentUtil.deleteCommentFromElements(pCloneContent, commentId); |
665 | var paragraph = new StandardParagraph(pClone); | |
666 |
1
1. generateParagraphsToAdd : removed call to pro/verron/officestamper/api/ParagraphPlaceholderReplacer::resolveExpressionsForParagraph → KILLED |
placeholderReplacer.resolveExpressionsForParagraph(document, paragraph, expressionContext); |
667 | paragraphsToAdd.add(pClone); | |
668 | } | |
669 | } | |
670 |
1
1. generateParagraphsToAdd : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphRepeatProcessor::generateParagraphsToAdd → KILLED |
return paragraphsToAdd; |
671 | } | |
672 | ||
673 | private static void restoreFirstSectionBreakIfNeeded( | |
674 | Paragraphs paragraphs, Deque<P> paragraphsToAdd | |
675 | ) { | |
676 |
1
1. restoreFirstSectionBreakIfNeeded : negated conditional → KILLED |
if (paragraphs.firstParagraphSectionBreak != null) { |
677 | P breakP = paragraphsToAdd.getLast(); | |
678 |
1
1. restoreFirstSectionBreakIfNeeded : removed call to pro/verron/officestamper/core/SectionUtil::applySectionBreakToParagraph → NO_COVERAGE |
SectionUtil.applySectionBreakToParagraph(paragraphs.firstParagraphSectionBreak, breakP); |
679 | } | |
680 | } | |
681 | ||
682 | /** | |
683 | * {@inheritDoc} | |
684 | */ | |
685 | @Override public void reset() { | |
686 | pToRepeat = new HashMap<>(); | |
687 | } | |
688 | ||
689 | private static class Paragraphs { | |
690 | Comment comment; | |
691 | Deque<Object> data; | |
692 | Deque<P> paragraphs; | |
693 | // hasOddSectionBreaks is true if the paragraphs to repeat contain an odd number of section breaks | |
694 | // changing the layout, false otherwise | |
695 | boolean hasOddSectionBreaks; | |
696 | // section break right before the first paragraph to repeat if present, or null | |
697 | SectPr sectionBreakBefore; | |
698 | // section break on the first paragraph to repeat if present, or null | |
699 | SectPr firstParagraphSectionBreak; | |
700 | } | |
701 | } | |
702 | ||
703 | /** | |
704 | * Walks through a document and replaces expressions with values from the given | |
705 | * expression context. | |
706 | * This walker only replaces expressions in paragraphs, not in tables. | |
707 | * | |
708 | * @author Joseph Verron | |
709 | * @version ${version} | |
710 | * @since 1.4.7 | |
711 | */ | |
712 | private static class ParagraphResolverDocumentWalker | |
713 | extends BaseDocumentWalker { | |
714 | private final Object expressionContext; | |
715 | private final DocxPart docxPart; | |
716 | private final ParagraphPlaceholderReplacer placeholderReplacer; | |
717 | ||
718 | /** | |
719 | * <p>Constructor for ParagraphResolverDocumentWalker.</p> | |
720 | * | |
721 | * @param rowClone The row to start with | |
722 | * @param expressionContext The context of the expressions to resolve | |
723 | * @param replacer The placeholderReplacer to use for resolving | |
724 | */ | |
725 | public ParagraphResolverDocumentWalker( | |
726 | DocxPart docxPart, | |
727 | Tr rowClone, | |
728 | Object expressionContext, | |
729 | ParagraphPlaceholderReplacer replacer | |
730 | ) { | |
731 | super(docxPart.from(rowClone)); | |
732 | this.expressionContext = expressionContext; | |
733 | this.docxPart = docxPart; | |
734 | this.placeholderReplacer = replacer; | |
735 | } | |
736 | ||
737 | /** | |
738 | * {@inheritDoc} | |
739 | */ | |
740 | @Override protected void onParagraph(P paragraph) { | |
741 | var standardParagraph = new StandardParagraph(paragraph); | |
742 |
1
1. onParagraph : removed call to pro/verron/officestamper/api/ParagraphPlaceholderReplacer::resolveExpressionsForParagraph → KILLED |
placeholderReplacer.resolveExpressionsForParagraph(docxPart, standardParagraph, expressionContext); |
743 | } | |
744 | } | |
745 | ||
746 | /** | |
747 | * This class is responsible for processing the <ds: repeat> tag. | |
748 | * It uses the {@link OfficeStamper} to stamp the sub document and then | |
749 | * copies the resulting sub document to the correct position in the | |
750 | * main document. | |
751 | * | |
752 | * @author Joseph Verron | |
753 | * @author Youssouf Naciri | |
754 | * @version ${version} | |
755 | * @since 1.3.0 | |
756 | */ | |
757 | private static class RepeatDocPartProcessor | |
758 | extends AbstractCommentProcessor | |
759 | implements IRepeatDocPartProcessor { | |
760 | private static final ThreadFactory threadFactory = Executors.defaultThreadFactory(); | |
761 | private static final ObjectFactory objectFactory = Context.getWmlObjectFactory(); | |
762 | ||
763 | private final OfficeStamper<WordprocessingMLPackage> stamper; | |
764 | private final Map<Comment, List<Object>> contexts = new HashMap<>(); | |
765 | private final Supplier<? extends List<?>> nullSupplier; | |
766 | ||
767 | private RepeatDocPartProcessor( | |
768 | ParagraphPlaceholderReplacer placeholderReplacer, | |
769 | OfficeStamper<WordprocessingMLPackage> stamper, | |
770 | Supplier<? extends List<?>> nullSupplier | |
771 | ) { | |
772 | super(placeholderReplacer); | |
773 | this.stamper = stamper; | |
774 | this.nullSupplier = nullSupplier; | |
775 | } | |
776 | ||
777 | /** | |
778 | * <p>newInstance.</p> | |
779 | * | |
780 | * @param pr the placeholderReplacer | |
781 | * @param stamper the stamper | |
782 | * | |
783 | * @return a new instance of this processor | |
784 | */ | |
785 | public static CommentProcessor newInstance( | |
786 | ParagraphPlaceholderReplacer pr, OfficeStamper<WordprocessingMLPackage> stamper | |
787 | ) { | |
788 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::newInstance → KILLED |
return new RepeatDocPartProcessor(pr, stamper, Collections::emptyList); |
789 | } | |
790 | ||
791 | /** | |
792 | * {@inheritDoc} | |
793 | */ | |
794 | @Override public void repeatDocPart(@Nullable List<Object> contexts) { | |
795 |
1
1. repeatDocPart : negated conditional → KILLED |
if (contexts == null) contexts = Collections.emptyList(); |
796 | ||
797 | Comment currentComment = getCurrentCommentWrapper(); | |
798 | List<Object> elements = currentComment.getElements(); | |
799 | ||
800 |
1
1. repeatDocPart : negated conditional → KILLED |
if (!elements.isEmpty()) { |
801 | this.contexts.put(currentComment, contexts); | |
802 | } | |
803 | } | |
804 | ||
805 | /** | |
806 | * {@inheritDoc} | |
807 | */ | |
808 | @Override public void commitChanges(DocxPart source) { | |
809 | for (Map.Entry<Comment, List<Object>> entry : this.contexts.entrySet()) { | |
810 | var comment = entry.getKey(); | |
811 | var expressionContexts = entry.getValue(); | |
812 | var gcp = requireNonNull(comment.getParent()); | |
813 | var repeatElements = comment.getElements(); | |
814 | var subTemplate = CommentUtil.createSubWordDocument(comment); | |
815 | var previousSectionBreak = SectionUtil.getPreviousSectionBreakIfPresent(repeatElements.get(0), gcp); | |
816 | var oddNumberOfBreaks = SectionUtil.isOddNumberOfSectionBreaks(repeatElements); | |
817 |
1
1. commitChanges : negated conditional → KILLED |
var changes = expressionContexts == null |
818 | ? nullSupplier.get() | |
819 | : stampSubDocuments(source.document(), | |
820 | expressionContexts, | |
821 | gcp, | |
822 | subTemplate, | |
823 | previousSectionBreak, | |
824 | oddNumberOfBreaks); | |
825 | var gcpContent = gcp.getContent(); | |
826 | var index = gcpContent.indexOf(repeatElements.get(0)); | |
827 | gcpContent.addAll(index, changes); | |
828 | gcpContent.removeAll(repeatElements); | |
829 | } | |
830 | ||
831 | } | |
832 | ||
833 | private List<Object> stampSubDocuments( | |
834 | WordprocessingMLPackage document, | |
835 | List<Object> expressionContexts, | |
836 | ContentAccessor gcp, | |
837 | WordprocessingMLPackage subTemplate, | |
838 | @Nullable SectPr previousSectionBreak, | |
839 | boolean oddNumberOfBreaks | |
840 | ) { | |
841 | var subDocuments = stampSubDocuments(expressionContexts, subTemplate); | |
842 | var replacements = subDocuments.stream() | |
843 | //TODO_LATER: move side effect somewhere else | |
844 |
1
1. lambda$stampSubDocuments$0 : replaced return value with Collections.emptyMap for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::lambda$stampSubDocuments$0 → KILLED |
.map(p -> walkObjectsAndImportImages(p, document)) |
845 | .map(Map::entrySet) | |
846 | .flatMap(Set::stream) | |
847 | .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); | |
848 | ||
849 | var changes = new ArrayList<>(); | |
850 | for (WordprocessingMLPackage subDocument : subDocuments) { | |
851 | var os = documentAsInsertableElements(subDocument, oddNumberOfBreaks, previousSectionBreak); | |
852 | os.stream() | |
853 | .filter(ContentAccessor.class::isInstance) | |
854 | .map(ContentAccessor.class::cast) | |
855 |
2
1. lambda$stampSubDocuments$1 : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::recursivelyReplaceImages → KILLED 2. stampSubDocuments : removed call to java/util/stream/Stream::forEach → KILLED |
.forEach(o -> recursivelyReplaceImages(o, replacements)); |
856 |
2
1. stampSubDocuments : removed call to java/util/List::forEach → SURVIVED 2. lambda$stampSubDocuments$2 : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::setParentIfPossible → SURVIVED |
os.forEach(c -> setParentIfPossible(c, gcp)); |
857 | changes.addAll(os); | |
858 | } | |
859 |
1
1. stampSubDocuments : replaced return value with Collections.emptyList for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::stampSubDocuments → KILLED |
return changes; |
860 | } | |
861 | ||
862 | private List<WordprocessingMLPackage> stampSubDocuments( | |
863 | List<Object> subContexts, WordprocessingMLPackage subTemplate | |
864 | ) { | |
865 | var subDocuments = new ArrayList<WordprocessingMLPackage>(); | |
866 | for (Object subContext : subContexts) { | |
867 |
1
1. lambda$stampSubDocuments$3 : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::copy → TIMED_OUT |
var templateCopy = outputWord(os -> copy(subTemplate, os)); |
868 |
1
1. lambda$stampSubDocuments$4 : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::stamp → TIMED_OUT |
var subDocument = outputWord(os -> stamp(subContext, templateCopy, os)); |
869 | subDocuments.add(subDocument); | |
870 | } | |
871 |
1
1. stampSubDocuments : replaced return value with Collections.emptyList for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::stampSubDocuments → KILLED |
return subDocuments; |
872 | } | |
873 | ||
874 | private static List<Object> documentAsInsertableElements( | |
875 | WordprocessingMLPackage subDocument, boolean oddNumberOfBreaks, @Nullable SectPr previousSectionBreak | |
876 | ) { | |
877 | List<Object> inserts = new ArrayList<>(DocumentUtil.allElements(subDocument)); | |
878 | // make sure we replicate the previous section break before each repeated doc part | |
879 |
2
1. documentAsInsertableElements : negated conditional → KILLED 2. documentAsInsertableElements : negated conditional → KILLED |
if (oddNumberOfBreaks && previousSectionBreak != null) { |
880 |
1
1. documentAsInsertableElements : negated conditional → KILLED |
if (DocumentUtil.lastElement(subDocument) instanceof P p) { |
881 |
1
1. documentAsInsertableElements : removed call to pro/verron/officestamper/core/SectionUtil::applySectionBreakToParagraph → KILLED |
SectionUtil.applySectionBreakToParagraph(previousSectionBreak, p); |
882 | } | |
883 | else { | |
884 | // when the last element to be repeated is not a paragraph, we need to add a new | |
885 | // one right after to carry the section break to have a valid xml | |
886 | P p = objectFactory.createP(); | |
887 |
1
1. documentAsInsertableElements : removed call to pro/verron/officestamper/core/SectionUtil::applySectionBreakToParagraph → KILLED |
SectionUtil.applySectionBreakToParagraph(previousSectionBreak, p); |
888 | inserts.add(p); | |
889 | } | |
890 | } | |
891 |
1
1. documentAsInsertableElements : replaced return value with Collections.emptyList for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor::documentAsInsertableElements → KILLED |
return inserts; |
892 | } | |
893 | ||
894 | private static void recursivelyReplaceImages( | |
895 | ContentAccessor r, Map<R, R> replacements | |
896 | ) { | |
897 | Queue<ContentAccessor> q = new ArrayDeque<>(); | |
898 | q.add(r); | |
899 |
1
1. recursivelyReplaceImages : negated conditional → KILLED |
while (!q.isEmpty()) { |
900 | ContentAccessor run = q.remove(); | |
901 |
2
1. recursivelyReplaceImages : negated conditional → KILLED 2. recursivelyReplaceImages : negated conditional → KILLED |
if (replacements.containsKey(run) && run instanceof Child child |
902 |
1
1. recursivelyReplaceImages : negated conditional → KILLED |
&& child.getParent() instanceof ContentAccessor parent) { |
903 | List<Object> parentContent = parent.getContent(); | |
904 |
1
1. recursivelyReplaceImages : removed call to java/util/List::add → KILLED |
parentContent.add(parentContent.indexOf(run), replacements.get(run)); |
905 | parentContent.remove(run); | |
906 | } | |
907 | else { | |
908 | q.addAll(run.getContent() | |
909 | .stream() | |
910 | .filter(ContentAccessor.class::isInstance) | |
911 | .map(ContentAccessor.class::cast) | |
912 | .toList()); | |
913 | } | |
914 | } | |
915 | } | |
916 | ||
917 | private static void setParentIfPossible( | |
918 | Object object, ContentAccessor parent | |
919 | ) { | |
920 |
2
1. setParentIfPossible : removed call to org/jvnet/jaxb2_commons/ppp/Child::setParent → SURVIVED 2. setParentIfPossible : negated conditional → KILLED |
if (object instanceof Child child) child.setParent(parent); |
921 | } | |
922 | ||
923 | private WordprocessingMLPackage outputWord(Consumer<OutputStream> outputter) { | |
924 | var exceptionHandler = new ProcessorExceptionHandler(); | |
925 | try ( | |
926 | PipedOutputStream os = new PipedOutputStream(); PipedInputStream is = new PipedInputStream(os) | |
927 | ) { | |
928 | // closing on exception to not block the pipe infinitely | |
929 | // TODO_LATER: model both PipedxxxStream as 1 class for only 1 close() | |
930 |
1
1. outputWord : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor$ProcessorExceptionHandler::onException → SURVIVED |
exceptionHandler.onException(is::close); // I know it's redundant, |
931 |
1
1. outputWord : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor$ProcessorExceptionHandler::onException → TIMED_OUT |
exceptionHandler.onException(os::close); // but symmetry |
932 | ||
933 |
1
1. lambda$outputWord$5 : removed call to java/util/function/Consumer::accept → TIMED_OUT |
var thread = threadFactory.newThread(() -> outputter.accept(os)); |
934 |
1
1. outputWord : removed call to java/lang/Thread::setUncaughtExceptionHandler → TIMED_OUT |
thread.setUncaughtExceptionHandler(exceptionHandler); |
935 |
1
1. outputWord : removed call to java/lang/Thread::start → TIMED_OUT |
thread.start(); |
936 | var wordprocessingMLPackage = WordprocessingMLPackage.load(is); | |
937 |
1
1. outputWord : removed call to java/lang/Thread::join → SURVIVED |
thread.join(); |
938 | return wordprocessingMLPackage; | |
939 | } catch (Docx4JException | IOException e) { | |
940 | OfficeStamperException exception = new OfficeStamperException(e); | |
941 | exceptionHandler.exception() | |
942 |
1
1. outputWord : removed call to java/util/Optional::ifPresent → KILLED |
.ifPresent(exception::addSuppressed); |
943 | throw exception; | |
944 | } catch (InterruptedException e) { | |
945 | OfficeStamperException exception = new OfficeStamperException(e); | |
946 | exceptionHandler.exception() | |
947 |
1
1. outputWord : removed call to java/util/Optional::ifPresent → NO_COVERAGE |
.ifPresent(e::addSuppressed); |
948 | Thread.currentThread() | |
949 |
1
1. outputWord : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
.interrupt(); |
950 | throw exception; | |
951 | } | |
952 | } | |
953 | ||
954 | private void copy( | |
955 | WordprocessingMLPackage aPackage, OutputStream outputStream | |
956 | ) { | |
957 | try { | |
958 |
1
1. copy : removed call to org/docx4j/openpackaging/packages/WordprocessingMLPackage::save → TIMED_OUT |
aPackage.save(outputStream); |
959 | } catch (Docx4JException e) { | |
960 | throw new OfficeStamperException(e); | |
961 | } | |
962 | } | |
963 | ||
964 | private void stamp( | |
965 | Object context, WordprocessingMLPackage template, OutputStream outputStream | |
966 | ) { | |
967 |
1
1. stamp : removed call to pro/verron/officestamper/api/OfficeStamper::stamp → TIMED_OUT |
stamper.stamp(template, context, outputStream); |
968 | } | |
969 | ||
970 | /** | |
971 | * {@inheritDoc} | |
972 | */ | |
973 | @Override public void reset() { | |
974 |
1
1. reset : removed call to java/util/Map::clear → SURVIVED |
contexts.clear(); |
975 | } | |
976 | ||
977 | /** | |
978 | * A functional interface representing runnable task able to throw an exception. | |
979 | * It extends the {@link Runnable} interface and provides default implementation | |
980 | * of the {@link Runnable#run()} method handling the exception by rethrowing it | |
981 | * wrapped inside a {@link OfficeStamperException}. | |
982 | * | |
983 | * @author Joseph Verron | |
984 | * @version ${version} | |
985 | * @since 1.6.6 | |
986 | */ | |
987 | interface ThrowingRunnable | |
988 | extends Runnable { | |
989 | ||
990 | /** | |
991 | * Executes the runnable task, handling any exception by throwing it wrapped | |
992 | * inside a {@link OfficeStamperException}. | |
993 | */ | |
994 | default void run() { | |
995 | try { | |
996 |
1
1. run : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor$ThrowingRunnable::throwingRun → TIMED_OUT |
throwingRun(); |
997 | } catch (Exception e) { | |
998 | throw new OfficeStamperException(e); | |
999 | } | |
1000 | } | |
1001 | ||
1002 | /** | |
1003 | * Executes the runnable task | |
1004 | * | |
1005 | * @throws Exception if an exception occurs executing the task | |
1006 | */ | |
1007 | void throwingRun() | |
1008 | throws Exception; | |
1009 | } | |
1010 | ||
1011 | /** | |
1012 | * This class is responsible for capturing and handling uncaught exceptions | |
1013 | * that occur in a thread. | |
1014 | * It implements the {@link Thread.UncaughtExceptionHandler} interface and can | |
1015 | * be assigned to a thread using the | |
1016 | * {@link Thread#setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)} method. | |
1017 | * When an exception occurs in the thread, | |
1018 | * the {@link ProcessorExceptionHandler#uncaughtException(Thread, Throwable)} | |
1019 | * method will be called. | |
1020 | * This class provides the following features: | |
1021 | * 1. Capturing and storing the uncaught exception. | |
1022 | * 2. Executing a list of routines when an exception occurs. | |
1023 | * 3. Providing access to the captured exception, if any. | |
1024 | * Example usage: | |
1025 | * <code> | |
1026 | * ProcessorExceptionHandler exceptionHandler = new | |
1027 | * ProcessorExceptionHandler(){}; | |
1028 | * thread.setUncaughtExceptionHandler(exceptionHandler); | |
1029 | * </code> | |
1030 | * | |
1031 | * @author Joseph Verron | |
1032 | * @version ${version} | |
1033 | * @see Thread.UncaughtExceptionHandler | |
1034 | * @since 1.6.6 | |
1035 | */ | |
1036 | static class ProcessorExceptionHandler | |
1037 | implements Thread.UncaughtExceptionHandler { | |
1038 | private final AtomicReference<Throwable> exception; | |
1039 | private final List<Runnable> onException; | |
1040 | ||
1041 | /** | |
1042 | * Constructs a new instance for managing thread's uncaught exceptions. | |
1043 | * Once set to a thread, it retains the exception information and performs specified routines. | |
1044 | */ | |
1045 | public ProcessorExceptionHandler() { | |
1046 | this.exception = new AtomicReference<>(); | |
1047 | this.onException = new CopyOnWriteArrayList<>(); | |
1048 | } | |
1049 | ||
1050 | /** | |
1051 | * {@inheritDoc} | |
1052 | * <p> | |
1053 | * Captures and stores an uncaught exception from a thread run | |
1054 | * and executes all defined routines on occurrence of the exception. | |
1055 | */ | |
1056 | @Override public void uncaughtException(Thread t, Throwable e) { | |
1057 |
1
1. uncaughtException : removed call to java/util/concurrent/atomic/AtomicReference::set → KILLED |
exception.set(e); |
1058 |
1
1. uncaughtException : removed call to java/util/List::forEach → TIMED_OUT |
onException.forEach(Runnable::run); |
1059 | } | |
1060 | ||
1061 | /** | |
1062 | * Adds a routine to the list of routines that should be run | |
1063 | * when an exception occurs. | |
1064 | * | |
1065 | * @param runnable The runnable routine to be added | |
1066 | */ | |
1067 | public void onException(ThrowingRunnable runnable) { | |
1068 | onException.add(runnable); | |
1069 | } | |
1070 | ||
1071 | /** | |
1072 | * Returns the captured exception if present. | |
1073 | * | |
1074 | * @return an {@link Optional} containing the captured exception, | |
1075 | * or an {@link Optional#empty()} if no exception was captured | |
1076 | */ | |
1077 | public Optional<Throwable> exception() { | |
1078 |
1
1. exception : replaced return value with Optional.empty for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatDocPartProcessor$ProcessorExceptionHandler::exception → KILLED |
return Optional.ofNullable(exception.get()); |
1079 | } | |
1080 | } | |
1081 | } | |
1082 | ||
1083 | /** | |
1084 | * Repeats a table row for each element in a list. | |
1085 | * | |
1086 | * @author Joseph Verron | |
1087 | * @author Tom Hombergs | |
1088 | * @version ${version} | |
1089 | * @since 1.0.0 | |
1090 | */ | |
1091 | private static class RepeatProcessor | |
1092 | extends AbstractCommentProcessor | |
1093 | implements IRepeatProcessor { | |
1094 | ||
1095 | private final BiFunction<WordprocessingMLPackage, Tr, List<Tr>> nullSupplier; | |
1096 | private Map<Tr, List<Object>> tableRowsToRepeat = new HashMap<>(); | |
1097 | private Map<Tr, Comment> tableRowsCommentsToRemove = new HashMap<>(); | |
1098 | ||
1099 | private RepeatProcessor( | |
1100 | ParagraphPlaceholderReplacer placeholderReplacer, | |
1101 | BiFunction<WordprocessingMLPackage, Tr, List<Tr>> nullSupplier1 | |
1102 | ) { | |
1103 | super(placeholderReplacer); | |
1104 | nullSupplier = nullSupplier1; | |
1105 | } | |
1106 | ||
1107 | /** | |
1108 | * Creates a new RepeatProcessor. | |
1109 | * | |
1110 | * @param pr The PlaceholderReplacer to use. | |
1111 | * | |
1112 | * @return A new RepeatProcessor. | |
1113 | */ | |
1114 | public static CommentProcessor newInstance(ParagraphPlaceholderReplacer pr) { | |
1115 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$RepeatProcessor::newInstance → KILLED |
return new RepeatProcessor(pr, (document, row) -> emptyList()); |
1116 | } | |
1117 | ||
1118 | /** {@inheritDoc} */ | |
1119 | @Override public void commitChanges(DocxPart source) { | |
1120 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$RepeatProcessor::repeatRows → KILLED |
repeatRows(source); |
1121 | } | |
1122 | ||
1123 | private void repeatRows(DocxPart source) { | |
1124 | for (Map.Entry<Tr, List<Object>> entry : tableRowsToRepeat.entrySet()) { | |
1125 | Tr row = entry.getKey(); | |
1126 | List<Object> expressionContexts = entry.getValue(); | |
1127 | ||
1128 | Tbl table = (Tbl) XmlUtils.unwrap(row.getParent()); | |
1129 | var content = table.getContent(); | |
1130 | int index = content.indexOf(row); | |
1131 | content.remove(row); | |
1132 | ||
1133 | List<Tr> changes; | |
1134 |
1
1. repeatRows : negated conditional → KILLED |
if (expressionContexts == null) { |
1135 | changes = nullSupplier.apply(source.document(), row); | |
1136 | } | |
1137 | else { | |
1138 | changes = new ArrayList<>(); | |
1139 | for (Object expressionContext : expressionContexts) { | |
1140 | Tr rowClone = XmlUtils.deepCopy(row); | |
1141 | Comment commentWrapper = requireNonNull(tableRowsCommentsToRemove.get(row)); | |
1142 | Comments.Comment comment = requireNonNull(commentWrapper.getComment()); | |
1143 | BigInteger commentId = comment.getId(); | |
1144 |
1
1. repeatRows : removed call to pro/verron/officestamper/core/CommentUtil::deleteCommentFromElements → KILLED |
CommentUtil.deleteCommentFromElements(rowClone.getContent(), commentId); |
1145 | new ParagraphResolverDocumentWalker(source, | |
1146 | rowClone, | |
1147 | expressionContext, | |
1148 |
1
1. repeatRows : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$ParagraphResolverDocumentWalker::walk → KILLED |
this.placeholderReplacer).walk(); |
1149 | changes.add(rowClone); | |
1150 | } | |
1151 | } | |
1152 | content.addAll(index, changes); | |
1153 | } | |
1154 | } | |
1155 | ||
1156 | /** {@inheritDoc} */ | |
1157 | @Override public void reset() { | |
1158 | this.tableRowsToRepeat = new HashMap<>(); | |
1159 | this.tableRowsCommentsToRemove = new HashMap<>(); | |
1160 | } | |
1161 | ||
1162 | /** {@inheritDoc} */ | |
1163 | @Override public void repeatTableRow(List<Object> objects) { | |
1164 | var row = parentRow(getParagraph()); | |
1165 | tableRowsToRepeat.put(row, objects); | |
1166 | tableRowsCommentsToRemove.put(row, getCurrentCommentWrapper()); | |
1167 | } | |
1168 | } | |
1169 | ||
1170 | /** | |
1171 | * Processor for the {@link IDisplayIfProcessor} comment. | |
1172 | * | |
1173 | * @author Joseph Verron | |
1174 | * @author Tom Hombergs | |
1175 | * @version ${version} | |
1176 | * @since 1.0.0 | |
1177 | */ | |
1178 | private static class DisplayIfProcessor | |
1179 | extends AbstractCommentProcessor | |
1180 | implements IDisplayIfProcessor { | |
1181 | ||
1182 | private List<P> paragraphsToBeRemoved = new ArrayList<>(); | |
1183 | private List<Tbl> tablesToBeRemoved = new ArrayList<>(); | |
1184 | private List<Tr> tableRowsToBeRemoved = new ArrayList<>(); | |
1185 | ||
1186 | private DisplayIfProcessor(ParagraphPlaceholderReplacer placeholderReplacer) { | |
1187 | super(placeholderReplacer); | |
1188 | } | |
1189 | ||
1190 | /** | |
1191 | * Creates a new DisplayIfProcessor instance. | |
1192 | * | |
1193 | * @param pr the {@link PlaceholderReplacer} used for replacing expressions. | |
1194 | * | |
1195 | * @return a new DisplayIfProcessor instance. | |
1196 | */ | |
1197 | public static CommentProcessor newInstance(ParagraphPlaceholderReplacer pr) { | |
1198 |
1
1. newInstance : replaced return value with null for pro/verron/officestamper/preset/CommentProcessorFactory$DisplayIfProcessor::newInstance → KILLED |
return new DisplayIfProcessor(pr); |
1199 | } | |
1200 | ||
1201 | /** {@inheritDoc} */ | |
1202 | @Override public void commitChanges(DocxPart source) { | |
1203 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$DisplayIfProcessor::removeParagraphs → KILLED |
removeParagraphs(); |
1204 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$DisplayIfProcessor::removeTables → KILLED |
removeTables(); |
1205 |
1
1. commitChanges : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$DisplayIfProcessor::removeTableRows → KILLED |
removeTableRows(); |
1206 | } | |
1207 | ||
1208 | private void removeParagraphs() { | |
1209 | for (P p : paragraphsToBeRemoved) { | |
1210 |
1
1. removeParagraphs : removed call to pro/verron/officestamper/core/ObjectDeleter::deleteParagraph → KILLED |
ObjectDeleter.deleteParagraph(p); |
1211 | } | |
1212 | } | |
1213 | ||
1214 | private void removeTables() { | |
1215 | for (Tbl table : tablesToBeRemoved) { | |
1216 |
1
1. removeTables : removed call to pro/verron/officestamper/core/ObjectDeleter::deleteTable → KILLED |
ObjectDeleter.deleteTable(table); |
1217 | } | |
1218 | } | |
1219 | ||
1220 | private void removeTableRows() { | |
1221 | for (Tr row : tableRowsToBeRemoved) { | |
1222 |
1
1. removeTableRows : removed call to pro/verron/officestamper/core/ObjectDeleter::deleteTableRow → KILLED |
ObjectDeleter.deleteTableRow(row); |
1223 | } | |
1224 | } | |
1225 | ||
1226 | /** {@inheritDoc} */ | |
1227 | @Override public void reset() { | |
1228 | paragraphsToBeRemoved = new ArrayList<>(); | |
1229 | tablesToBeRemoved = new ArrayList<>(); | |
1230 | tableRowsToBeRemoved = new ArrayList<>(); | |
1231 | } | |
1232 | ||
1233 | /** {@inheritDoc} */ | |
1234 | @Override public void displayParagraphIf(Boolean condition) { | |
1235 |
1
1. displayParagraphIf : negated conditional → KILLED |
if (Boolean.TRUE.equals(condition)) return; |
1236 | paragraphsToBeRemoved.add(getParagraph()); | |
1237 | } | |
1238 | ||
1239 | /** {@inheritDoc} */ | |
1240 | @Override public void displayParagraphIfPresent(@Nullable Object condition) { | |
1241 |
2
1. displayParagraphIfPresent : removed call to pro/verron/officestamper/preset/CommentProcessorFactory$DisplayIfProcessor::displayParagraphIf → SURVIVED 2. displayParagraphIfPresent : negated conditional → KILLED |
displayParagraphIf(condition != null); |
1242 | } | |
1243 | ||
1244 | /** {@inheritDoc} */ | |
1245 | @Override public void displayTableRowIf(Boolean condition) { | |
1246 |
1
1. displayTableRowIf : negated conditional → KILLED |
if (Boolean.TRUE.equals(condition)) return; |
1247 | P p = getParagraph(); | |
1248 | var tr = parentRow(p); | |
1249 | tableRowsToBeRemoved.add(tr); | |
1250 | } | |
1251 | ||
1252 | /** {@inheritDoc} */ | |
1253 | @Override public void displayTableIf(Boolean condition) { | |
1254 |
1
1. displayTableIf : negated conditional → KILLED |
if (Boolean.TRUE.equals(condition)) return; |
1255 | tablesToBeRemoved.add(parentTable(getParagraph())); | |
1256 | } | |
1257 | } | |
1258 | } | |
Mutations | ||
59 |
1.1 |
|
60 |
1.1 |
|
65 |
1.1 |
|
66 |
1.1 |
|
71 |
1.1 |
|
72 |
1.1 |
|
84 |
1.1 |
|
95 |
1.1 |
|
99 |
1.1 2.2 |
|
110 |
1.1 |
|
121 |
1.1 |
|
132 |
1.1 |
|
143 |
1.1 |
|
336 |
1.1 |
|
356 |
1.1 |
|
357 |
1.1 |
|
376 |
1.1 |
|
386 |
1.1 |
|
388 |
1.1 |
|
390 |
1.1 |
|
402 |
1.1 2.2 |
|
404 |
1.1 2.2 |
|
408 |
1.1 |
|
417 |
1.1 2.2 |
|
420 |
1.1 |
|
422 |
1.1 |
|
427 |
1.1 |
|
462 |
1.1 |
|
485 |
1.1 |
|
491 |
1.1 |
|
498 |
1.1 |
|
541 |
1.1 |
|
562 |
1.1 2.2 |
|
564 |
1.1 |
|
586 |
1.1 |
|
587 |
1.1 2.2 |
|
589 |
1.1 2.2 3.3 |
|
592 |
1.1 |
|
595 |
1.1 2.2 3.3 4.4 |
|
597 |
1.1 2.2 |
|
599 |
1.1 |
|
602 |
1.1 |
|
604 |
1.1 2.2 |
|
613 |
1.1 |
|
626 |
1.1 2.2 |
|
631 |
1.1 |
|
634 |
1.1 |
|
654 |
1.1 2.2 3.3 4.4 |
|
657 |
1.1 |
|
664 |
1.1 |
|
666 |
1.1 |
|
670 |
1.1 |
|
676 |
1.1 |
|
678 |
1.1 |
|
742 |
1.1 |
|
788 |
1.1 |
|
795 |
1.1 |
|
800 |
1.1 |
|
817 |
1.1 |
|
844 |
1.1 |
|
855 |
1.1 2.2 |
|
856 |
1.1 2.2 |
|
859 |
1.1 |
|
867 |
1.1 |
|
868 |
1.1 |
|
871 |
1.1 |
|
879 |
1.1 2.2 |
|
880 |
1.1 |
|
881 |
1.1 |
|
887 |
1.1 |
|
891 |
1.1 |
|
899 |
1.1 |
|
901 |
1.1 2.2 |
|
902 |
1.1 |
|
904 |
1.1 |
|
920 |
1.1 2.2 |
|
930 |
1.1 |
|
931 |
1.1 |
|
933 |
1.1 |
|
934 |
1.1 |
|
935 |
1.1 |
|
937 |
1.1 |
|
942 |
1.1 |
|
947 |
1.1 |
|
949 |
1.1 |
|
958 |
1.1 |
|
967 |
1.1 |
|
974 |
1.1 |
|
996 |
1.1 |
|
1057 |
1.1 |
|
1058 |
1.1 |
|
1078 |
1.1 |
|
1115 |
1.1 |
|
1120 |
1.1 |
|
1134 |
1.1 |
|
1144 |
1.1 |
|
1148 |
1.1 |
|
1198 |
1.1 |
|
1203 |
1.1 |
|
1204 |
1.1 |
|
1205 |
1.1 |
|
1210 |
1.1 |
|
1216 |
1.1 |
|
1222 |
1.1 |
|
1235 |
1.1 |
|
1241 |
1.1 2.2 |
|
1246 |
1.1 |
|
1254 |
1.1 |