Main.java

1
package pro.verron.officestamper;
2
3
4
import com.fasterxml.jackson.core.type.TypeReference;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import com.opencsv.CSVReader;
7
import com.opencsv.exceptions.CsvException;
8
import org.w3c.dom.Document;
9
import org.w3c.dom.Element;
10
import org.w3c.dom.Node;
11
import org.w3c.dom.NodeList;
12
import org.xml.sax.SAXException;
13
import picocli.CommandLine;
14
import picocli.CommandLine.Command;
15
import picocli.CommandLine.Option;
16
import pro.verron.officestamper.api.OfficeStamperException;
17
import pro.verron.officestamper.experimental.ExperimentalStampers;
18
import pro.verron.officestamper.preset.OfficeStampers;
19
20
import javax.xml.XMLConstants;
21
import javax.xml.parsers.DocumentBuilder;
22
import javax.xml.parsers.DocumentBuilderFactory;
23
import javax.xml.parsers.ParserConfigurationException;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.InputStreamReader;
27
import java.io.OutputStream;
28
import java.nio.file.Files;
29
import java.nio.file.Path;
30
import java.util.LinkedHashMap;
31
import java.util.Map;
32
import java.util.Properties;
33
import java.util.logging.Level;
34
import java.util.logging.Logger;
35
import java.util.stream.Collectors;
36
37
import static java.nio.file.Files.newOutputStream;
38
39
/// Main class for the CLI.
40
@Command(name = "officestamper", mixinStandardHelpOptions = true, description = "Office Stamper CLI tool")
41
public class Main
42
        implements Runnable {
43
44
    private static final Logger logger = Utils.getLogger();
45
    @Option(names = {"-i", "--input"},
46
            required = true,
47
            description = "Input file path (csv, properties, html, xml, json, excel) or a keyword (diagnostic) for "
48
                          + "documented data sources") private String inputFile;
49
    @Option(names = {"-t", "--template"},
50
            required = true,
51
            description = "Template file path or a keyword (diagnostic) for documented template packages") private String templateFile;
52
    @Option(names = {"-o", "--output"},
53
            defaultValue = "output.docx",
54
            description = "Output file path") private String outputPath;
55
    @Option(names = {"-s", "--stamper"},
56
            defaultValue = "word",
57
            description = "Stamper type (word, powerpoint)") private String stamperType;
58
59
    /// Default constructor.
60
    public Main() {
61
    }
62
63
    static void main(String[] args) {
64
        var main = new Main();
65
        var cli = new CommandLine(main);
66
        int exitCode = cli.execute(args);
67 1 1. main : removed call to java/lang/System::exit → NO_COVERAGE
        System.exit(exitCode);
68
    }
69
70
    private static InputStream streamFile(Path path) {
71
        try {
72 1 1. streamFile : replaced return value with null for pro/verron/officestamper/Main::streamFile → NO_COVERAGE
            return Files.newInputStream(path);
73
        } catch (IOException e) {
74
            throw new OfficeStamperException(e);
75
        }
76
    }
77
78
    @Override
79
    public void run() {
80 2 1. run : negated conditional → NO_COVERAGE
2. run : negated conditional → NO_COVERAGE
        if (inputFile == null || templateFile == null) {
81
            logger.log(Level.SEVERE, "Input file and template file must be provided");
82
            return;
83
        }
84
85
        stamperType = stamperType.toLowerCase();
86
87
        logger.log(Level.INFO, "Input File: {}", inputFile);
88
        logger.log(Level.INFO, "Template File: {}", templateFile);
89
        logger.log(Level.INFO, "Output Path: {}", outputPath);
90
        logger.log(Level.INFO, "Stamper Type: {}", stamperType);
91
92
        final var context = extractContext(inputFile);
93
        final var templateStream = extractTemplate(templateFile);
94
        final var outputStream = createOutputStream(Path.of(outputPath));
95
96
        final var stamper = switch (stamperType) {
97
            case "word" -> OfficeStampers.docxStamper();
98
            case "powerpoint" -> ExperimentalStampers.pptxStamper();
99
            default -> throw new OfficeStamperException("Invalid stamper type: " + stamperType);
100
        };
101
102 1 1. run : removed call to pro/verron/officestamper/api/StreamStamper::stamp → NO_COVERAGE
        stamper.stamp(templateStream, context, outputStream);
103
    }
104
105
    private Object extractContext(String input) {
106 2 1. extractContext : negated conditional → NO_COVERAGE
2. extractContext : replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE
        if ("diagnostic".equals(input)) return Diagnostic.context();
107 1 1. extractContext : replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE
        return contextualise(Path.of(input));
108
    }
109
110
    private InputStream extractTemplate(String template) {
111 2 1. extractTemplate : replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE
2. extractTemplate : negated conditional → NO_COVERAGE
        if ("diagnostic".equals(template)) return Diagnostic.template();
112 1 1. extractTemplate : replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE
        return streamFile(Path.of(template));
113
    }
114
115
    private OutputStream createOutputStream(Path path) {
116
        try {
117 1 1. createOutputStream : replaced return value with null for pro/verron/officestamper/Main::createOutputStream → NO_COVERAGE
            return newOutputStream(path);
118
        } catch (IOException e) {
119
            throw new OfficeStamperException(e);
120
        }
121
    }
122
123
    private Object contextualise(Path path) {
124 2 1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE
2. contextualise : negated conditional → NO_COVERAGE
        if (path.endsWith(".csv")) return processCsv(path);
125 2 1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE
2. contextualise : negated conditional → NO_COVERAGE
        if (path.endsWith(".properties")) return processProperties(path);
126 3 1. contextualise : negated conditional → NO_COVERAGE
2. contextualise : negated conditional → NO_COVERAGE
3. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE
        if (path.endsWith(".html") || path.endsWith(".xml")) return processXmlOrHtml(path);
127 2 1. contextualise : negated conditional → NO_COVERAGE
2. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE
        if (path.endsWith(".json")) return processJson(path);
128 2 1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE
2. contextualise : negated conditional → NO_COVERAGE
        if (path.endsWith(".xlsx")) return processExcel(path);
129
        throw new OfficeStamperException("Unsupported file type: " + path);
130
    }
131
132
    /// Return a list of objects with the csv properties
133
    private Object processCsv(Path path) {
134
        try (var reader = new CSVReader(new InputStreamReader(Files.newInputStream(path)))) {
135
            String[] headers = reader.readNext();
136 1 1. processCsv : replaced return value with null for pro/verron/officestamper/Main::processCsv → NO_COVERAGE
            return reader.readAll()
137
                         .stream()
138
                         .map(row -> {
139
                             Map<String, String> map = new LinkedHashMap<>();
140 2 1. lambda$processCsv$0 : changed conditional boundary → NO_COVERAGE
2. lambda$processCsv$0 : negated conditional → NO_COVERAGE
                             for (int i = 0; i < headers.length; i++) {
141
                                 map.put(headers[i], row[i]);
142
                             }
143 1 1. lambda$processCsv$0 : replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::lambda$processCsv$0 → NO_COVERAGE
                             return map;
144
                         })
145
                         .toList();
146
        } catch (IOException | CsvException e) {
147
            throw new OfficeStamperException(e);
148
        }
149
    }
150
151
    private Object processProperties(Path path) {
152
        var properties = new Properties();
153
        try (var inputStream = Files.newInputStream(path)) {
154 1 1. processProperties : removed call to java/util/Properties::load → NO_COVERAGE
            properties.load(inputStream);
155 1 1. processProperties : replaced return value with null for pro/verron/officestamper/Main::processProperties → NO_COVERAGE
            return new LinkedHashMap<>(properties.entrySet()
156
                                                 .stream()
157 1 1. lambda$processProperties$0 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$0 → NO_COVERAGE
                                                 .collect(Collectors.toMap(e -> String.valueOf(e.getKey()),
158 1 1. lambda$processProperties$1 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$1 → NO_COVERAGE
                                                         e -> String.valueOf(e.getValue()),
159 1 1. lambda$processProperties$2 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$2 → NO_COVERAGE
                                                         (a, b) -> b,
160
                                                         LinkedHashMap::new)));
161
        } catch (IOException e) {
162
            throw new OfficeStamperException(e);
163
        }
164
    }
165
166
    private Object processXmlOrHtml(Path path) {
167
        try {
168
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
169 1 1. processXmlOrHtml : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → NO_COVERAGE
            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
170 1 1. processXmlOrHtml : removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → NO_COVERAGE
            factory.setExpandEntityReferences(false);
171
172
            DocumentBuilder builder = factory.newDocumentBuilder();
173
            Document document = builder.parse(Files.newInputStream(path));
174 1 1. processXmlOrHtml : replaced return value with null for pro/verron/officestamper/Main::processXmlOrHtml → NO_COVERAGE
            return processNode(document.getDocumentElement());
175
        } catch (ParserConfigurationException | SAXException | IOException e) {
176
            throw new OfficeStamperException(e);
177
        }
178
    }
179
180
    private Map<String, Object> processNode(Element element) {
181
        Map<String, Object> result = new LinkedHashMap<>();
182
        NodeList children = element.getChildNodes();
183
184 2 1. processNode : changed conditional boundary → NO_COVERAGE
2. processNode : negated conditional → NO_COVERAGE
        for (int i = 0; i < children.getLength(); i++) {
185
            Node node = children.item(i);
186 1 1. processNode : negated conditional → NO_COVERAGE
            if (node instanceof Element childElement) {
187
                String name = childElement.getTagName();
188 1 1. processNode : negated conditional → NO_COVERAGE
                if (childElement.hasChildNodes() && childElement.getFirstChild()
189 1 1. processNode : negated conditional → NO_COVERAGE
                                                                .getNodeType() != Node.TEXT_NODE) {
190
                    result.put(name, processNode(childElement));
191
                }
192
                else {
193
                    result.put(name, childElement.getTextContent());
194
                }
195
            }
196
        }
197 1 1. processNode : replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::processNode → NO_COVERAGE
        return result;
198
    }
199
200
    private Object processJson(Path path) {
201
        try {
202
            ObjectMapper mapper = new ObjectMapper();
203
            TypeReference<LinkedHashMap<String, Object>> typeRef = new TypeReference<>() {};
204 1 1. processJson : replaced return value with null for pro/verron/officestamper/Main::processJson → NO_COVERAGE
            return mapper.readValue(Files.newInputStream(path), typeRef);
205
        } catch (IOException e) {
206
            throw new OfficeStamperException(e);
207
        }
208
    }
209
210
    private Object processExcel(Path path) {
211
        try {
212 1 1. processExcel : replaced return value with null for pro/verron/officestamper/Main::processExcel → NO_COVERAGE
            return ExcelContext.from(Files.newInputStream(path));
213
        } catch (IOException e) {
214
            throw new OfficeStamperException(e);
215
        }
216
    }
217
}

Mutations

67

1.1
Location : main
Killed by : none
removed call to java/lang/System::exit → NO_COVERAGE

72

1.1
Location : streamFile
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::streamFile → NO_COVERAGE

80

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

2.2
Location : run
Killed by : none
negated conditional → NO_COVERAGE

102

1.1
Location : run
Killed by : none
removed call to pro/verron/officestamper/api/StreamStamper::stamp → NO_COVERAGE

106

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

2.2
Location : extractContext
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE

107

1.1
Location : extractContext
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE

111

1.1
Location : extractTemplate
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE

2.2
Location : extractTemplate
Killed by : none
negated conditional → NO_COVERAGE

112

1.1
Location : extractTemplate
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE

117

1.1
Location : createOutputStream
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::createOutputStream → NO_COVERAGE

124

1.1
Location : contextualise
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE

2.2
Location : contextualise
Killed by : none
negated conditional → NO_COVERAGE

125

1.1
Location : contextualise
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE

2.2
Location : contextualise
Killed by : none
negated conditional → NO_COVERAGE

126

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

2.2
Location : contextualise
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : contextualise
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE

127

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

2.2
Location : contextualise
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE

128

1.1
Location : contextualise
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE

2.2
Location : contextualise
Killed by : none
negated conditional → NO_COVERAGE

136

1.1
Location : processCsv
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::processCsv → NO_COVERAGE

140

1.1
Location : lambda$processCsv$0
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : lambda$processCsv$0
Killed by : none
negated conditional → NO_COVERAGE

143

1.1
Location : lambda$processCsv$0
Killed by : none
replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::lambda$processCsv$0 → NO_COVERAGE

154

1.1
Location : processProperties
Killed by : none
removed call to java/util/Properties::load → NO_COVERAGE

155

1.1
Location : processProperties
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::processProperties → NO_COVERAGE

157

1.1
Location : lambda$processProperties$0
Killed by : none
replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$0 → NO_COVERAGE

158

1.1
Location : lambda$processProperties$1
Killed by : none
replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$1 → NO_COVERAGE

159

1.1
Location : lambda$processProperties$2
Killed by : none
replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$2 → NO_COVERAGE

169

1.1
Location : processXmlOrHtml
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → NO_COVERAGE

170

1.1
Location : processXmlOrHtml
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → NO_COVERAGE

174

1.1
Location : processXmlOrHtml
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::processXmlOrHtml → NO_COVERAGE

184

1.1
Location : processNode
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : processNode
Killed by : none
negated conditional → NO_COVERAGE

186

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

188

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

189

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

197

1.1
Location : processNode
Killed by : none
replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::processNode → NO_COVERAGE

204

1.1
Location : processJson
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::processJson → NO_COVERAGE

212

1.1
Location : processExcel
Killed by : none
replaced return value with null for pro/verron/officestamper/Main::processExcel → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.0