| 1 | package pro.verron.officestamper.preset.processors.repeat; | |
| 2 | ||
| 3 | import org.docx4j.XmlUtils; | |
| 4 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
| 5 | import org.docx4j.wml.ContentAccessor; | |
| 6 | import org.docx4j.wml.P; | |
| 7 | import org.docx4j.wml.PPr; | |
| 8 | import org.docx4j.wml.SectPr; | |
| 9 | import org.jspecify.annotations.Nullable; | |
| 10 | import org.jvnet.jaxb2_commons.ppp.Child; | |
| 11 | import org.slf4j.Logger; | |
| 12 | import org.slf4j.LoggerFactory; | |
| 13 | import pro.verron.officestamper.api.*; | |
| 14 | import pro.verron.officestamper.preset.CommentProcessorFactory.IRepeatProcessor; | |
| 15 | import pro.verron.officestamper.utils.wml.WmlFactory; | |
| 16 | import pro.verron.officestamper.utils.wml.WmlUtils; | |
| 17 | ||
| 18 | import java.util.ArrayList; | |
| 19 | import java.util.List; | |
| 20 | import java.util.Objects; | |
| 21 | import java.util.Optional; | |
| 22 | import java.util.function.Supplier; | |
| 23 | ||
| 24 | import static java.util.Optional.ofNullable; | |
| 25 | import static java.util.stream.Collectors.toCollection; | |
| 26 | ||
| 27 | public class RepeatProcessor | |
| 28 | extends CommentProcessor | |
| 29 | implements IRepeatProcessor { | |
| 30 | private static final Logger log = LoggerFactory.getLogger(RepeatProcessor.class); | |
| 31 | ||
| 32 | /// Constructs a new instance of CommentProcessor to process comments and placeholders within a paragraph. | |
| 33 | /// | |
| 34 | /// @param context the context containing the paragraph, comment, and placeholder associated with the | |
| 35 | /// processing of this CommentProcessor. | |
| 36 | public RepeatProcessor(ProcessorContext context) { | |
| 37 | super(context); | |
| 38 | } | |
| 39 | ||
| 40 | @Override | |
| 41 | public void repeat(@Nullable Iterable<Object> items) { | |
| 42 |
1
1. repeat : negated conditional → KILLED |
if (items == null) return; |
| 43 | var comment = context().comment(); | |
| 44 | var elements = comment.getElements(); | |
| 45 | var contextHolder = context().contextHolder(); | |
| 46 | var parent = comment.getParent(); | |
| 47 | var siblings = parent.getContent(); | |
| 48 | var firstElement = elements.getFirst(); | |
| 49 | var previousSectionBreak = previousSectionBreak(firstElement, parent).orElse(documentSection(context().part())); | |
| 50 | var index = siblings.indexOf(firstElement); | |
| 51 | siblings.removeAll(elements); | |
| 52 | var iterator = items.iterator(); | |
| 53 | // Iterates items; copies elements; conditionally adds section break; adds elements | |
| 54 |
1
1. repeat : negated conditional → KILLED |
while (iterator.hasNext()) { |
| 55 | var item = iterator.next(); | |
| 56 | var copiedElements = elements.stream() | |
| 57 | .map(XmlUtils::deepCopy) | |
| 58 | .collect(toCollection(ArrayList::new)); | |
| 59 |
1
1. repeat : removed call to pro/verron/officestamper/utils/wml/WmlUtils::deleteCommentFromElements → TIMED_OUT |
WmlUtils.deleteCommentFromElements(comment.getId(), copiedElements); |
| 60 | // Adds section break to last paragraph if needed | |
| 61 |
2
1. repeat : negated conditional → TIMED_OUT 2. repeat : negated conditional → KILLED |
if (iterator.hasNext() && containsSectionBreaks(copiedElements)) { |
| 62 | var lastParagraph = lastParagraph(copiedElements).orElseGet(newEndParagraph(copiedElements)); | |
| 63 |
1
1. repeat : negated conditional → KILLED |
if (!hasSectionBreak(lastParagraph)) { |
| 64 |
1
1. repeat : removed call to pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::addSectionBreak → KILLED |
addSectionBreak(previousSectionBreak, lastParagraph); |
| 65 | } | |
| 66 | } | |
| 67 | siblings.addAll(index, copiedElements); | |
| 68 |
1
1. repeat : Replaced integer addition with subtraction → KILLED |
index += copiedElements.size(); |
| 69 |
3
1. repeat : removed call to java/util/ArrayList::forEach → TIMED_OUT 2. lambda$repeat$0 : removed call to org/jvnet/jaxb2_commons/ppp/Child::setParent → TIMED_OUT 3. lambda$repeat$0 : negated conditional → KILLED |
copiedElements.forEach(element -> {if (element instanceof Child child) child.setParent(parent);}); |
| 70 | var subContextKey = contextHolder.addBranch(item); | |
| 71 |
1
1. lambda$repeat$1 : replaced return value with Collections.emptyList for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::lambda$repeat$1 → KILLED |
Hooks.ofHooks(() -> copiedElements) |
| 72 |
2
1. repeat : removed call to pro/verron/officestamper/utils/iterator/ResetableIterator::forEachRemaining → KILLED 2. lambda$repeat$2 : removed call to pro/verron/officestamper/api/Hook::setContextKey → KILLED |
.forEachRemaining(hook -> hook.setContextKey(subContextKey)); |
| 73 | } | |
| 74 | } | |
| 75 | ||
| 76 | private static Optional<SectPr> previousSectionBreak(Object firstObject, ContentAccessor parent) { | |
| 77 | List<Object> parentContent = parent.getContent(); | |
| 78 | int pIndex = parentContent.indexOf(firstObject); | |
| 79 | ||
| 80 |
1
1. previousSectionBreak : Replaced integer subtraction with addition → TIMED_OUT |
int i = pIndex - 1; |
| 81 |
2
1. previousSectionBreak : changed conditional boundary → TIMED_OUT 2. previousSectionBreak : negated conditional → KILLED |
while (i >= 0) { |
| 82 |
1
1. previousSectionBreak : negated conditional → TIMED_OUT |
if (parentContent.get(i) instanceof P prevParagraph) { |
| 83 | // the first P preceding the object is the one carrying a section break | |
| 84 |
1
1. previousSectionBreak : replaced return value with Optional.empty for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::previousSectionBreak → TIMED_OUT |
return ofNullable(prevParagraph.getPPr()).map(PPr::getSectPr); |
| 85 | } | |
| 86 | else log.debug("The previous sibling was not a P, continuing search"); | |
| 87 | i--; | |
| 88 | } | |
| 89 | log.info("No previous section break found from : {}, first object index={}", parent, pIndex); | |
| 90 | return Optional.empty(); | |
| 91 | } | |
| 92 | ||
| 93 | private static SectPr documentSection(DocxPart part) { | |
| 94 | try { | |
| 95 |
1
1. documentSection : replaced return value with null for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::documentSection → TIMED_OUT |
return part.document() |
| 96 | .getMainDocumentPart() | |
| 97 | .getContents() | |
| 98 | .getBody() | |
| 99 | .getSectPr(); | |
| 100 | } catch (Docx4JException e) { | |
| 101 | throw new OfficeStamperException(e); | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | private static boolean containsSectionBreaks(ArrayList<Object> elements) { | |
| 106 |
2
1. containsSectionBreaks : replaced boolean return with false for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::containsSectionBreaks → TIMED_OUT 2. containsSectionBreaks : replaced boolean return with true for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::containsSectionBreaks → KILLED |
return elements.stream() |
| 107 | .filter(P.class::isInstance) | |
| 108 | .map(P.class::cast) | |
| 109 | .map(P::getPPr) | |
| 110 | .filter(Objects::nonNull) | |
| 111 | .map(PPr::getSectPr) | |
| 112 | .anyMatch(Objects::nonNull); | |
| 113 | } | |
| 114 | ||
| 115 | private static Optional<P> lastParagraph(List<Object> elements) { | |
| 116 |
2
1. lastParagraph : replaced return value with Optional.empty for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::lastParagraph → KILLED 2. lastParagraph : negated conditional → KILLED |
if (elements.getLast() instanceof P paragraph) return Optional.of(paragraph); |
| 117 | else return Optional.empty(); | |
| 118 | } | |
| 119 | ||
| 120 | private static Supplier<P> newEndParagraph(ArrayList<Object> copiedElements) { | |
| 121 |
1
1. newEndParagraph : replaced return value with null for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::newEndParagraph → KILLED |
return () -> { |
| 122 | var p = WmlFactory.newParagraph(); | |
| 123 |
1
1. lambda$newEndParagraph$0 : removed call to java/util/ArrayList::addLast → KILLED |
copiedElements.addLast(p); |
| 124 |
1
1. lambda$newEndParagraph$0 : replaced return value with null for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::lambda$newEndParagraph$0 → KILLED |
return p; |
| 125 | }; | |
| 126 | } | |
| 127 | ||
| 128 | private static boolean hasSectionBreak(P lastParagraph) { | |
| 129 | PPr pPr = lastParagraph.getPPr(); | |
| 130 |
2
1. hasSectionBreak : replaced boolean return with true for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::hasSectionBreak → KILLED 2. hasSectionBreak : negated conditional → KILLED |
if (pPr == null) return false; |
| 131 | SectPr sectPr = pPr.getSectPr(); | |
| 132 |
2
1. hasSectionBreak : negated conditional → NO_COVERAGE 2. hasSectionBreak : replaced boolean return with true for pro/verron/officestamper/preset/processors/repeat/RepeatProcessor::hasSectionBreak → NO_COVERAGE |
return sectPr != null; |
| 133 | } | |
| 134 | ||
| 135 | private static void addSectionBreak(SectPr sectPr, P paragraph) { | |
| 136 | PPr nextPPr = ofNullable(paragraph.getPPr()).orElseGet(WmlFactory::newPPr); | |
| 137 |
1
1. addSectionBreak : removed call to org/docx4j/wml/PPr::setSectPr → KILLED |
nextPPr.setSectPr(XmlUtils.deepCopy(sectPr)); |
| 138 |
1
1. addSectionBreak : removed call to org/docx4j/wml/P::setPPr → KILLED |
paragraph.setPPr(nextPPr); |
| 139 | } | |
| 140 | } | |
Mutations | ||
| 42 |
1.1 |
|
| 54 |
1.1 |
|
| 59 |
1.1 |
|
| 61 |
1.1 2.2 |
|
| 63 |
1.1 |
|
| 64 |
1.1 |
|
| 68 |
1.1 |
|
| 69 |
1.1 2.2 3.3 |
|
| 71 |
1.1 |
|
| 72 |
1.1 2.2 |
|
| 80 |
1.1 |
|
| 81 |
1.1 2.2 |
|
| 82 |
1.1 |
|
| 84 |
1.1 |
|
| 95 |
1.1 |
|
| 106 |
1.1 2.2 |
|
| 116 |
1.1 2.2 |
|
| 121 |
1.1 |
|
| 123 |
1.1 |
|
| 124 |
1.1 |
|
| 130 |
1.1 2.2 |
|
| 132 |
1.1 2.2 |
|
| 137 |
1.1 |
|
| 138 |
1.1 |