1 /*
2 * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3 * SPDX-License-Identifier: MIT
4 */
5 package com.qulice.maven;
6
7 import com.qulice.spi.ResourceValidator;
8 import java.io.File;
9 import java.util.Collection;
10 import java.util.LinkedHashMap;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.stream.Collectors;
14
15 /**
16 * The scope of a finished check, as one line of text.
17 *
18 * <p>Tells the reader what the run has just covered: how many Java files
19 * went through the validators and how many rules judged them, validator
20 * by validator. A validator that cannot count its rules stays out of the
21 * breakdown, instead of claiming zero, and a module that had no file to
22 * give them counts no rules at all.</p>
23 *
24 * @since 1.0
25 */
26 final class Summary {
27
28 /**
29 * Files handed to the validators.
30 */
31 private final Collection<File> files;
32
33 /**
34 * Validators that read them.
35 */
36 private final Collection<ResourceValidator> validators;
37
38 /**
39 * Constructor.
40 * @param files Files handed to the validators
41 * @param validators Validators that read them
42 */
43 Summary(final Collection<File> files,
44 final Collection<ResourceValidator> validators) {
45 this.files = files;
46 this.validators = validators;
47 }
48
49 @Override
50 public String toString() {
51 final Map<String, Integer> counts = this.counts();
52 return String.format(
53 "checked %s against %d rules%s",
54 this.javas(),
55 counts.values().stream().mapToInt(Integer::intValue).sum(),
56 Summary.breakdown(counts)
57 );
58 }
59
60 /**
61 * How many rules each validator applies, by validator name, leaving
62 * out the ones that count none.
63 *
64 * <p>A module without a single file to read runs no validator at all,
65 * so nobody is asked to count: the counting itself is expensive, since
66 * it makes Checkstyle load its configuration and PMD resolve its
67 * ruleset.</p>
68 *
69 * @return Rule counts, in the order the validators ran
70 */
71 private Map<String, Integer> counts() {
72 final Map<String, Integer> counts =
73 new LinkedHashMap<>(this.validators.size());
74 if (!this.files.isEmpty()) {
75 for (final ResourceValidator validator : this.validators) {
76 final int rules = validator.rules();
77 if (rules > 0) {
78 counts.put(validator.name(), rules);
79 }
80 }
81 }
82 return counts;
83 }
84
85 /**
86 * The Java files among them, counted and named.
87 * @return Text, e.g. {@code "42 .java files"}
88 */
89 private String javas() {
90 final long count = this.files.stream().filter(
91 file -> file.getName().toLowerCase(Locale.ROOT).endsWith(".java")
92 ).count();
93 final String text;
94 if (count == 1L) {
95 text = "1 .java file";
96 } else {
97 text = String.format("%d .java files", count);
98 }
99 return text;
100 }
101
102 /**
103 * Spell the rule counts out, validator by validator.
104 * @param counts Rule counts by validator name
105 * @return Text in brackets, e.g. {@code " (253 Checkstyle, 132 PMD)"},
106 * or empty when nobody counted anything
107 */
108 private static String breakdown(final Map<String, Integer> counts) {
109 final String text;
110 if (counts.isEmpty()) {
111 text = "";
112 } else {
113 text = counts.entrySet().stream().map(
114 entry -> String.format("%d %s", entry.getValue(), entry.getKey())
115 ).collect(Collectors.joining(", ", " (", ")"));
116 }
117 return text;
118 }
119 }