• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pkiraly / metadata-qa-marc / #1527

22 Aug 2025 02:21PM UTC coverage: 90.345%. Remained the same
#1527

push

pkiraly
Improve timeline handling

5191 of 6416 new or added lines in 219 files covered. (80.91%)

886 existing lines in 78 files now uncovered.

36717 of 40641 relevant lines covered (90.34%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.18
/src/main/java/de/gwdg/metadataqa/marc/cli/ValidatorCli.java
1
package de.gwdg.metadataqa.marc.cli;
2

3
import de.gwdg.metadataqa.marc.CsvUtils;
4
import de.gwdg.metadataqa.marc.analysis.validator.Validator;
5
import de.gwdg.metadataqa.marc.analysis.validator.ValidatorConfiguration;
6
import de.gwdg.metadataqa.marc.analysis.validator.ValidatorDAO;
7
import de.gwdg.metadataqa.marc.cli.parameters.ValidatorParameters;
8
import de.gwdg.metadataqa.marc.cli.processor.BibliographicInputProcessor;
9
import de.gwdg.metadataqa.marc.cli.utils.RecordIterator;
10
import de.gwdg.metadataqa.marc.dao.record.BibliographicRecord;
11
import de.gwdg.metadataqa.marc.dao.record.Marc21Record;
12
import de.gwdg.metadataqa.marc.model.validation.ValidationError;
13
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorCategory;
14
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorFormatter;
15
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorType;
16
import org.apache.commons.cli.HelpFormatter;
17
import org.apache.commons.cli.Options;
18
import org.apache.commons.cli.ParseException;
19
import org.apache.commons.io.FileUtils;
20
import org.apache.commons.lang3.StringUtils;
21
import org.marc4j.marc.Record;
22

23
import java.io.File;
24
import java.io.IOException;
25
import java.io.Serializable;
26
import java.nio.file.Files;
27
import java.nio.file.Path;
28
import java.nio.file.Paths;
29
import java.util.ArrayList;
30
import java.util.Arrays;
31
import java.util.Comparator;
32
import java.util.HashMap;
33
import java.util.HashSet;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.Set;
37
import java.util.TreeMap;
38
import java.util.logging.Level;
39
import java.util.logging.Logger;
40

41
import static de.gwdg.metadataqa.marc.Utils.count;
42
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorFormat.TAB_SEPARATED;
43

44
/**
45
 * usage:
46
 * java -cp target/qa-catalogue-0.1-SNAPSHOT-jar-with-dependencies.jar de.gwdg.metadataqa.marc.cli.Validator [MARC21 file]
47
 *
48
 * @author Péter Király <peter.kiraly at gwdg.de>
49
 */
50
public class ValidatorCli extends QACli<ValidatorParameters> implements BibliographicInputProcessor, Serializable {
51

52
  private static final Logger logger = Logger.getLogger(ValidatorCli.class.getCanonicalName());
1✔
53
  private Options options;
54

55
  private final Map<Integer, Integer> hashedIndex = new HashMap<>();
1✔
56
  private File detailsFile = null;
1✔
57
  private File summaryFile = null;
1✔
58
  private File collectorFile = null;
1✔
59
  private boolean doPrintInProcessRecord = true;
1✔
60
  private boolean readyToProcess;
61
  private int recordCounter;
62
  private int numberOfprocessedRecords;
63
  private char separator;
64
  private int vErrorId = 1;
1✔
65
  private List<ValidationError> allValidationErrors;
66
  private ValidatorConfiguration validatorConfiguration;
67
  private ValidatorDAO validatorDAO = new ValidatorDAO();
1✔
68

69
  public ValidatorCli(String[] args) throws ParseException {
70
    this(new ValidatorParameters(args));
1✔
71
  }
1✔
72

73
  public ValidatorCli(ValidatorParameters parameters) {
1✔
74
    this.parameters = parameters;
1✔
75
    options = parameters.getOptions();
1✔
76
    readyToProcess = true;
1✔
77
    recordCounter = 0;
1✔
78
    validatorConfiguration = new ValidatorConfiguration()
1✔
79
      .withMarcVersion(parameters.getMarcVersion())
1✔
80
      .withDoSummary(parameters.doSummary())
1✔
81
      .withIgnorableFields(parameters.getIgnorableFields())
1✔
82
      .withIgnorableIssueTypes(parameters.getIgnorableIssueTypes())
1✔
83
      .withSchemaType(parameters.getSchemaType());
1✔
84

85
    initializeGroups(parameters.getGroupBy(), parameters.isPica());
1✔
86
    if (doGroups()) {
1✔
87
      initializeMeta(parameters);
1✔
88
      if (doSaveGroupIds) {
1✔
89
        logger.info("saveGroupIds!");
1✔
90
        idCollectorFile = prepareReportFile(parameters.getOutputDir(), "id-groupid.csv");
1✔
91
        printToFile(idCollectorFile, CsvUtils.createCsv("id", "groupId"));
1✔
92
      }
93
    }
94
    separator = parameters.getFormat().equals(TAB_SEPARATED) ? '\t' : ',';
1✔
95
  }
1✔
96

97
  public static void main(String[] args) {
UNCOV
98
    BibliographicInputProcessor processor = null;
×
99
    try {
UNCOV
100
      processor = new ValidatorCli(args);
×
101
    } catch (ParseException e) {
×
NEW
102
      logger.severe("ERROR. " + e.getLocalizedMessage());
×
103
      System.exit(1);
×
104
    }
×
105
    if (processor.getParameters().getArgs().length < 1) {
×
NEW
106
      logger.severe("Please provide a MARC file name!");
×
107
      processor.printHelp(processor.getParameters().getOptions());
×
108
      System.exit(0);
×
109
    }
UNCOV
110
    if (processor.getParameters().doHelp()) {
×
111
      processor.printHelp(processor.getParameters().getOptions());
×
112
      System.exit(0);
×
113
    }
UNCOV
114
    RecordIterator iterator = new RecordIterator(processor);
×
NEW
115
    iterator.setProcessWithErrors(true);
×
116
    // iterator.setProcessWithErrors(processor.getParameters().getProcessRecordsWithoutId());
UNCOV
117
    iterator.start();
×
UNCOV
118
  }
×
119

120
  public void printHelp(Options opions) {
UNCOV
121
    HelpFormatter formatter = new HelpFormatter();
×
UNCOV
122
    String message = String.format("java -cp qa-catalogue.jar %s [options] [file]",
×
123
      this.getClass().getCanonicalName());
×
124
    formatter.printHelp(message, options);
×
125
  }
×
126

127
  @Override
128
  public ValidatorParameters getParameters() {
129
    return parameters;
1✔
130
  }
131

132
  @Override
133
  public void beforeIteration() {
134
    logger.info(() -> parameters.formatParameters());
1✔
135
    if (!parameters.useStandardOutput()) {
1✔
136
      detailsFile = prepareReportFile(parameters.getOutputDir(), parameters.getDetailsFileName());
1✔
137
      logger.info("details output: " + detailsFile.getPath());
1✔
138
      if (parameters.getSummaryFileName() != null) {
1✔
139
        summaryFile = prepareReportFile(parameters.getOutputDir(), parameters.getSummaryFileName());
1✔
140
        logger.info("summary output: " + summaryFile.getPath());
1✔
141

142
        collectorFile = prepareReportFile(parameters.getOutputDir(), "issue-collector.csv");
1✔
143
        String header = ValidationErrorFormatter.formatHeaderForCollector(parameters.getFormat());
1✔
144
        print(collectorFile, header);
1✔
145

146
      } else {
1✔
UNCOV
147
        if (parameters.doSummary())
×
UNCOV
148
          summaryFile = detailsFile;
×
149
      }
150
    }
151
    if (parameters.doDetails()) {
1✔
152
      String header = ValidationErrorFormatter.formatHeaderForDetails(parameters.getFormat());
1✔
153
      print(detailsFile, header);
1✔
154
    }
155

156
    if (parameters.collectAllErrors())
1✔
UNCOV
157
      allValidationErrors = new ArrayList<>();
×
158
  }
1✔
159

160
  @Override
161
  public void fileOpened(Path currentFile) {
162
    // do nothing
163
  }
1✔
164

165
  @Override
166
  public void fileProcessed() {
167
    // do nothing
UNCOV
168
  }
×
169

170
  @Override
171
  public void processRecord(Record marc4jRecord, int recordNumber) throws IOException {
172
    // do nothing
173
  }
1✔
174

175
  @Override
176
  public void processRecord(BibliographicRecord bibliographicRecord, int recordNumber) throws IOException {
NEW
177
    processRecord(bibliographicRecord, recordNumber, null);
×
178
  }
×
179

180
  @Override
181
  public void processRecord(BibliographicRecord bibliographicRecord, int recordNumber, List<ValidationError> errors) {
182
    logRecordIssuesIfPresent(bibliographicRecord, recordNumber);
1✔
183

184
    if (bibliographicRecord != null && parameters.getRecordIgnorator().isIgnorable(bibliographicRecord)) {
1✔
UNCOV
185
      logger.info("skip " + bibliographicRecord.getId() + " (ignorable record)");
×
UNCOV
186
      return;
×
187
    }
188

189
    Set<String> groupIds = getGroupIds(parameters, bibliographicRecord);
1✔
190
    if (doSaveGroupIds && bibliographicRecord != null && bibliographicRecord.getId() != null)
1✔
191
      saveGroupIds(bibliographicRecord.getId(true), groupIds);
1✔
192

193
    Validator validator = new Validator(validatorConfiguration, errors);
1✔
194
    boolean isValid = validator.validate(bibliographicRecord);
1✔
195

196
    if (!isValid) {
1✔
197
      processInvalidRecord(bibliographicRecord, validator, groupIds);
1✔
NEW
198
    } else if (parameters.doSummary()) {
×
199
      // TODO: use enum instead
NEW
200
      updateCounters(0, groupIds, validatorDAO.getTotalRecordCounter(), validatorDAO.getTotalRecordCounterGrouped());
×
201
    }
202

203
    if (parameters.collectAllErrors())
1✔
UNCOV
204
      allValidationErrors.addAll(validator.getValidationErrors());
×
205

206
    recordCounter++;
1✔
207
  }
1✔
208

209
  private void logRecordIssuesIfPresent(BibliographicRecord bibliographicRecord, int recordNumber) {
210
    if (bibliographicRecord == null) {
1✔
211
      logger.severe(() -> "bibliographicRecord is null at " + recordNumber);
1✔
212
    } else if (bibliographicRecord.getId() == null || hasNoControl001(bibliographicRecord)) {
1✔
NEW
213
      logger.severe(() -> "No record identifier at " + recordNumber);
×
214
    }
215
    if (recordNumber % 100000 == 0) {
1✔
NEW
216
      logger.info(() -> "Number of error types so far: " + validatorDAO.getInstanceBasedErrorCounter().size());
×
217
    }
218
  }
1✔
219

220
  /**
221
   * Creates the summary and the details for the invalid record in case of printing being enabled.
222
   * @param bibliographicRecord The record to process
223
   * @param validator The validator object containing the validation errors
224
   * @param groupIds The group IDs of the record
225
   */
226
  private void processInvalidRecord(BibliographicRecord bibliographicRecord, Validator validator, Set<String> groupIds) {
227
    if (!doPrintInProcessRecord) {
1✔
NEW
228
      return;
×
229
    }
230

231
    if (parameters.doSummary()) {
1✔
232
      processSummary(bibliographicRecord, validator, groupIds);
1✔
233
    }
234
    if (parameters.doDetails()) {
1✔
235
      processDetails(bibliographicRecord, validator);
1✔
236
    }
237
  }
1✔
238

239
  private static boolean hasNoControl001(BibliographicRecord bibliographicRecord) {
240
    return bibliographicRecord instanceof Marc21Record && ((Marc21Record) bibliographicRecord).getControl001() == null;
1✔
241
  }
242

243
  private void processDetails(BibliographicRecord marcRecord, Validator validator) {
244
    List<ValidationError> errors = validator.getValidationErrors();
1✔
245
    if (errors.isEmpty()) {
1✔
NEW
246
      return;
×
247
    }
248
    String message;
249

250
    if (!parameters.doSummary()) {
1✔
NEW
251
      message = ValidationErrorFormatter.format(errors, parameters.getFormat(), parameters.getTrimId());
×
NEW
252
      print(detailsFile, message);
×
NEW
253
      return;
×
254
    }
255

256
    Map<Integer, Integer> errorIds = new HashMap<>();
1✔
257
    for (ValidationError error : errors) {
1✔
258
      if (error.getId() == null) {
1✔
NEW
259
        error.setId(hashedIndex.get(error.hashCode()));
×
260
      }
261
      count(error.getId(), errorIds);
1✔
262
    }
1✔
263

264
    String recordId = marcRecord != null ? marcRecord.getId(parameters.getTrimId()) : "unknown";
1✔
265
    message = ValidationErrorFormatter.formatSimple(recordId, parameters.getFormat(), errorIds);
1✔
266

267
    print(detailsFile, message);
1✔
268
  }
1✔
269

270
  private void processSummary(BibliographicRecord marcRecord, Validator validator) {
UNCOV
271
    processSummary(marcRecord, validator, null);
×
UNCOV
272
  }
×
273

274
  private void processSummary(BibliographicRecord marcRecord,
275
                              Validator validator,
276
                              Set<String> groupIds) {
277
    List<ValidationError> errors = validator.getValidationErrors();
1✔
278
    List<ValidationError> allButInvalidFieldErrors = new ArrayList<>();
1✔
279
    Set<Integer> uniqueErrors = new HashSet<>();
1✔
280
    Set<ValidationErrorType> uniqueTypes = new HashSet<>();
1✔
281
    Set<ValidationErrorCategory> uniqueCategories = new HashSet<>();
1✔
282
    for (ValidationError error : errors) {
1✔
283
      // set error ID
284
      if (!validatorDAO.getInstanceBasedErrorCounter().containsKey(error)) {
1✔
285
        error.setId(vErrorId++);
1✔
286
        hashedIndex.put(error.hashCode(), error.getId());
1✔
287
      } else {
288
        error.setId(hashedIndex.get(error.hashCode()));
1✔
289
      }
290

291
      if (!error.getType().equals(ValidationErrorType.FIELD_UNDEFINED)) {
1✔
292
        count(2, validatorDAO.getTotalInstanceCounter());
1✔
293
        allButInvalidFieldErrors.add(error);
1✔
294
      }
295

296
      count(error, validatorDAO.getInstanceBasedErrorCounter());
1✔
297
      for (String groupId : groupIds) {
1✔
298
        validatorDAO.getInstanceBasedErrorCounterGrouped().computeIfAbsent(groupId, s -> new HashMap<>());
1✔
299
        count(error, validatorDAO.getInstanceBasedErrorCounterGrouped().get(groupId));
1✔
300
      }
1✔
301

302
      updateCounters(error.getType(), groupIds, validatorDAO.getTypeInstanceCounter(), validatorDAO.getTypeInstanceCounterGrouped());
1✔
303
      updateCounters(error.getType().getCategory(), groupIds, validatorDAO.getCategoryInstanceCounter(), validatorDAO.getCategoryInstanceCounterGrouped());
1✔
304

305
      count(1, validatorDAO.getTotalInstanceCounter());
1✔
306
      updateErrorCollector(marcRecord != null ? marcRecord.getId(true) : "unknown", error.getId());
1✔
307
      uniqueErrors.add(error.getId());
1✔
308
      uniqueTypes.add(error.getType());
1✔
309
      uniqueCategories.add(error.getType().getCategory());
1✔
310
    }
1✔
311

312
    for (Integer errorId : uniqueErrors) {
1✔
313
      updateCounters(errorId, groupIds, validatorDAO.getRecordBasedErrorCounter(), validatorDAO.getRecordBasedErrorCounterGrouped());
1✔
314
    }
1✔
315
    for (ValidationErrorType errorType : uniqueTypes) {
1✔
316
      updateCounters(errorType, groupIds, validatorDAO.getTypeRecordCounter(), validatorDAO.getTypeRecordCounterGrouped());
1✔
317
    }
1✔
318
    for (ValidationErrorCategory errorCategory : uniqueCategories) {
1✔
319
      updateCounters(errorCategory, groupIds, validatorDAO.getCategoryRecordCounter(), validatorDAO.getCategoryRecordCounterGrouped());
1✔
320
    }
1✔
321

322
    updateCounters(1, groupIds, validatorDAO.getTotalRecordCounter(), validatorDAO.getTotalRecordCounterGrouped());
1✔
323
    if (!allButInvalidFieldErrors.isEmpty())
1✔
324
      updateCounters(2, groupIds, validatorDAO.getTotalRecordCounter(), validatorDAO.getTotalRecordCounterGrouped());
1✔
325
  }
1✔
326

327
  @Override
328
  public void afterIteration(int numberOfprocessedRecords, long duration) {
329
    logger.info("printCounter");
1✔
330
    this.numberOfprocessedRecords = numberOfprocessedRecords;
1✔
331
    printCounter();
1✔
332

333
    if (parameters.doSummary()) {
1✔
334
      if (doGroups()) {
1✔
335
        logger.info("Saving grouped summary");
1✔
336
        printSummaryGrouped();
1✔
337
        printCategoryCountsGrouped();
1✔
338
        printTypeCountsGrouped();
1✔
339
        printTotalCountsGrouped();
1✔
340
      } else {
341
        logger.info("Saving summary");
1✔
342
        printSummary();
1✔
343
        printCategoryCounts();
1✔
344
        printTypeCounts();
1✔
345
        printTotalCounts();
1✔
346
      }
347
      printCollector();
1✔
348
    }
349
    copySchemaFileToOutputDir();
1✔
350

351
    logger.info("all printing is DONE");
1✔
352
    saveParameters("validation.params.json", parameters, Map.of("numberOfprocessedRecords", numberOfprocessedRecords, "duration", duration));
1✔
353
  }
1✔
354

355
  private void copySchemaFileToOutputDir() {
356
    if (parameters.isPica() || parameters.isUnimarc()) {
1✔
357
      // TODO define constants somewhere
358
      String defaultSchemaFile = parameters.isPica()
1✔
359
        ? "src/main/resources/pica/avram-k10plus-title.json"
1✔
360
        : "src/main/resources/unimarc/avram-unimarc.json";
1✔
361
      String schemaFile = StringUtils.isNotEmpty(parameters.getPicaSchemaFile())
1✔
UNCOV
362
        ? parameters.getPicaSchemaFile()
×
363
        : Paths.get(defaultSchemaFile).toAbsolutePath().toString();
1✔
364
      File source = new File(schemaFile);
1✔
365
      try {
366
        FileUtils.copyFileToDirectory(source, new File(parameters.getOutputDir()));
1✔
UNCOV
367
      } catch (IOException e) {
×
UNCOV
368
        logger.warning(e.getLocalizedMessage());
×
369
      }
1✔
370
    }
371
  }
1✔
372

373
  private void printCounter() {
374
    File countFile = prepareReportFile(parameters.getOutputDir(), "count.csv");
1✔
375
    if (parameters.getRecordIgnorator().isEmpty()) {
1✔
376
      printToFile(countFile, "total\n");
1✔
377
      printToFile(countFile, String.valueOf(numberOfprocessedRecords) + "\n");
1✔
378
    } else {
UNCOV
379
      printToFile(countFile, StringUtils.join(Arrays.asList("total", "processed"), ",") + "\n");
×
NEW
380
      printToFile(countFile, StringUtils.join(Arrays.asList(numberOfprocessedRecords, recordCounter), ",") + "\n");
×
381
    }
382
  }
1✔
383

384
  private void printCollector() {
385
    for (Map.Entry<Integer, Set<String>> entry : validatorDAO.getErrorCollector().entrySet()) {
1✔
386
      printCollectorEntry(entry.getKey(), entry.getValue());
1✔
387
    }
1✔
388
  }
1✔
389

390
  private void printSummary() {
391
    String header = ValidationErrorFormatter.formatHeaderForSummary(parameters.getFormat(), doGroups());
1✔
392
    print(summaryFile, header);
1✔
393

394
    Comparator<Map.Entry<ValidationError, Integer>> comparator = Comparator.comparing(a -> a.getKey().getType().getId());
1✔
395
    comparator = comparator.thenComparing(a -> validatorDAO.getRecordBasedErrorCounter().get(a.getKey().getId()));
1✔
396
    comparator = comparator.thenComparing(a -> a.getKey().getId());
1✔
397

398
    validatorDAO.getInstanceBasedErrorCounter()
1✔
399
      .entrySet()
1✔
400
      .stream()
1✔
401
      .sorted(comparator)
1✔
402
      .forEach(
1✔
403
        entry -> {
404
          ValidationError error = entry.getKey();
1✔
405
          int instanceCount = entry.getValue();
1✔
406
          List<Serializable> cells = new ArrayList<>();
1✔
407
          cells.add(error.getId());
1✔
408
          cells.addAll(Arrays.asList(ValidationErrorFormatter.asArrayWithoutId(error)));
1✔
409
          cells.addAll(Arrays.asList(instanceCount, validatorDAO.getRecordBasedErrorCounter().get(error.getId())));
1✔
410
          // TODO: separator
411
          print(summaryFile, CsvUtils.createCsv(cells));
1✔
412
        }
1✔
413
      );
414
  }
1✔
415

416
  private void printSummaryGrouped() {
417
    String header = ValidationErrorFormatter.formatHeaderForSummary(parameters.getFormat(), doGroups());
1✔
418
    print(summaryFile, header);
1✔
419
    validatorDAO.getInstanceBasedErrorCounterGrouped()
1✔
420
      .entrySet()
1✔
421
      .stream()
1✔
422
      .sorted(Comparator.comparing(Map.Entry::getKey))
1✔
423
      .forEach(groupEntry -> {
1✔
424
        String groupId = groupEntry.getKey();
1✔
425
        Map<ValidationError, Integer> groupMap = groupEntry.getValue();
1✔
426
        groupMap
1✔
427
          .entrySet()
1✔
428
          .stream()
1✔
429
          .sorted((a,b) -> {
1✔
430
            Integer typeIdA = Integer.valueOf(a.getKey().getType().getId());
1✔
431
            Integer typeIdB = Integer.valueOf(b.getKey().getType().getId());
1✔
432
            int result = typeIdA.compareTo(typeIdB);
1✔
433
            if (result == 0) {
1✔
434
              Integer recordCountA = validatorDAO.getRecordBasedErrorCounterGrouped().get(groupId).get(a.getKey().getId());
1✔
435
              Integer recordCountB = validatorDAO.getRecordBasedErrorCounterGrouped().get(groupId).get(b.getKey().getId());
1✔
436
              result = recordCountB.compareTo(recordCountA);
1✔
437
            }
438
            return result;
1✔
439
          }) // sort
440
          .forEach(
1✔
441
            entry -> {
442
              ValidationError error = entry.getKey();
1✔
443
              int instanceCount = entry.getValue();
1✔
444
              List<Serializable> cells = new ArrayList<>();
1✔
445
              cells.add(groupId);
1✔
446
              cells.add(error.getId());
1✔
447
              cells.addAll(Arrays.asList(ValidationErrorFormatter.asArrayWithoutId(error)));
1✔
448
              cells.addAll(Arrays.asList(instanceCount, validatorDAO.getRecordBasedErrorCounterGrouped().get(groupId).get(error.getId())));
1✔
449
              // TODO: separator
450
              print(summaryFile, CsvUtils.createCsv(cells));
1✔
451
          });
1✔
452
      });
1✔
453
  }
1✔
454

455
  private void printTypeCounts() {
456
    var path = Paths.get(parameters.getOutputDir(), "issue-by-type.csv");
1✔
457
    try (var writer = Files.newBufferedWriter(path)) {
1✔
458
      writer.write(CsvUtils.createCsv("id", "categoryId", "category", "type", "instances", "records"));
1✔
459
      validatorDAO.getTypeRecordCounter()
1✔
460
        .entrySet()
1✔
461
        .stream()
1✔
462
        .sorted(Comparator.comparing(a -> ((Integer) a.getKey().getId())))
1✔
463
        .forEach(entry -> {
1✔
464
          ValidationErrorType type = entry.getKey();
1✔
465
          int records = entry.getValue();
1✔
466
          int instances = validatorDAO.getTypeInstanceCounter().get(entry.getKey());
1✔
467
          try {
468
            writer.write(CsvUtils.createCsv(type.getId(), type.getCategory().getId(), type.getCategory().getName(), type.getMessage(), instances, records));
1✔
UNCOV
469
          } catch (IOException e) {
×
UNCOV
470
            logger.log(Level.SEVERE, "printTypeCounts", e);
×
471
          }
1✔
472
        });
1✔
UNCOV
473
    } catch (IOException e) {
×
UNCOV
474
      logger.log(Level.SEVERE, "printTypeCounts", e);
×
475
    }
1✔
476
  }
1✔
477

478
  private void printTypeCountsGrouped() {
479
    var path = Paths.get(parameters.getOutputDir(), "issue-by-type.csv");
1✔
480
    try (var writer = Files.newBufferedWriter(path)) {
1✔
481
      writer.write(CsvUtils.createCsv("groupId", "id", "categoryId", "category", "type", "instances", "records"));
1✔
482
      validatorDAO.getTypeRecordCounterGrouped()
1✔
483
        .entrySet()
1✔
484
        .stream()
1✔
485
        .sorted(Comparator.comparing(Map.Entry::getKey))
1✔
486
        .forEach(groupEntry -> {
1✔
487
          String groupId = groupEntry.getKey();
1✔
488
          Map<ValidationErrorType, Integer> groupMap = groupEntry.getValue();
1✔
489
          groupMap
1✔
490
            .entrySet()
1✔
491
            .stream()
1✔
492
            .sorted(Comparator.comparing(a -> ((Integer) a.getKey().getId())))
1✔
493
            .forEach(entry -> {
1✔
494
              ValidationErrorType type = entry.getKey();
1✔
495
              int records = entry.getValue();
1✔
496
              int instances = validatorDAO.getTypeInstanceCounterGrouped().get(groupId).get(entry.getKey());
1✔
497
              try {
498
                writer.write(CsvUtils.createCsv(groupId, type.getId(), type.getCategory().getId(), type.getCategory().getName(), type.getMessage(), instances, records));
1✔
UNCOV
499
              } catch (IOException e) {
×
UNCOV
500
                logger.log(Level.SEVERE, "printTypeCounts", e);
×
501
              }
1✔
502
            });
1✔
503
      });
1✔
504
    } catch (IOException e) {
×
UNCOV
505
      logger.log(Level.SEVERE, "printTypeCounts", e);
×
506
    }
1✔
507
  }
1✔
508

509
  private void printTotalCounts() {
510
    var path = Paths.get(parameters.getOutputDir(), "issue-total.csv");
1✔
511
    try (var writer = Files.newBufferedWriter(path)) {
1✔
512
      writer.write(CsvUtils.createCsv("type", "instances", "records"));
1✔
513
      validatorDAO.getTotalRecordCounter()
1✔
514
        .entrySet()
1✔
515
        .stream()
1✔
516
        .forEach(entry -> {
1✔
517
          int records = entry.getValue();
1✔
518
          int instances = validatorDAO.getTotalInstanceCounter().getOrDefault(entry.getKey(), 0);
1✔
519
          try {
520
            writer.write(CsvUtils.createCsv(entry.getKey(), instances, records));
1✔
UNCOV
521
          } catch (IOException e) {
×
UNCOV
522
            logger.log(Level.SEVERE, "printTotalCounts", e);
×
523
          }
1✔
524
        });
1✔
UNCOV
525
    } catch (IOException e) {
×
UNCOV
526
      logger.log(Level.SEVERE, "printTotalCounts", e);
×
527
    }
1✔
528
  }
1✔
529

530
  private void printTotalCountsGrouped() {
531
    var path = Paths.get(parameters.getOutputDir(), "issue-total.csv");
1✔
532
    try (var writer = Files.newBufferedWriter(path)) {
1✔
533
      writer.write(CsvUtils.createCsv("groupId", "type", "instances", "records"));
1✔
534
      validatorDAO.getTotalRecordCounterGrouped()
1✔
535
        .entrySet()
1✔
536
        .stream()
1✔
537
        .sorted(Comparator.comparing(Map.Entry::getKey))
1✔
538
        .forEach(groupEntry -> {
1✔
539
          String groupId = groupEntry.getKey();
1✔
540
          Map<Integer, Integer> groupMap = groupEntry.getValue();
1✔
541
          groupMap
1✔
542
            .entrySet()
1✔
543
            .stream()
1✔
544
            .sorted(Comparator.comparing(Map.Entry::getKey))
1✔
545
            .forEach(entry -> {
1✔
546
              int type = entry.getKey();
1✔
547
              int records = entry.getValue();
1✔
548
              int instances = validatorDAO.getTotalInstanceCounter().getOrDefault(type, 0);
1✔
549
              try {
550
                writer.write(CsvUtils.createCsv(groupId, type, instances, records));
1✔
UNCOV
551
              } catch (IOException e) {
×
UNCOV
552
                logger.log(Level.SEVERE, "printTotalCounts", e);
×
553
              }
1✔
554
            });
1✔
555
        });
1✔
556
    } catch (IOException e) {
×
UNCOV
557
      logger.log(Level.SEVERE, "printTotalCounts", e);
×
558
    }
1✔
559
  }
1✔
560

561
  private void printCategoryCounts() {
562
    var path = Paths.get(parameters.getOutputDir(), "issue-by-category.csv");
1✔
563
    try (var writer = Files.newBufferedWriter(path)) {
1✔
564
      writer.write(CsvUtils.createCsv("id", "category", "instances", "records"));
1✔
565
      validatorDAO.getCategoryRecordCounter()
1✔
566
        .entrySet()
1✔
567
        .stream()
1✔
568
        .sorted((a, b) -> ((Integer)a.getKey().getId()).compareTo(b.getKey().getId()))
1✔
569
        .forEach(entry -> {
1✔
570
          ValidationErrorCategory category = entry.getKey();
1✔
571
          int records = entry.getValue();
1✔
572
          int instances = validatorDAO.getCategoryInstanceCounter().getOrDefault(entry.getKey(), -1);
1✔
573
          try {
574
            writer.write(CsvUtils.createCsv(category.getId(), category.getName(), instances, records));
1✔
UNCOV
575
          } catch (IOException e) {
×
UNCOV
576
            logger.log(Level.SEVERE, "printCategoryCounts", e);
×
577
          }
1✔
578
        });
1✔
UNCOV
579
    } catch (IOException e) {
×
UNCOV
580
      logger.log(Level.SEVERE, "printCategoryCounts", e);
×
581
    }
1✔
582
  }
1✔
583

584
  private void printCategoryCountsGrouped() {
585
    var path = Paths.get(parameters.getOutputDir(), "issue-by-category.csv");
1✔
586
    try (var writer = Files.newBufferedWriter(path)) {
1✔
587
      writer.write(CsvUtils.createCsv("groupId", "id", "category", "instances", "records"));
1✔
588
      validatorDAO.getCategoryRecordCounterGrouped()
1✔
589
        .entrySet()
1✔
590
        .stream()
1✔
591
        .sorted(Comparator.comparing(Map.Entry::getKey))
1✔
592
        .forEach(groupEntry -> {
1✔
593
          String groupId = groupEntry.getKey();
1✔
594
          Map<ValidationErrorCategory, Integer> groupMap = groupEntry.getValue();
1✔
595
          groupMap
1✔
596
            .entrySet()
1✔
597
            .stream()
1✔
598
            .sorted(Comparator.comparing(a -> a.getKey().getId()))
1✔
599
            .forEach(entry -> {
1✔
600
              ValidationErrorCategory category = entry.getKey();
1✔
601
              int records = entry.getValue();
1✔
602
              int instances = validatorDAO.getCategoryInstanceCounterGrouped().get(groupId).getOrDefault(entry.getKey(), -1);
1✔
603
              try {
604
                writer.write(CsvUtils.createCsv(groupId, category.getId(), category.getName(), instances, records));
1✔
UNCOV
605
              } catch (IOException e) {
×
UNCOV
606
                logger.log(Level.SEVERE, "printCategoryCounts", e);
×
607
              }
1✔
608
            });
1✔
609
        });
1✔
610
    } catch (IOException e) {
×
UNCOV
611
      logger.log(Level.SEVERE, "printCategoryCounts", e);
×
612
    }
1✔
613
  }
1✔
614

615
  private void printCollectorEntry(Integer errorId, Set<String> recordIds) {
616
    print(collectorFile, String.valueOf(errorId) + separator);
1✔
617
    boolean isFirst = true;
1✔
618
    for (String recordId : recordIds) {
1✔
619
      print(collectorFile, (isFirst ? "" : ";") + recordId);
1✔
620
      if (isFirst)
1✔
621
        isFirst = false;
1✔
622
    }
1✔
623
    print(collectorFile, "\n");
1✔
624
  }
1✔
625

626
  /**
627
   * Print to the standard output or into file
628
   * @param file The output file
629
   * @param content Te content to write
630
   */
631
  private void print(File file, String content) {
632
    if (parameters.useStandardOutput())
1✔
633
      System.out.print(content);
×
634
    else {
635
      printToFile(file, content);
1✔
636
    }
637
  }
1✔
638

639
  private void updateErrorCollector(String recordId, int errorId) {
640
    if (!validatorDAO.getErrorCollector().containsKey(errorId)) {
1✔
641
      validatorDAO.getErrorCollector().put(errorId, new HashSet<>());
1✔
642
    } else if (parameters.doEmptyLargeCollectors()
1✔
UNCOV
643
               && validatorDAO.getErrorCollector().get(errorId).size() >= 1000) {
×
UNCOV
644
      printCollectorEntry(errorId, validatorDAO.getErrorCollector().get(errorId));
×
UNCOV
645
      validatorDAO.getErrorCollector().put(errorId, new HashSet<>());
×
646
    }
647
    validatorDAO.getErrorCollector().get(errorId).add(recordId);
1✔
648
  }
1✔
649

650
  public boolean doPrintInProcessRecord() {
UNCOV
651
    return doPrintInProcessRecord;
×
652
  }
653

654
  public void setDoPrintInProcessRecord(boolean doPrintInProcessRecord) {
UNCOV
655
    this.doPrintInProcessRecord = doPrintInProcessRecord;
×
UNCOV
656
  }
×
657

658
  @Override
659
  public boolean readyToProcess() {
660
    return readyToProcess;
1✔
661
  }
662

663
  public List<ValidationError> getAllValidationErrors() {
UNCOV
664
    return allValidationErrors;
×
665
  }
666

667
  public int getRecordCounter() {
NEW
668
    return recordCounter;
×
669
  }
670

671
  public int getNumberOfprocessedRecords() {
672
    return numberOfprocessedRecords;
×
673
  }
674

675
  public ValidatorConfiguration getValidityConfiguration() {
UNCOV
676
    return validatorConfiguration;
×
677
  }
678

679
  private <T extends Object> void updateCounters(T key, Set<String> groupIds, Map<T, Integer> counterSingle, Map<String, Map<T, Integer>> counterGrouped) {
680
    if (doGroups()) {
1✔
681
      for (String groupId : groupIds) {
1✔
682
        counterGrouped.computeIfAbsent(groupId, s -> new TreeMap<>());
1✔
683
        count(key, counterGrouped.get(groupId));
1✔
684
      }
1✔
685
    } else {
686
      count(key, counterSingle);
1✔
687
    }
688
  }
1✔
689
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc