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

pmd / pmd / 415

27 Feb 2026 12:39PM UTC coverage: 79.038% (+0.03%) from 79.004%
415

push

github

adangel
[release] Prepare next development version

18604 of 24437 branches covered (76.13%)

Branch coverage included in aggregate %.

40598 of 50466 relevant lines covered (80.45%)

0.81 hits per line

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

95.43
/pmd-core/src/main/java/net/sourceforge/pmd/renderers/YAHTMLRenderer.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.renderers;
6

7
import java.io.File;
8
import java.io.IOException;
9
import java.io.PrintWriter;
10
import java.net.URLEncoder;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.Files;
13
import java.util.LinkedList;
14
import java.util.List;
15
import java.util.SortedMap;
16
import java.util.TreeMap;
17

18
import org.apache.commons.lang3.StringEscapeUtils;
19
import org.apache.commons.lang3.StringUtils;
20

21
import net.sourceforge.pmd.properties.PropertyDescriptor;
22
import net.sourceforge.pmd.properties.PropertyFactory;
23
import net.sourceforge.pmd.reporting.Report;
24
import net.sourceforge.pmd.reporting.RuleViolation;
25
import net.sourceforge.pmd.util.StringUtil;
26

27
/**
28
 * Renderer to another HTML format.
29
 */
30
public class YAHTMLRenderer extends AbstractAccumulatingRenderer {
31

32
    public static final String NAME = "yahtml";
33
    public static final PropertyDescriptor<String> OUTPUT_DIR =
1✔
34
        PropertyFactory.stringProperty("outputDir")
1✔
35
                       .desc("Output directory.")
1✔
36
                       .defaultValue(".")
1✔
37
                       .build();
1✔
38

39
    private SortedMap<String, ReportNode> reportNodesByPackage = new TreeMap<>();
1✔
40

41
    public YAHTMLRenderer() {
42
        // YA = Yet Another?
43
        super(NAME, "Yet Another HTML format.");
1✔
44
        definePropertyDescriptor(OUTPUT_DIR);
1✔
45
    }
1✔
46

47
    @Override
48
    public String defaultFileExtension() {
49
        return "html";
×
50
    }
51

52
    private static String escape(String s) {
53
        return StringEscapeUtils.escapeHtml4(s);
1✔
54
    }
55

56
    private void addViolation(RuleViolation violation) {
57
        String packageName = violation.getAdditionalInfo().getOrDefault(RuleViolation.PACKAGE_NAME, "");
1✔
58
        String className = violation.getAdditionalInfo().getOrDefault(RuleViolation.CLASS_NAME, "");
1✔
59

60
        // report each part of the package name: e.g. net.sf.pmd.test will create nodes for
61
        // net, net.sf, net.sf.pmd, and net.sf.pmd.test
62
        int index = packageName.indexOf('.', 0);
1✔
63
        while (index > -1) {
1✔
64
            String currentPackage = packageName.substring(0, index);
1✔
65
            ReportNode reportNode = reportNodesByPackage.get(currentPackage);
1✔
66
            if (reportNode == null) {
1✔
67
                reportNode = new ReportNode(currentPackage);
1✔
68
                reportNodesByPackage.put(currentPackage, reportNode);
1✔
69
            }
70
            reportNode.incrementViolations();
1✔
71

72
            int oldIndex = index;
1✔
73
            index = packageName.indexOf('.', index + 1);
1✔
74
            if (index == -1 && oldIndex != packageName.length()) {
1✔
75
                index = packageName.length();
1✔
76
            }
77
        }
1✔
78

79
        // add one node per class collecting the actual violations
80
        String fqClassName = packageName + "." + className;
1✔
81
        ReportNode classNode = reportNodesByPackage.get(fqClassName);
1✔
82
        if (classNode == null) {
1✔
83
            classNode = new ReportNode(packageName, className);
1✔
84
            reportNodesByPackage.put(fqClassName, classNode);
1✔
85
        }
86
        classNode.addRuleViolation(violation);
1✔
87

88
        // count the overall violations in the root node
89
        ReportNode rootNode = reportNodesByPackage.get(ReportNode.ROOT_NODE_NAME);
1✔
90
        if (rootNode == null) {
1✔
91
            rootNode = new ReportNode("Aggregate");
1✔
92
            reportNodesByPackage.put(ReportNode.ROOT_NODE_NAME, rootNode);
1✔
93
        }
94
        rootNode.incrementViolations();
1✔
95
    }
1✔
96

97
    @Override
98
    public void outputReport(Report report) throws IOException {
99
        String outputDir = getProperty(OUTPUT_DIR);
1✔
100

101
        for (RuleViolation ruleViolation : report.getViolations()) {
1✔
102
            addViolation(ruleViolation);
1✔
103
        }
1✔
104

105
        renderIndex(outputDir);
1✔
106
        renderClasses(outputDir);
1✔
107

108
        writer.println("<h3 align=\"center\">The HTML files are located "
1✔
109
                + (outputDir == null ? "above the project directory" : "in '" + outputDir + '\'') + ".</h3>");
1!
110
    }
1✔
111

112
    private void renderIndex(String outputDir) throws IOException {
113
        try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(new File(outputDir, "index.html").toPath(), StandardCharsets.UTF_8))) {
1✔
114
            out.println("<!DOCTYPE html>");
1✔
115
            out.println("<html>");
1✔
116
            out.println("    <head>");
1✔
117
            out.println("        <meta charset=\"UTF-8\">");
1✔
118
            out.println("        <title>PMD</title>");
1✔
119
            out.println("    </head>");
1✔
120
            out.println("    <body>");
1✔
121
            out.println("    <h2>Package View</h2>");
1✔
122
            out.println("    <table border=\"1\" align=\"center\" cellspacing=\"0\" cellpadding=\"3\">");
1✔
123
            out.println("        <tr><th>Package</th><th>Class</th><th>#</th></tr>");
1✔
124

125
            for (ReportNode node : reportNodesByPackage.values()) {
1✔
126
                out.print("        <tr><td><b>");
1✔
127
                out.print(escape(node.getPackageName()));
1✔
128
                out.print("</b></td> <td>");
1✔
129
                if (node.hasViolations()) {
1✔
130
                    out.print("<a href=\"");
1✔
131
                    out.print(URLEncoder.encode(node.getClassName(), "UTF-8"));
1✔
132
                    out.print(".html");
1✔
133
                    out.print("\">");
1✔
134
                    out.print(escape(node.getClassName()));
1✔
135
                    out.print("</a>");
1✔
136
                } else {
137
                    out.print(escape(node.getClassName()));
1✔
138
                }
139
                out.print("</td> <td>");
1✔
140
                out.print(node.getViolationCount());
1✔
141
                out.print("</td></tr>");
1✔
142
                out.println();
1✔
143
            }
1✔
144

145
            out.println("    </table>");
1✔
146
            out.println("    </body>");
1✔
147
            out.println("</html>");
1✔
148
        }
149
    }
1✔
150

151
    private void renderClasses(String outputDir) throws IOException {
152
        for (ReportNode node : reportNodesByPackage.values()) {
1✔
153
            if (node.hasViolations()) {
1✔
154
                try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(new File(outputDir, node.getClassName() + ".html").toPath(), StandardCharsets.UTF_8))) {
1✔
155
                    out.println("<!DOCTYPE html>");
1✔
156
                    out.println("<html>");
1✔
157
                    out.println("    <head>");
1✔
158
                    out.println("        <meta charset=\"UTF-8\">");
1✔
159
                    out.print("        <title>PMD - ");
1✔
160
                    out.print(escape(node.getClassName()));
1✔
161
                    out.println("</title>");
1✔
162
                    out.println("    </head>");
1✔
163
                    out.println("    <body>");
1✔
164
                    out.println("        <h2>Class View</h2>");
1✔
165
                    out.print("        <h3 align=\"center\">Class: ");
1✔
166
                    out.print(escape(node.getClassName()));
1✔
167
                    out.println("</h3>");
1✔
168
                    out.println("        <table border=\"\" align=\"center\" cellspacing=\"0\" cellpadding=\"3\">");
1✔
169
                    out.println("        <tr><th>Method</th><th>Violation</th></tr>");
1✔
170
                    for (RuleViolation violation : node.getViolations()) {
1✔
171
                        out.print("        <tr><td>");
1✔
172
                        String methodName = violation.getAdditionalInfo().get(RuleViolation.METHOD_NAME);
1✔
173
                        out.print(escape(StringUtil.nullToEmpty(methodName)));
1✔
174
                        out.print("</td><td>");
1✔
175
                        out.print("<table border=\"0\">");
1✔
176

177
                        out.print(renderViolationRowEscaped("Rule:", violation.getRule().getName()));
1✔
178
                        out.print(renderViolationRowEscaped("Description:", violation.getDescription()));
1✔
179

180
                        String variableName = violation.getAdditionalInfo().get(RuleViolation.VARIABLE_NAME);
1✔
181
                        if (StringUtils.isNotBlank(variableName)) {
1!
182
                            out.print(renderViolationRowEscaped("Variable:", variableName));
×
183
                        }
184

185
                        out.print(renderViolationRowEscaped("Line:", violation.getEndLine() > 0
1!
186
                                ? violation.getBeginLine() + " and " + violation.getEndLine()
1✔
187
                                : String.valueOf(violation.getBeginLine())));
×
188

189
                        out.print("</table>");
1✔
190

191
                        out.print("</td></tr>");
1✔
192
                        out.println();
1✔
193
                    }
1✔
194
                    out.println("        </table>");
1✔
195
                    out.println("    </body>");
1✔
196
                    out.println("</html>");
1✔
197
                }
198
            }
199
        }
1✔
200
    }
1✔
201

202
    private String renderViolationRowEscaped(String name, String value) {
203
        return "<tr><td><b>"
1✔
204
            + escape(name)
1✔
205
            + "</b></td>"
206
            + "<td>"
207
            + escape(value)
1✔
208
            + "</td></tr>";
209
    }
210

211
    private static class ReportNode {
212
        // deliberately starts with a space, so that it is sorted before the packages
213
        private static final String ROOT_NODE_NAME = " <root> ";
214

215
        private final String packageName;
216
        private final String className;
217
        private int violationCount;
218
        private final List<RuleViolation> violations = new LinkedList<>();
1✔
219

220
        ReportNode(String packageName) {
1✔
221
            this.packageName = packageName;
1✔
222
            this.className = "-";
1✔
223
        }
1✔
224

225
        ReportNode(String packageName, String className) {
1✔
226
            this.packageName = packageName;
1✔
227
            this.className = className;
1✔
228
        }
1✔
229

230
        public void incrementViolations() {
231
            violationCount++;
1✔
232
        }
1✔
233

234
        public void addRuleViolation(RuleViolation violation) {
235
            violations.add(violation);
1✔
236
        }
1✔
237

238
        public String getPackageName() {
239
            return packageName;
1✔
240
        }
241

242
        public String getClassName() {
243
            return className;
1✔
244
        }
245

246
        public int getViolationCount() {
247
            return violationCount + violations.size();
1✔
248
        }
249

250
        public List<RuleViolation> getViolations() {
251
            return violations;
1✔
252
        }
253

254
        public boolean hasViolations() {
255
            return !violations.isEmpty();
1✔
256
        }
257

258
        @Override
259
        public String toString() {
260
            return "ReportNode[packageName=" + packageName
×
261
                + ",className=" + className
262
                + ",violationCount=" + violationCount
263
                + ",violations=" + violations.size()
×
264
                + "]";
265
        }
266
    }
267
}
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