[MPMD-375] Replace *ReportGenerators with a new *ReportRenderers
This closes #130
diff --git a/pom.xml b/pom.xml
index 2e28f68..62a8990 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,7 @@
<pmdVersion>6.55.0</pmdVersion>
<slf4jVersion>1.7.36</slf4jVersion>
<aetherVersion>1.0.0.v20140518</aetherVersion>
+ <doxiaVersion>1.12.0</doxiaVersion>
<compilerPluginVersion>3.11.0</compilerPluginVersion>
<sitePluginVersion>3.12.1</sitePluginVersion>
<projectInfoReportsPluginVersion>3.4.3</projectInfoReportsPluginVersion>
@@ -139,6 +140,12 @@
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-common-artifact-filters</artifactId>
<version>3.3.2</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.sonatype.sisu</groupId>
+ <artifactId>sisu-inject-plexus</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
@@ -190,7 +197,24 @@
<dependency>
<groupId>org.apache.maven.doxia</groupId>
<artifactId>doxia-sink-api</artifactId>
- <version>1.12.0</version>
+ <version>${doxiaVersion}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-container-default</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.doxia</groupId>
+ <artifactId>doxia-core</artifactId>
+ <version>${doxiaVersion}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-container-default</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven.doxia</groupId>
@@ -207,6 +231,14 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-container-default</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-api</artifactId>
+ </exclusion>
</exclusions>
</dependency>
@@ -227,6 +259,17 @@
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-i18n</artifactId>
+ <version>1.0-beta-10</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<!-- test -->
<dependency>
@@ -240,6 +283,12 @@
<artifactId>maven-plugin-testing-harness</artifactId>
<version>3.3.0</version>
<scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-container-default</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java b/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
index e5c4249..c01fdbc 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
@@ -22,10 +22,10 @@
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Properties;
-import java.util.ResourceBundle;
import net.sourceforge.pmd.cpd.JavaTokenizer;
import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
+import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.pmd.exec.CpdExecutor;
@@ -33,6 +33,7 @@
import org.apache.maven.plugins.pmd.exec.CpdResult;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.toolchain.Toolchain;
+import org.codehaus.plexus.i18n.I18N;
/**
* Creates a report for PMD's Copy/Paste Detector (CPD) tool.
@@ -97,24 +98,35 @@
private boolean ignoreAnnotations;
/**
+ * Internationalization component
+ */
+ @Component
+ private I18N i18n;
+
+ /**
* Contains the result of the last CPD execution.
* It might be <code>null</code> which means, that CPD
* has not been executed yet.
*/
private CpdResult cpdResult;
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public String getName(Locale locale) {
- return getBundle(locale).getString("report.cpd.name");
+ return getI18nString(locale, "name");
+ }
+
+ /** {@inheritDoc} */
+ public String getDescription(Locale locale) {
+ return getI18nString(locale, "description");
}
/**
- * {@inheritDoc}
+ * @param locale The locale
+ * @param key The key to search for
+ * @return The text appropriate for the locale.
*/
- public String getDescription(Locale locale) {
- return getBundle(locale).getString("report.cpd.description");
+ protected String getI18nString(Locale locale, String key) {
+ return i18n.getString("cpd-report", locale, "report.cpd." + key);
}
/**
@@ -126,7 +138,9 @@
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
- generateMavenSiteReport(locale);
+ CpdReportRenderer r = new CpdReportRenderer(
+ getSink(), i18n, locale, filesToProcess, cpdResult.getDuplications(), isAggregator());
+ r.render();
} finally {
Thread.currentThread().setContextClassLoader(origLoader);
}
@@ -209,11 +223,6 @@
}
}
- private void generateMavenSiteReport(Locale locale) {
- CpdReportGenerator gen = new CpdReportGenerator(getSink(), filesToProcess, getBundle(locale), isAggregator());
- gen.generate(cpdResult.getDuplications());
- }
-
/**
* {@inheritDoc}
*/
@@ -221,10 +230,6 @@
return "cpd";
}
- private static ResourceBundle getBundle(Locale locale) {
- return ResourceBundle.getBundle("cpd-report", locale, CpdReport.class.getClassLoader());
- }
-
/**
* Create and return the correct renderer for the output type.
*
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java b/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java
deleted file mode 100644
index dd13594..0000000
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.plugins.pmd;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-import java.util.ResourceBundle;
-
-import org.apache.maven.doxia.sink.Sink;
-import org.apache.maven.plugins.pmd.model.CpdFile;
-import org.apache.maven.plugins.pmd.model.Duplication;
-import org.apache.maven.project.MavenProject;
-import org.codehaus.plexus.util.StringUtils;
-
-/**
- * Class that generated the CPD report.
- *
- * @author mperham
- * @version $Id$
- */
-public class CpdReportGenerator {
- private Sink sink;
-
- private Map<File, PmdFileInfo> fileMap;
-
- private ResourceBundle bundle;
-
- private boolean aggregate;
-
- public CpdReportGenerator(Sink sink, Map<File, PmdFileInfo> fileMap, ResourceBundle bundle, boolean aggregate) {
- this.sink = sink;
- this.fileMap = fileMap;
- this.bundle = bundle;
- this.aggregate = aggregate;
- }
-
- /**
- * Method that returns the title of the CPD Report
- *
- * @return a String that contains the title
- */
- private String getTitle() {
- return bundle.getString("report.cpd.title");
- }
-
- /**
- * Method that generates the start of the CPD report.
- */
- public void beginDocument() {
- sink.head();
- sink.title();
- sink.text(getTitle());
- sink.title_();
- sink.head_();
-
- sink.body();
-
- sink.section1();
- sink.sectionTitle1();
- sink.text(getTitle());
- sink.sectionTitle1_();
-
- sink.paragraph();
- sink.text(bundle.getString("report.cpd.cpdlink") + " ");
- sink.link("https://pmd.github.io/latest/pmd_userdocs_cpd.html");
- sink.text("CPD");
- sink.link_();
- sink.text(" " + AbstractPmdReport.getPmdVersion() + ".");
- sink.paragraph_();
-
- sink.section1_();
-
- // TODO overall summary
-
- sink.section1();
- sink.sectionTitle1();
- sink.text(bundle.getString("report.cpd.dupes"));
- sink.sectionTitle1_();
-
- // TODO files summary
- }
-
- /**
- * Method that generates a line of CPD report according to a TokenEntry.
- */
- private void generateFileLine(CpdFile duplicationMark) {
- // Get information for report generation
- String filename = duplicationMark.getPath();
- File file = new File(filename);
- PmdFileInfo fileInfo = fileMap.get(file);
- File sourceDirectory = fileInfo.getSourceDirectory();
- filename = StringUtils.substring(
- filename, sourceDirectory.getAbsolutePath().length() + 1);
- String xrefLocation = fileInfo.getXrefLocation();
- MavenProject projectFile = fileInfo.getProject();
- int line = duplicationMark.getLine();
-
- sink.tableRow();
- sink.tableCell();
- sink.text(filename);
- sink.tableCell_();
- if (aggregate) {
- sink.tableCell();
- sink.text(projectFile.getName());
- sink.tableCell_();
- }
- sink.tableCell();
-
- if (xrefLocation != null) {
- sink.link(xrefLocation + "/"
- + filename.replaceAll("\\.java$", ".html").replace('\\', '/') + "#L" + line);
- }
- sink.text(String.valueOf(line));
- if (xrefLocation != null) {
- sink.link_();
- }
-
- sink.tableCell_();
- sink.tableRow_();
- }
-
- /**
- * Method that generates the contents of the CPD report
- *
- * @param duplications the found duplications
- */
- public void generate(List<Duplication> duplications) {
- beginDocument();
-
- if (duplications.isEmpty()) {
- sink.paragraph();
- sink.text(bundle.getString("report.cpd.noProblems"));
- sink.paragraph_();
- }
-
- for (Duplication duplication : duplications) {
- String code = duplication.getCodefragment();
-
- sink.table();
- sink.tableRows(null, false);
- sink.tableRow();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.cpd.column.file"));
- sink.tableHeaderCell_();
- if (aggregate) {
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.cpd.column.project"));
- sink.tableHeaderCell_();
- }
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.cpd.column.line"));
- sink.tableHeaderCell_();
- sink.tableRow_();
-
- // Iterating on every token entry
- for (CpdFile mark : duplication.getFiles()) {
- generateFileLine(mark);
- }
-
- // Source snippet
- sink.tableRow();
-
- int colspan = 2;
- if (aggregate) {
- ++colspan;
- }
- // TODO Cleaner way to do this?
- sink.rawText("<td colspan='" + colspan + "'>");
- sink.verbatim(null);
- sink.text(code);
- sink.verbatim_();
- sink.rawText("</td>");
- sink.tableRow_();
- sink.tableRows_();
- sink.table_();
- }
-
- sink.section1_();
- sink.body_();
- sink.flush();
- sink.close();
- }
-}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdReportRenderer.java b/src/main/java/org/apache/maven/plugins/pmd/CpdReportRenderer.java
new file mode 100644
index 0000000..58f6076
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdReportRenderer.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.pmd;
+
+import javax.swing.text.html.HTML.Attribute;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.SinkEventAttributes;
+import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
+import org.apache.maven.plugins.pmd.model.CpdFile;
+import org.apache.maven.plugins.pmd.model.Duplication;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.reporting.AbstractMavenReportRenderer;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Class that generated the CPD report.
+ *
+ * @author mperham
+ * @version $Id$
+ */
+public class CpdReportRenderer extends AbstractMavenReportRenderer {
+ private final I18N i18n;
+
+ private final Locale locale;
+
+ private final Map<File, PmdFileInfo> files;
+
+ private final Collection<Duplication> duplications;
+
+ private final boolean aggregate;
+
+ public CpdReportRenderer(
+ Sink sink,
+ I18N i18n,
+ Locale locale,
+ Map<File, PmdFileInfo> files,
+ Collection<Duplication> duplications,
+ boolean aggregate) {
+ super(sink);
+ this.i18n = i18n;
+ this.locale = locale;
+ this.files = files;
+ this.duplications = duplications;
+ this.aggregate = aggregate;
+ }
+
+ @Override
+ public String getTitle() {
+ return getI18nString("title");
+ }
+
+ /**
+ * @param key The key.
+ * @return The translated string.
+ */
+ private String getI18nString(String key) {
+ return i18n.getString("cpd-report", locale, "report.cpd." + key);
+ }
+
+ @Override
+ protected void renderBody() {
+ startSection(getTitle());
+
+ sink.paragraph();
+ sink.text(getI18nString("cpdlink") + " ");
+ link("https://pmd.github.io/latest/pmd_userdocs_cpd.html", "CPD");
+ sink.text(" " + AbstractPmdReport.getPmdVersion() + ".");
+ sink.paragraph_();
+
+ // TODO overall summary
+
+ if (!duplications.isEmpty()) {
+ renderDuplications();
+ } else {
+ paragraph(getI18nString("noProblems"));
+ }
+
+ // TODO files summary
+
+ endSection();
+ }
+
+ /**
+ * Method that generates a line of CPD report according to a TokenEntry.
+ */
+ private void generateFileLine(CpdFile duplicationMark) {
+ // Get information for report generation
+ String filename = duplicationMark.getPath();
+ File file = new File(filename);
+ PmdFileInfo fileInfo = files.get(file);
+ File sourceDirectory = fileInfo.getSourceDirectory();
+ filename = StringUtils.substring(
+ filename, sourceDirectory.getAbsolutePath().length() + 1);
+ String xrefLocation = fileInfo.getXrefLocation();
+ MavenProject projectFile = fileInfo.getProject();
+ int line = duplicationMark.getLine();
+
+ sink.tableRow();
+ tableCell(filename);
+ if (aggregate) {
+ tableCell(projectFile.getName());
+ }
+ sink.tableCell();
+
+ if (xrefLocation != null) {
+ sink.link(xrefLocation + "/"
+ + filename.replaceAll("\\.java$", ".html").replace('\\', '/') + "#L" + line);
+ }
+ sink.text(String.valueOf(line));
+ if (xrefLocation != null) {
+ sink.link_();
+ }
+
+ sink.tableCell_();
+ sink.tableRow_();
+ }
+
+ private void renderDuplications() {
+ startSection(getI18nString("dupes"));
+
+ for (Duplication duplication : duplications) {
+ String code = duplication.getCodefragment();
+
+ startTable();
+ sink.tableRow();
+ tableHeaderCell(getI18nString("column.file"));
+ if (aggregate) {
+ tableHeaderCell(getI18nString("column.project"));
+ }
+ tableHeaderCell(getI18nString("column.line"));
+ sink.tableRow_();
+
+ // Iterating on every token entry
+ for (CpdFile mark : duplication.getFiles()) {
+ generateFileLine(mark);
+ }
+
+ // Source snippet
+ sink.tableRow();
+
+ int colspan = 2;
+ if (aggregate) {
+ colspan = 3;
+ }
+ SinkEventAttributes att = new SinkEventAttributeSet();
+ att.addAttribute(Attribute.COLSPAN, colspan);
+ sink.tableCell(att);
+ verbatimText(code);
+ sink.tableCell_();
+ sink.tableRow_();
+ endTable();
+ }
+
+ endSection();
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
index c3f1065..dab00bb 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
@@ -24,10 +24,8 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.ResourceBundle;
import net.sourceforge.pmd.renderers.Renderer;
-import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@@ -46,6 +44,7 @@
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
import org.apache.maven.toolchain.Toolchain;
+import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceCreationException;
import org.codehaus.plexus.resource.loader.FileResourceLoader;
@@ -238,26 +237,35 @@
private DependencyResolver dependencyResolver;
/**
+ * Internationalization component
+ */
+ @Component
+ private I18N i18n;
+
+ /**
* Contains the result of the last PMD execution.
* It might be <code>null</code> which means, that PMD
* has not been executed yet.
*/
private PmdResult pmdResult;
- /**
- * {@inheritDoc}
- */
- @Override
+ /** {@inheritDoc} */
public String getName(Locale locale) {
- return getBundle(locale).getString("report.pmd.name");
+ return getI18nString(locale, "name");
+ }
+
+ /** {@inheritDoc} */
+ public String getDescription(Locale locale) {
+ return getI18nString(locale, "description");
}
/**
- * {@inheritDoc}
+ * @param locale The locale
+ * @param key The key to search for
+ * @return The text appropriate for the locale.
*/
- @Override
- public String getDescription(Locale locale) {
- return getBundle(locale).getString("report.pmd.description");
+ protected String getI18nString(Locale locale, String key) {
+ return i18n.getString("pmd-report", locale, "report.pmd." + key);
}
/**
@@ -280,7 +288,24 @@
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
- generateMavenSiteReport(locale);
+ PmdReportRenderer r = new PmdReportRenderer(
+ getLog(),
+ getSink(),
+ i18n,
+ locale,
+ filesToProcess,
+ pmdResult.getViolations(),
+ renderRuleViolationPriority,
+ renderViolationsByPriority,
+ isAggregator());
+ if (renderSuppressedViolations) {
+ r.setSuppressedViolations(pmdResult.getSuppressedViolations());
+ }
+ if (renderProcessingErrors) {
+ r.setProcessingErrors(pmdResult.getErrors());
+ }
+
+ r.render();
} finally {
Thread.currentThread().setContextClassLoader(origLoader);
}
@@ -422,29 +447,6 @@
return result;
}
- private void generateMavenSiteReport(Locale locale) throws MavenReportException {
- Sink sink = getSink();
- PmdReportGenerator doxiaRenderer = new PmdReportGenerator(getLog(), sink, getBundle(locale), isAggregator());
- doxiaRenderer.setRenderRuleViolationPriority(renderRuleViolationPriority);
- doxiaRenderer.setRenderViolationsByPriority(renderViolationsByPriority);
- doxiaRenderer.setFiles(filesToProcess);
- doxiaRenderer.setViolations(pmdResult.getViolations());
- if (renderSuppressedViolations) {
- doxiaRenderer.setSuppressedViolations(pmdResult.getSuppressedViolations());
- }
- if (renderProcessingErrors) {
- doxiaRenderer.setProcessingErrors(pmdResult.getErrors());
- }
-
- try {
- doxiaRenderer.beginDocument();
- doxiaRenderer.render();
- doxiaRenderer.endDocument();
- } catch (IOException e) {
- getLog().warn("Failure creating the report: " + e.getLocalizedMessage(), e);
- }
- }
-
/**
* Convenience method to get the location of the specified file name.
*
@@ -551,10 +553,6 @@
return "pmd";
}
- private static ResourceBundle getBundle(Locale locale) {
- return ResourceBundle.getBundle("pmd-report", locale, PmdReport.class.getClassLoader());
- }
-
/**
* Create and return the correct renderer for the output type.
*
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java b/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
deleted file mode 100644
index bb836c9..0000000
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.plugins.pmd;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.ResourceBundle;
-import java.util.Set;
-
-import net.sourceforge.pmd.RulePriority;
-import org.apache.maven.doxia.sink.Sink;
-import org.apache.maven.plugin.logging.Log;
-import org.apache.maven.plugins.pmd.model.ProcessingError;
-import org.apache.maven.plugins.pmd.model.SuppressedViolation;
-import org.apache.maven.plugins.pmd.model.Violation;
-import org.codehaus.plexus.util.StringUtils;
-
-/**
- * Render the PMD violations into Doxia events.
- *
- * @author Brett Porter
- * @version $Id$
- */
-public class PmdReportGenerator {
- private Log log;
-
- private Sink sink;
-
- private String currentFilename;
-
- private ResourceBundle bundle;
-
- private Set<Violation> violations = new HashSet<>();
-
- private List<SuppressedViolation> suppressedViolations = new ArrayList<>();
-
- private List<ProcessingError> processingErrors = new ArrayList<>();
-
- private boolean aggregate;
-
- private boolean renderRuleViolationPriority;
-
- private boolean renderViolationsByPriority;
-
- private Map<File, PmdFileInfo> files;
-
- // private List<Metric> metrics = new ArrayList<Metric>();
-
- public PmdReportGenerator(Log log, Sink sink, ResourceBundle bundle, boolean aggregate) {
- this.log = log;
- this.sink = sink;
- this.bundle = bundle;
- this.aggregate = aggregate;
- }
-
- private String getTitle() {
- return bundle.getString("report.pmd.title");
- }
-
- public void setViolations(Collection<Violation> violations) {
- this.violations = new HashSet<>(violations);
- }
-
- public List<Violation> getViolations() {
- return new ArrayList<>(violations);
- }
-
- public void setSuppressedViolations(Collection<SuppressedViolation> suppressedViolations) {
- this.suppressedViolations = new ArrayList<>(suppressedViolations);
- }
-
- public void setProcessingErrors(Collection<ProcessingError> errors) {
- this.processingErrors = new ArrayList<>(errors);
- }
-
- public List<ProcessingError> getProcessingErrors() {
- return processingErrors;
- }
-
- // public List<Metric> getMetrics()
- // {
- // return metrics;
- // }
- //
- // public void setMetrics( List<Metric> metrics )
- // {
- // this.metrics = metrics;
- // }
-
- private String shortenFilename(String filename, PmdFileInfo fileInfo) {
- String result = filename;
- if (fileInfo != null && fileInfo.getSourceDirectory() != null) {
- result = StringUtils.substring(
- result, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1);
- }
- return StringUtils.replace(result, "\\", "/");
- }
-
- private String makeFileSectionName(String filename, PmdFileInfo fileInfo) {
- if (aggregate && fileInfo != null && fileInfo.getProject() != null) {
- return fileInfo.getProject().getName() + " - " + filename;
- }
- return filename;
- }
-
- private PmdFileInfo determineFileInfo(String filename) throws IOException {
- File canonicalFilename = new File(filename).getCanonicalFile();
- PmdFileInfo fileInfo = files.get(canonicalFilename);
- if (fileInfo == null) {
- log.warn("Couldn't determine PmdFileInfo for file " + filename + " (canonical: " + canonicalFilename
- + "). XRef links won't be available.");
- }
-
- return fileInfo;
- }
-
- private void startFileSection(int level, String currentFilename, PmdFileInfo fileInfo) {
- sink.section(level, null);
- sink.sectionTitle(level, null);
-
- // prepare the filename
- this.currentFilename = shortenFilename(currentFilename, fileInfo);
-
- sink.text(makeFileSectionName(this.currentFilename, fileInfo));
- sink.sectionTitle_(level);
-
- sink.table();
- sink.tableRows(null, false);
- sink.tableRow();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.column.rule"));
- sink.tableHeaderCell_();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.column.violation"));
- sink.tableHeaderCell_();
- if (this.renderRuleViolationPriority) {
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.column.priority"));
- sink.tableHeaderCell_();
- }
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.column.line"));
- sink.tableHeaderCell_();
- sink.tableRow_();
- }
-
- private void endFileSection(int level) {
- sink.tableRows_();
- sink.table_();
- sink.section_(level);
- }
-
- private void addRuleName(Violation ruleViolation) {
- boolean hasUrl = StringUtils.isNotBlank(ruleViolation.getExternalInfoUrl());
-
- if (hasUrl) {
- sink.link(ruleViolation.getExternalInfoUrl());
- }
-
- sink.text(ruleViolation.getRule());
-
- if (hasUrl) {
- sink.link_();
- }
- }
-
- private void processSingleRuleViolation(Violation ruleViolation, PmdFileInfo fileInfo) {
- sink.tableRow();
- sink.tableCell();
- addRuleName(ruleViolation);
- sink.tableCell_();
- sink.tableCell();
- sink.text(ruleViolation.getText());
- sink.tableCell_();
-
- if (this.renderRuleViolationPriority) {
- sink.tableCell();
- sink.text(String.valueOf(
- RulePriority.valueOf(ruleViolation.getPriority()).getPriority()));
- sink.tableCell_();
- }
-
- sink.tableCell();
-
- int beginLine = ruleViolation.getBeginline();
- outputLineLink(beginLine, fileInfo);
- int endLine = ruleViolation.getEndline();
- if (endLine != beginLine) {
- sink.text("–"); // \u2013 is a medium long dash character
- outputLineLink(endLine, fileInfo);
- }
-
- sink.tableCell_();
- sink.tableRow_();
- }
-
- // PMD might run the analysis multi-threaded, so the violations might be reported
- // out of order. We sort them here by filename and line number before writing them to
- // the report.
- private void renderViolations() throws IOException {
- sink.section1();
- sink.sectionTitle1();
- sink.text(bundle.getString("report.pmd.files"));
- sink.sectionTitle1_();
-
- // TODO files summary
-
- List<Violation> violations2 = new ArrayList<>(violations);
- renderViolationsTable(2, violations2);
-
- sink.section1_();
- }
-
- private void renderViolationsByPriority() throws IOException {
- if (!renderViolationsByPriority) {
- return;
- }
-
- boolean oldPriorityColumn = this.renderRuleViolationPriority;
- this.renderRuleViolationPriority = false;
-
- sink.section1();
- sink.sectionTitle1();
- sink.text(bundle.getString("report.pmd.violationsByPriority"));
- sink.sectionTitle1_();
-
- Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
- for (Violation violation : violations) {
- RulePriority priority = RulePriority.valueOf(violation.getPriority());
- List<Violation> violationSegment = violationsByPriority.get(priority);
- if (violationSegment == null) {
- violationSegment = new ArrayList<>();
- violationsByPriority.put(priority, violationSegment);
- }
- violationSegment.add(violation);
- }
-
- for (RulePriority priority : RulePriority.values()) {
- List<Violation> violationsWithPriority = violationsByPriority.get(priority);
- if (violationsWithPriority == null || violationsWithPriority.isEmpty()) {
- continue;
- }
-
- sink.section2();
- sink.sectionTitle2();
- sink.text(bundle.getString("report.pmd.priority") + " " + priority.getPriority());
- sink.sectionTitle2_();
-
- renderViolationsTable(3, violationsWithPriority);
-
- sink.section2_();
- }
-
- if (violations.isEmpty()) {
- sink.paragraph();
- sink.text(bundle.getString("report.pmd.noProblems"));
- sink.paragraph_();
- }
-
- sink.section1_();
-
- this.renderRuleViolationPriority = oldPriorityColumn;
- }
-
- private void renderViolationsTable(int level, List<Violation> violationSegment) throws IOException {
- Collections.sort(violationSegment, new Comparator<Violation>() {
- /** {@inheritDoc} */
- public int compare(Violation o1, Violation o2) {
- int filenames = o1.getFileName().compareTo(o2.getFileName());
- if (filenames == 0) {
- return o1.getBeginline() - o2.getBeginline();
- } else {
- return filenames;
- }
- }
- });
-
- boolean fileSectionStarted = false;
- String previousFilename = null;
- for (Violation ruleViolation : violationSegment) {
- String currentFn = ruleViolation.getFileName();
- PmdFileInfo fileInfo = determineFileInfo(currentFn);
-
- if (!currentFn.equalsIgnoreCase(previousFilename) && fileSectionStarted) {
- endFileSection(level);
- fileSectionStarted = false;
- }
- if (!fileSectionStarted) {
- startFileSection(level, currentFn, fileInfo);
- fileSectionStarted = true;
- }
-
- processSingleRuleViolation(ruleViolation, fileInfo);
-
- previousFilename = currentFn;
- }
-
- if (fileSectionStarted) {
- endFileSection(level);
- }
- }
-
- private void outputLineLink(int line, PmdFileInfo fileInfo) {
- String xrefLocation = null;
- if (fileInfo != null) {
- xrefLocation = fileInfo.getXrefLocation();
- }
-
- if (xrefLocation != null) {
- sink.link(xrefLocation + "/" + currentFilename.replaceAll("\\.java$", ".html") + "#L" + line);
- }
- sink.text(String.valueOf(line));
- if (xrefLocation != null) {
- sink.link_();
- }
- }
-
- // PMD might run the analysis multi-threaded, so the suppressed violations might be reported
- // out of order. We sort them here by filename before writing them to
- // the report.
- private void renderSuppressedViolations() throws IOException {
- sink.section1();
- sink.sectionTitle1();
- sink.text(bundle.getString("report.pmd.suppressedViolations.title"));
- sink.sectionTitle1_();
-
- Collections.sort(suppressedViolations, new Comparator<SuppressedViolation>() {
- @Override
- public int compare(SuppressedViolation o1, SuppressedViolation o2) {
- return o1.getFilename().compareTo(o2.getFilename());
- }
- });
-
- sink.table();
- sink.tableRows(null, false);
- sink.tableRow();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.suppressedViolations.column.filename"));
- sink.tableHeaderCell_();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.suppressedViolations.column.ruleMessage"));
- sink.tableHeaderCell_();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.suppressedViolations.column.suppressionType"));
- sink.tableHeaderCell_();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.suppressedViolations.column.userMessage"));
- sink.tableHeaderCell_();
- sink.tableRow_();
-
- for (SuppressedViolation suppressedViolation : suppressedViolations) {
- String filename = suppressedViolation.getFilename();
- PmdFileInfo fileInfo = determineFileInfo(filename);
- filename = shortenFilename(filename, fileInfo);
-
- sink.tableRow();
-
- sink.tableCell();
- sink.text(filename);
- sink.tableCell_();
-
- sink.tableCell();
- sink.text(suppressedViolation.getRuleMessage());
- sink.tableCell_();
-
- sink.tableCell();
- sink.text(suppressedViolation.getSuppressionType());
- sink.tableCell_();
-
- sink.tableCell();
- sink.text(suppressedViolation.getUserMessage());
- sink.tableCell_();
-
- sink.tableRow_();
- }
-
- sink.tableRows_();
- sink.table_();
- sink.section1_();
- }
-
- private void processProcessingErrors() throws IOException {
- // sort the problem by filename first, since PMD is executed multi-threaded
- // and might reports the results unsorted
- Collections.sort(processingErrors, new Comparator<ProcessingError>() {
- @Override
- public int compare(ProcessingError e1, ProcessingError e2) {
- return e1.getFilename().compareTo(e2.getFilename());
- }
- });
-
- sink.section1();
- sink.sectionTitle1();
- sink.text(bundle.getString("report.pmd.processingErrors.title"));
- sink.sectionTitle1_();
-
- sink.table();
- sink.tableRows(null, false);
- sink.tableRow();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.processingErrors.column.filename"));
- sink.tableHeaderCell_();
- sink.tableHeaderCell();
- sink.text(bundle.getString("report.pmd.processingErrors.column.problem"));
- sink.tableHeaderCell_();
- sink.tableRow_();
-
- for (ProcessingError error : processingErrors) {
- processSingleProcessingError(error);
- }
-
- sink.tableRows_();
- sink.table_();
-
- sink.section1_();
- }
-
- private void processSingleProcessingError(ProcessingError error) throws IOException {
- String filename = error.getFilename();
- PmdFileInfo fileInfo = determineFileInfo(filename);
- filename = makeFileSectionName(shortenFilename(filename, fileInfo), fileInfo);
-
- sink.tableRow();
- sink.tableCell();
- sink.text(filename);
- sink.tableCell_();
- sink.tableCell();
- sink.text(error.getMsg());
- sink.verbatim(null);
- sink.rawText(error.getDetail());
- sink.verbatim_();
- sink.tableCell_();
- sink.tableRow_();
- }
-
- public void beginDocument() {
- sink.head();
- sink.title();
- sink.text(getTitle());
- sink.title_();
- sink.head_();
-
- sink.body();
-
- sink.section1();
- sink.sectionTitle1();
- sink.text(getTitle());
- sink.sectionTitle1_();
-
- sink.paragraph();
- sink.text(bundle.getString("report.pmd.pmdlink") + " ");
- sink.link("https://pmd.github.io");
- sink.text("PMD");
- sink.link_();
- sink.text(" " + AbstractPmdReport.getPmdVersion() + ".");
- sink.paragraph_();
-
- sink.section1_();
-
- // TODO overall summary
- }
-
- /*
- * private void processMetrics() { if ( metrics.size() == 0 ) { return; } sink.section1(); sink.sectionTitle1();
- * sink.text( "Metrics" ); sink.sectionTitle1_(); sink.table(); sink.tableRow(); sink.tableHeaderCell(); sink.text(
- * "Name" ); sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Count" ); sink.tableHeaderCell_();
- * sink.tableHeaderCell(); sink.text( "High" ); sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Low" );
- * sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Average" ); sink.tableHeaderCell_();
- * sink.tableRow_(); for ( Metric met : metrics ) { sink.tableRow(); sink.tableCell(); sink.text(
- * met.getMetricName() ); sink.tableCell_(); sink.tableCell(); sink.text( String.valueOf( met.getCount() ) );
- * sink.tableCell_(); sink.tableCell(); sink.text( String.valueOf( met.getHighValue() ) ); sink.tableCell_();
- * sink.tableCell(); sink.text( String.valueOf( met.getLowValue() ) ); sink.tableCell_(); sink.tableCell();
- * sink.text( String.valueOf( met.getAverage() ) ); sink.tableCell_(); sink.tableRow_(); } sink.table_();
- * sink.section1_(); }
- */
-
- public void render() throws IOException {
- if (!violations.isEmpty()) {
- renderViolationsByPriority();
-
- renderViolations();
- } else {
- sink.paragraph();
- sink.text(bundle.getString("report.pmd.noProblems"));
- sink.paragraph_();
- }
-
- if (!suppressedViolations.isEmpty()) {
- renderSuppressedViolations();
- }
-
- if (!processingErrors.isEmpty()) {
- processProcessingErrors();
- }
- }
-
- public void endDocument() throws IOException {
- // The Metrics report useless with the current PMD metrics impl.
- // For instance, run the coupling ruleset and you will get a boatload
- // of excessive imports metrics, none of which is really any use.
- // TODO Determine if we are going to just ignore metrics.
-
- // processMetrics();
-
- sink.body_();
-
- sink.flush();
-
- sink.close();
- }
-
- public void setFiles(Map<File, PmdFileInfo> files) {
- this.files = files;
- }
-
- public void setRenderRuleViolationPriority(boolean renderRuleViolationPriority) {
- this.renderRuleViolationPriority = renderRuleViolationPriority;
- }
-
- public void setRenderViolationsByPriority(boolean renderViolationsByPriority) {
- this.renderViolationsByPriority = renderViolationsByPriority;
- }
-}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdReportRenderer.java b/src/main/java/org/apache/maven/plugins/pmd/PmdReportRenderer.java
new file mode 100644
index 0000000..3de9146
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReportRenderer.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.pmd;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import net.sourceforge.pmd.RulePriority;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.pmd.model.ProcessingError;
+import org.apache.maven.plugins.pmd.model.SuppressedViolation;
+import org.apache.maven.plugins.pmd.model.Violation;
+import org.apache.maven.reporting.AbstractMavenReportRenderer;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Render the PMD violations into Doxia events.
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class PmdReportRenderer extends AbstractMavenReportRenderer {
+ private final Log log;
+
+ private final I18N i18n;
+
+ private final Locale locale;
+
+ private final Map<File, PmdFileInfo> files;
+
+ // TODO Should not share state
+ private String currentFilename;
+
+ private final Collection<Violation> violations;
+
+ private boolean renderRuleViolationPriority;
+
+ private final boolean renderViolationsByPriority;
+
+ private final boolean aggregate;
+
+ private Collection<SuppressedViolation> suppressedViolations = new ArrayList<>();
+
+ private Collection<ProcessingError> processingErrors = new ArrayList<>();
+
+ public PmdReportRenderer(
+ Log log,
+ Sink sink,
+ I18N i18n,
+ Locale locale,
+ Map<File, PmdFileInfo> files,
+ Collection<Violation> violations,
+ boolean renderRuleViolationPriority,
+ boolean renderViolationsByPriority,
+ boolean aggregate) {
+ super(sink);
+ this.log = log;
+ this.i18n = i18n;
+ this.locale = locale;
+ this.files = files;
+ this.violations = violations;
+ this.renderRuleViolationPriority = renderRuleViolationPriority;
+ this.renderViolationsByPriority = renderViolationsByPriority;
+ this.aggregate = aggregate;
+ }
+
+ public void setSuppressedViolations(Collection<SuppressedViolation> suppressedViolations) {
+ this.suppressedViolations = suppressedViolations;
+ }
+
+ public void setProcessingErrors(Collection<ProcessingError> processingErrors) {
+ this.processingErrors = processingErrors;
+ }
+
+ @Override
+ public String getTitle() {
+ return getI18nString("title");
+ }
+
+ /**
+ * @param key The key.
+ * @return The translated string.
+ */
+ private String getI18nString(String key) {
+ return i18n.getString("pmd-report", locale, "report.pmd." + key);
+ }
+
+ public void renderBody() {
+ startSection(getTitle());
+
+ sink.paragraph();
+ sink.text(getI18nString("pmdlink") + " ");
+ link("https://pmd.github.io", "PMD");
+ sink.text(" " + AbstractPmdReport.getPmdVersion() + ".");
+ sink.paragraph_();
+
+ if (!violations.isEmpty()) {
+ renderViolationsByPriority();
+
+ renderViolations();
+ } else {
+ paragraph(getI18nString("noProblems"));
+ }
+
+ renderSuppressedViolations();
+
+ renderProcessingErrors();
+
+ endSection();
+ }
+
+ private void startFileSection(String currentFilename, PmdFileInfo fileInfo) {
+ // prepare the filename
+ this.currentFilename = shortenFilename(currentFilename, fileInfo);
+
+ startSection(makeFileSectionName(this.currentFilename, fileInfo));
+
+ startTable();
+ sink.tableRow();
+ tableHeaderCell(getI18nString("column.rule"));
+ tableHeaderCell(getI18nString("column.violation"));
+ if (this.renderRuleViolationPriority) {
+ tableHeaderCell(getI18nString("column.priority"));
+ }
+ tableHeaderCell(getI18nString("column.line"));
+ sink.tableRow_();
+ }
+
+ private void endFileSection() {
+ endTable();
+ endSection();
+ }
+
+ private void addRuleName(Violation ruleViolation) {
+ boolean hasUrl = StringUtils.isNotBlank(ruleViolation.getExternalInfoUrl());
+
+ if (hasUrl) {
+ sink.link(ruleViolation.getExternalInfoUrl());
+ }
+
+ sink.text(ruleViolation.getRule());
+
+ if (hasUrl) {
+ sink.link_();
+ }
+ }
+
+ private void renderSingleRuleViolation(Violation ruleViolation, PmdFileInfo fileInfo) {
+ sink.tableRow();
+ sink.tableCell();
+ addRuleName(ruleViolation);
+ sink.tableCell_();
+ tableCell(ruleViolation.getText());
+
+ if (this.renderRuleViolationPriority) {
+ tableCell(String.valueOf(
+ RulePriority.valueOf(ruleViolation.getPriority()).getPriority()));
+ }
+
+ sink.tableCell();
+
+ int beginLine = ruleViolation.getBeginline();
+ outputLineLink(beginLine, fileInfo);
+ int endLine = ruleViolation.getEndline();
+ if (endLine != beginLine) {
+ sink.text("–"); // \u2013 is a medium long dash character
+ outputLineLink(endLine, fileInfo);
+ }
+
+ sink.tableCell_();
+ sink.tableRow_();
+ }
+
+ // PMD might run the analysis multi-threaded, so the violations might be reported
+ // out of order. We sort them here by filename and line number before writing them to
+ // the report.
+ private void renderViolations() {
+ startSection(getI18nString("files"));
+
+ // TODO files summary
+ renderViolationsTable(violations);
+
+ endSection();
+ }
+
+ private void renderViolationsByPriority() {
+ if (!renderViolationsByPriority) {
+ return;
+ }
+
+ boolean oldPriorityColumn = this.renderRuleViolationPriority;
+ this.renderRuleViolationPriority = false;
+
+ startSection(getI18nString("violationsByPriority"));
+
+ Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
+ for (Violation violation : violations) {
+ RulePriority priority = RulePriority.valueOf(violation.getPriority());
+ List<Violation> violationSegment = violationsByPriority.get(priority);
+ if (violationSegment == null) {
+ violationSegment = new ArrayList<>();
+ violationsByPriority.put(priority, violationSegment);
+ }
+ violationSegment.add(violation);
+ }
+
+ for (RulePriority priority : RulePriority.values()) {
+ List<Violation> violationsWithPriority = violationsByPriority.get(priority);
+ if (violationsWithPriority == null || violationsWithPriority.isEmpty()) {
+ continue;
+ }
+
+ startSection(getI18nString("priority") + " " + priority.getPriority());
+
+ renderViolationsTable(violationsWithPriority);
+
+ endSection();
+ }
+
+ if (violations.isEmpty()) {
+ paragraph(getI18nString("noProblems"));
+ }
+
+ endSection();
+
+ this.renderRuleViolationPriority = oldPriorityColumn;
+ }
+
+ private void renderViolationsTable(Collection<Violation> violationSegment) {
+ List<Violation> violationSegmentCopy = new ArrayList<>(violationSegment);
+ Collections.sort(violationSegmentCopy, new Comparator<Violation>() {
+ /** {@inheritDoc} */
+ public int compare(Violation o1, Violation o2) {
+ int filenames = o1.getFileName().compareTo(o2.getFileName());
+ if (filenames == 0) {
+ return o1.getBeginline() - o2.getBeginline();
+ } else {
+ return filenames;
+ }
+ }
+ });
+
+ boolean fileSectionStarted = false;
+ String previousFilename = null;
+ for (Violation ruleViolation : violationSegmentCopy) {
+ String currentFn = ruleViolation.getFileName();
+ PmdFileInfo fileInfo = determineFileInfo(currentFn);
+
+ if (!currentFn.equalsIgnoreCase(previousFilename) && fileSectionStarted) {
+ endFileSection();
+ fileSectionStarted = false;
+ }
+ if (!fileSectionStarted) {
+ startFileSection(currentFn, fileInfo);
+ fileSectionStarted = true;
+ }
+
+ renderSingleRuleViolation(ruleViolation, fileInfo);
+
+ previousFilename = currentFn;
+ }
+
+ if (fileSectionStarted) {
+ endFileSection();
+ }
+ }
+
+ private void outputLineLink(int line, PmdFileInfo fileInfo) {
+ String xrefLocation = null;
+ if (fileInfo != null) {
+ xrefLocation = fileInfo.getXrefLocation();
+ }
+
+ if (xrefLocation != null) {
+ sink.link(xrefLocation + "/" + currentFilename.replaceAll("\\.java$", ".html") + "#L" + line);
+ }
+ sink.text(String.valueOf(line));
+ if (xrefLocation != null) {
+ sink.link_();
+ }
+ }
+
+ // PMD might run the analysis multi-threaded, so the suppressed violations might be reported
+ // out of order. We sort them here by filename before writing them to
+ // the report.
+ private void renderSuppressedViolations() {
+ if (suppressedViolations.isEmpty()) {
+ return;
+ }
+
+ startSection(getI18nString("suppressedViolations.title"));
+
+ List<SuppressedViolation> suppressedViolationsCopy = new ArrayList<>(suppressedViolations);
+ Collections.sort(suppressedViolationsCopy, new Comparator<SuppressedViolation>() {
+ @Override
+ public int compare(SuppressedViolation o1, SuppressedViolation o2) {
+ return o1.getFilename().compareTo(o2.getFilename());
+ }
+ });
+
+ startTable();
+ tableHeader(new String[] {
+ getI18nString("suppressedViolations.column.filename"),
+ getI18nString("suppressedViolations.column.ruleMessage"),
+ getI18nString("suppressedViolations.column.suppressionType"),
+ getI18nString("suppressedViolations.column.userMessage")
+ });
+
+ for (SuppressedViolation suppressedViolation : suppressedViolationsCopy) {
+ String filename = suppressedViolation.getFilename();
+ PmdFileInfo fileInfo = determineFileInfo(filename);
+ filename = shortenFilename(filename, fileInfo);
+
+ tableRow(new String[] {
+ filename,
+ suppressedViolation.getRuleMessage(),
+ suppressedViolation.getSuppressionType(),
+ suppressedViolation.getUserMessage()
+ });
+ }
+
+ endTable();
+ endSection();
+ }
+
+ private void renderProcessingErrors() {
+ if (processingErrors.isEmpty()) {
+ return;
+ }
+
+ // sort the problem by filename first, since PMD is executed multi-threaded
+ // and might reports the results unsorted
+ List<ProcessingError> processingErrorsCopy = new ArrayList<>(processingErrors);
+ Collections.sort(processingErrorsCopy, new Comparator<ProcessingError>() {
+ @Override
+ public int compare(ProcessingError e1, ProcessingError e2) {
+ return e1.getFilename().compareTo(e2.getFilename());
+ }
+ });
+
+ startSection(getI18nString("processingErrors.title"));
+
+ startTable();
+ tableHeader(new String[] {
+ getI18nString("processingErrors.column.filename"), getI18nString("processingErrors.column.problem")
+ });
+
+ for (ProcessingError error : processingErrorsCopy) {
+ renderSingleProcessingError(error);
+ }
+
+ endTable();
+ endSection();
+ }
+
+ private void renderSingleProcessingError(ProcessingError error) {
+ String filename = error.getFilename();
+ PmdFileInfo fileInfo = determineFileInfo(filename);
+ filename = makeFileSectionName(shortenFilename(filename, fileInfo), fileInfo);
+
+ sink.tableRow();
+ tableCell(filename);
+ sink.tableCell();
+ sink.text(error.getMsg());
+ sink.verbatim(null);
+ sink.rawText(error.getDetail());
+ sink.verbatim_();
+ sink.tableCell_();
+ sink.tableRow_();
+ }
+
+ private String shortenFilename(String filename, PmdFileInfo fileInfo) {
+ String result = filename;
+ if (fileInfo != null && fileInfo.getSourceDirectory() != null) {
+ result = StringUtils.substring(
+ result, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1);
+ }
+ return StringUtils.replace(result, "\\", "/");
+ }
+
+ private String makeFileSectionName(String filename, PmdFileInfo fileInfo) {
+ if (aggregate && fileInfo != null && fileInfo.getProject() != null) {
+ return fileInfo.getProject().getName() + " - " + filename;
+ }
+ return filename;
+ }
+
+ private PmdFileInfo determineFileInfo(String filename) {
+ try {
+ File canonicalFilename = new File(filename).getCanonicalFile();
+ PmdFileInfo fileInfo = files.get(canonicalFilename);
+ if (fileInfo == null) {
+ log.warn("Couldn't determine PmdFileInfo for file " + filename + " (canonical: " + canonicalFilename
+ + "). XRef links won't be available.");
+ }
+ return fileInfo;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java b/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
index eed633a..7bde404 100644
--- a/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
+++ b/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
@@ -86,9 +86,9 @@
assertTrue(str.contains("pmd_rules_java_bestpractices.html#unusedprivatefield\">UnusedPrivateField</a>"));
// there should be the section Violations By Priority
- assertTrue(str.contains("Violations By Priority</h2>"));
- assertTrue(str.contains("Priority 3</h3>"));
- assertTrue(str.contains("Priority 4</h3>"));
+ assertTrue(str.contains("Violations By Priority</h3>"));
+ assertTrue(str.contains("Priority 3</h4>"));
+ assertTrue(str.contains("Priority 4</h4>"));
// the file App.java is mentioned 3 times: in prio 3, in prio 4 and in the files section
assertEquals(3, StringUtils.countMatches(str, "def/configuration/App.java"));
@@ -605,7 +605,7 @@
String str = readFile(generatedReport);
// custom rule without link
- assertEquals(2, StringUtils.countMatches(str, "<td>CustomRule</td>"));
+ assertEquals(2, StringUtils.countMatches(str, "<td align=\"left\">CustomRule</td>"));
// standard rule with link
assertEquals(4, StringUtils.countMatches(str, "\">UnusedPrivateField</a></td>"));
}