OpenPackage.java

1
package pro.verron.officestamper.utils.openpackaging;
2
3
import org.docx4j.openpackaging.contenttype.ContentTypes;
4
import org.docx4j.openpackaging.packages.OpcPackage;
5
import org.docx4j.openpackaging.parts.DefaultXmlPart;
6
import org.docx4j.openpackaging.parts.Part;
7
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
8
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
9
import pro.verron.officestamper.utils.UtilsException;
10
import pro.verron.officestamper.utils.image.ImgPart;
11
import pro.verron.officestamper.utils.svg.SvgUtils;
12
13
import java.util.Arrays;
14
import java.util.Map;
15
import java.util.Optional;
16
import java.util.concurrent.ConcurrentHashMap;
17
import java.util.function.Supplier;
18
19
import static org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage.createImageName;
20
import static pro.verron.officestamper.utils.UtilsException.supply;
21
import static pro.verron.officestamper.utils.image.ImgUtils.detectFormat;
22
import static pro.verron.officestamper.utils.image.ImgUtils.supportedType;
23
import static pro.verron.officestamper.utils.openpackaging.OpenpackagingFactory.setupRelationship;
24
import static pro.verron.officestamper.utils.openpackaging.OpenpackagingUtils.createSvgPart;
25
26
/// Represents an open package that holds a reference to an [OpcPackage]
27
/// document and
28
/// a specific part of the package. This class provides utility methods to work
29
/// with the
30
/// package, such as searching for image parts.
31
///
32
/// @param <T> the type of the [OpcPackage] being managed
33
public final class OpenPackage<T extends OpcPackage> {
34
    private static final Map<OpcPackage, Map<Part, OpenPackage>> pool =
35
            new ConcurrentHashMap<>();
36
    private final Map<Integer, Part> imgParts = new ConcurrentHashMap<>();
37
    private final T document;
38
    private final Part part;
39
40
    /// Constructs a new instance of OpenPackage with the specified document and
41
    /// part.
42
    ///
43
    /// @param document the document object associated with this package
44
    /// @param part     the [Part] object representing a specific part of the
45
    ///  document
46
    public OpenPackage(T document, Part part) {
47
        this.document = document;
48
        this.part = part;
49
        part.getPackage()
50
            .getParts()
51
            .getParts()
52
            .values()
53 1 1. <init> : removed call to java/util/Collection::forEach → NO_COVERAGE
            .forEach(this::hash);
54
    }
55
56
    private void hash(Part part) {
57
        switch (part) {
58
            case DefaultXmlPart xmlPart -> {
59
                var extractedXml = OpenpackagingUtils.extractXml(xmlPart);
60
                var hashCode = extractedXml.hashCode();
61
                imgParts.put(hashCode, xmlPart);
62
            }
63
            case BinaryPartAbstractImage imagePart -> {
64
                var extractedBytes = imagePart.getBytes();
65
                var hashCode = Arrays.hashCode(extractedBytes);
66
                imgParts.put(hashCode, imagePart);
67
            }
68
            default -> { /* DO NOTHING */ }
69
        }
70
    }
71
72
    /// Returns an existing [OpenPackage] for the given document and part, or
73
    /// creates a new one if none exists.
74
    ///
75
    /// @param document the [OpcPackage] document
76
    /// @param part     the [Part] within the document
77
    /// @param <T>      the type of the [OpcPackage]
78
    ///
79
    /// @return an [OpenPackage] for the specified document and part
80
    public static <T extends OpcPackage> OpenPackage<T> getOrCreate(
81
            T document,
82
            Part part
83
    ) {
84
        //noinspection unchecked because the pool system ensure types respect
85 2 1. lambda$getOrCreate$0 : replaced return value with Collections.emptyMap for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$getOrCreate$0 → NO_COVERAGE
2. getOrCreate : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::getOrCreate → NO_COVERAGE
        return pool.computeIfAbsent(document, d -> new ConcurrentHashMap<>())
86 1 1. lambda$getOrCreate$1 : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$getOrCreate$1 → NO_COVERAGE
                   .computeIfAbsent(part, p -> new OpenPackage<>(document, p));
87
    }
88
89
    /// Finds an existing image part in the package that matches the given byte
90
    /// data, or creates a new one if no matching part is found or deduplication
91
    /// is disabled.
92
    ///
93
    /// @param bytes       a supplier providing the byte array containing
94
    /// image data
95
    /// @param deduplicate a boolean flag indicating whether to deduplicate by
96
    /// checking for an existing image part
97
    ///
98
    /// @return the found or newly created `ImgPart` containing the detected
99
    /// image format and its relationship
100
    public ImgPart findOrCreateImgPart(
101
            Supplier<byte[]> bytes,
102
            boolean deduplicate
103
    ) {
104 1 1. findOrCreateImgPart : negated conditional → NO_COVERAGE
        if (deduplicate) {
105
            var foundImagePart = findImgPart(bytes.get());
106 2 1. findOrCreateImgPart : negated conditional → NO_COVERAGE
2. findOrCreateImgPart : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::findOrCreateImgPart → NO_COVERAGE
            if (foundImagePart.isPresent()) return foundImagePart.get();
107
        }
108 1 1. findOrCreateImgPart : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::findOrCreateImgPart → NO_COVERAGE
        return newImgPart(bytes.get());
109
    }
110
111
    private Optional<ImgPart> findImgPart(byte[] bytes) {
112 1 1. findImgPart : negated conditional → NO_COVERAGE
        if (bytes.length == 0) throw new UtilsException(
113
                "Can't create image from empty byte " + "array");
114
        var format = detectFormat(bytes).orElseThrow(supply(
115
                "Could not detect a supported image type" + "."));
116
        var mimeType = supportedType(format.name()).orElseThrow(supply(
117
                "Unsupported image type:%s",
118
                format.name()));
119 1 1. findImgPart : removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::ensureHasRelationshipPart → NO_COVERAGE
        ensureHasRelationshipPart();
120
        var relationshipId = createRelationshipId();
121
        var ctm = document().getContentTypeManager();
122
        var isSvg = mimeType.equals(ContentTypes.IMAGE_SVG);
123 1 1. findImgPart : negated conditional → NO_COVERAGE
        if (isSvg) {
124
            var svgXml = OpenpackagingUtils.extractSvgXml(bytes, ctm);
125
            var svgXmlHashcode = svgXml.hashCode();
126 1 1. findImgPart : negated conditional → NO_COVERAGE
            if (imgParts.containsKey(svgXmlHashcode)) {
127
                var targetPart = imgParts.get(svgXmlHashcode);
128
                var relationship = setupRelationship(part,
129
                        targetPart,
130
                        relationshipId);
131 1 1. findImgPart : replaced return value with Optional.empty for pro/verron/officestamper/utils/openpackaging/OpenPackage::findImgPart → NO_COVERAGE
                return Optional.of(new ImgPart(format, relationship));
132
            }
133
        }
134
        else {
135
            var bytesHashcode = Arrays.hashCode(bytes);
136 1 1. findImgPart : negated conditional → NO_COVERAGE
            if (imgParts.containsKey(bytesHashcode)) {
137
                var targetPart = imgParts.get(bytesHashcode);
138
                var relationship = setupRelationship(part,
139
                        targetPart,
140
                        relationshipId);
141 1 1. findImgPart : replaced return value with Optional.empty for pro/verron/officestamper/utils/openpackaging/OpenPackage::findImgPart → NO_COVERAGE
                return Optional.of(new ImgPart(format, relationship));
142
            }
143
        }
144
        return Optional.empty();
145
    }
146
147
    private ImgPart newImgPart(byte[] bytes) {
148 1 1. newImgPart : negated conditional → NO_COVERAGE
        if (bytes.length == 0) throw new UtilsException(
149
                "Can't create image from empty byte " + "array");
150
151
        var optFormat = detectFormat(bytes);
152 1 1. lambda$newImgPart$0 : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$newImgPart$0 → NO_COVERAGE
        var format = optFormat.orElseThrow(() -> new UtilsException(
153
                "Could not detect a supported image type."));
154
155
        var optMimeType = supportedType(format.name());
156 1 1. lambda$newImgPart$1 : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$newImgPart$1 → NO_COVERAGE
        var mimeType = optMimeType.orElseThrow(() -> new UtilsException(
157
                "Unsupported image type"));
158
159 1 1. newImgPart : removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::ensureHasRelationshipPart → NO_COVERAGE
        ensureHasRelationshipPart();
160
        var relationshipId = createRelationshipId();
161
        var partName = createImageName(document(),
162
                part,
163
                relationshipId,
164
                format.name());
165
        var ctm = document().getContentTypeManager();
166
167
        Part imgPart;
168 1 1. newImgPart : negated conditional → NO_COVERAGE
        if (mimeType.equals(ContentTypes.IMAGE_SVG)) {
169
            var document = SvgUtils.parseDocument(bytes);
170
            imgPart = createSvgPart(ctm, document, partName);
171 1 1. newImgPart : removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::hash → NO_COVERAGE
            hash(imgPart);
172
        }
173
        else {
174
            imgPart = OpenpackagingFactory.createImagePart(ctm,
175
                    bytes,
176
                    mimeType,
177
                    partName);
178 1 1. newImgPart : removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::hash → NO_COVERAGE
            hash(imgPart);
179
        }
180
181
        var relationship = setupRelationship(part, imgPart, relationshipId);
182 1 1. newImgPart : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::newImgPart → NO_COVERAGE
        return new ImgPart(format, relationship);
183
    }
184
185
    private void ensureHasRelationshipPart() {
186 1 1. ensureHasRelationshipPart : negated conditional → NO_COVERAGE
        if (part.getRelationshipsPart() == null)
187
            RelationshipsPart.createRelationshipsPartForPart(part);
188
    }
189
190
    private String createRelationshipId() {
191
        var relationshipsPart = part.getRelationshipsPart();
192 1 1. createRelationshipId : replaced return value with "" for pro/verron/officestamper/utils/openpackaging/OpenPackage::createRelationshipId → NO_COVERAGE
        return relationshipsPart.getNextId();
193
    }
194
195
    /// Retrieves the document object associated with this package.
196
    ///
197
    /// @return the document object of type T
198 1 1. document : replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::document → NO_COVERAGE
    public T document() {return document;}
199
200
    @Override
201
    public String toString() {
202 1 1. toString : replaced return value with "" for pro/verron/officestamper/utils/openpackaging/OpenPackage::toString → NO_COVERAGE
        return "OpenPackage[document=%s, part=%s]".formatted(document, part);
203
    }
204
205
}

Mutations

53

1.1
Location : <init>
Killed by : none
removed call to java/util/Collection::forEach → NO_COVERAGE

85

1.1
Location : lambda$getOrCreate$0
Killed by : none
replaced return value with Collections.emptyMap for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$getOrCreate$0 → NO_COVERAGE

2.2
Location : getOrCreate
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::getOrCreate → NO_COVERAGE

86

1.1
Location : lambda$getOrCreate$1
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$getOrCreate$1 → NO_COVERAGE

104

1.1
Location : findOrCreateImgPart
Killed by : none
negated conditional → NO_COVERAGE

106

1.1
Location : findOrCreateImgPart
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : findOrCreateImgPart
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::findOrCreateImgPart → NO_COVERAGE

108

1.1
Location : findOrCreateImgPart
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::findOrCreateImgPart → NO_COVERAGE

112

1.1
Location : findImgPart
Killed by : none
negated conditional → NO_COVERAGE

119

1.1
Location : findImgPart
Killed by : none
removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::ensureHasRelationshipPart → NO_COVERAGE

123

1.1
Location : findImgPart
Killed by : none
negated conditional → NO_COVERAGE

126

1.1
Location : findImgPart
Killed by : none
negated conditional → NO_COVERAGE

131

1.1
Location : findImgPart
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/utils/openpackaging/OpenPackage::findImgPart → NO_COVERAGE

136

1.1
Location : findImgPart
Killed by : none
negated conditional → NO_COVERAGE

141

1.1
Location : findImgPart
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/utils/openpackaging/OpenPackage::findImgPart → NO_COVERAGE

148

1.1
Location : newImgPart
Killed by : none
negated conditional → NO_COVERAGE

152

1.1
Location : lambda$newImgPart$0
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$newImgPart$0 → NO_COVERAGE

156

1.1
Location : lambda$newImgPart$1
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::lambda$newImgPart$1 → NO_COVERAGE

159

1.1
Location : newImgPart
Killed by : none
removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::ensureHasRelationshipPart → NO_COVERAGE

168

1.1
Location : newImgPart
Killed by : none
negated conditional → NO_COVERAGE

171

1.1
Location : newImgPart
Killed by : none
removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::hash → NO_COVERAGE

178

1.1
Location : newImgPart
Killed by : none
removed call to pro/verron/officestamper/utils/openpackaging/OpenPackage::hash → NO_COVERAGE

182

1.1
Location : newImgPart
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::newImgPart → NO_COVERAGE

186

1.1
Location : ensureHasRelationshipPart
Killed by : none
negated conditional → NO_COVERAGE

192

1.1
Location : createRelationshipId
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/openpackaging/OpenPackage::createRelationshipId → NO_COVERAGE

198

1.1
Location : document
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/openpackaging/OpenPackage::document → NO_COVERAGE

202

1.1
Location : toString
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/openpackaging/OpenPackage::toString → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.25.5 support