Merge pull request #59 from adangel:MPMD-309

[MPMD-309] Add configuration option to show suppressed violations
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java b/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java
index 2370ead..12ada60 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java
@@ -22,12 +22,15 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.codehaus.plexus.util.StringUtils;
 
 import net.sourceforge.pmd.Report;
 import net.sourceforge.pmd.Report.ProcessingError;
+import net.sourceforge.pmd.Report.SuppressedViolation;
 import net.sourceforge.pmd.RuleViolation;
 import net.sourceforge.pmd.renderers.AbstractRenderer;
 import net.sourceforge.pmd.util.datasource.DataSource;
@@ -41,8 +44,9 @@
  */
 public class PmdCollectingRenderer extends AbstractRenderer
 {
-    private List<ProcessingError> errors = Collections.synchronizedList( new ArrayList<ProcessingError>() );
-    private List<RuleViolation> violations = Collections.synchronizedList( new ArrayList<RuleViolation>() );
+    private List<ProcessingError> errors = Collections.synchronizedList( new ArrayList<>() );
+    private List<RuleViolation> violations = Collections.synchronizedList( new ArrayList<>() );
+    private List<SuppressedViolation> suppressed = Collections.synchronizedList( new ArrayList<> () );
 
     /**
      * Collects all reports from all threads.
@@ -57,6 +61,7 @@
     {
         violations.addAll( report.getViolations() );
         errors.addAll( report.getProcessingErrors() );
+        suppressed.addAll( report.getSuppressedViolations() );
     }
 
     /**
@@ -129,6 +134,19 @@
         {
             report.addError( e );
         }
+        Map<Integer, String> suppressedLines = new HashMap<Integer, String>();
+        for ( SuppressedViolation s : suppressed )
+        {
+            if ( s.suppressedByNOPMD() )
+            {
+                suppressedLines.put( s.getRuleViolation().getBeginLine(), s.getUserMessage() );
+            }
+        }
+        report.suppress( suppressedLines );
+        for ( SuppressedViolation s : suppressed )
+        {
+            report.addRuleViolation( s.getRuleViolation() );
+        }
         return report;
     }
 
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 2c68e0f..124deb9 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
@@ -217,6 +217,14 @@
     private boolean renderViolationsByPriority = true;
 
     /**
+     * Add a section in the HTML report that lists the suppressed violations.
+     *
+     * @since 3.17.0
+     */
+    @Parameter( property = "pmd.renderSuppressedViolations", defaultValue = "true" )
+    private boolean renderSuppressedViolations = true;
+
+    /**
      * Before PMD is executed, the configured rulesets are resolved and copied into this directory.
      * <p>Note: Before 3.13.0, this was by default ${project.build.directory}.
      *
@@ -501,6 +509,10 @@
         doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
         doxiaRenderer.setFiles( filesToProcess );
         doxiaRenderer.setViolations( pmdResult.getViolations() );
+        if ( renderSuppressedViolations )
+        {
+            doxiaRenderer.setSuppressedViolations( pmdResult.getSuppressedViolations() );
+        }
         if ( renderProcessingErrors )
         {
             doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java b/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
index a094363..e266cb5 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
@@ -35,6 +35,7 @@
 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;
 
@@ -58,6 +59,8 @@
 
     private Set<Violation> violations = new HashSet<>();
 
+    private List<SuppressedViolation> suppressedViolations = new ArrayList<>();
+
     private List<ProcessingError> processingErrors = new ArrayList<>();
 
     private boolean aggregate;
@@ -93,6 +96,11 @@
         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 );
@@ -377,6 +385,73 @@
         }
     }
 
+    // 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.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.table_();
+        sink.section1_();
+    }
+
     private void processProcessingErrors() throws IOException
     {
         // sort the problem by filename first, since PMD is executed multi-threaded
@@ -492,6 +567,11 @@
             sink.paragraph_();
         }
 
+        if ( !suppressedViolations.isEmpty() )
+        {
+            renderSuppressedViolations();
+        }
+
         if ( !processingErrors.isEmpty() )
         {
             processProcessingErrors();
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java
index 93645d7..667b849 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java
@@ -32,6 +32,7 @@
 import org.apache.maven.plugins.pmd.model.PmdErrorDetail;
 import org.apache.maven.plugins.pmd.model.PmdFile;
 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.plugins.pmd.model.io.xpp3.PmdXpp3Reader;
 import org.apache.maven.reporting.MavenReportException;
@@ -43,6 +44,7 @@
 {
     private final List<ProcessingError> processingErrors = new ArrayList<>();
     private final List<Violation> violations = new ArrayList<>();
+    private final List<SuppressedViolation> suppressedViolations = new ArrayList<>();
 
     public static final PmdResult EMPTY = new PmdResult();
 
@@ -68,6 +70,7 @@
             PmdXpp3Reader reader = new PmdXpp3Reader();
             PmdErrorDetail details = reader.read( reader1, false );
             processingErrors.addAll( details.getErrors() );
+            suppressedViolations.addAll( details.getSuppressedViolations() );
 
             for ( PmdFile file : details.getFiles() )
             {
@@ -147,6 +150,11 @@
         return violations;
     }
 
+    public Collection<SuppressedViolation> getSuppressedViolations()
+    {
+        return suppressedViolations;
+    }
+
     public Collection<ProcessingError> getErrors()
     {
         return processingErrors;
diff --git a/src/main/mdo/pmd.mdo b/src/main/mdo/pmd.mdo
index 2c2a084..f41ee3c 100644
--- a/src/main/mdo/pmd.mdo
+++ b/src/main/mdo/pmd.mdo
@@ -46,6 +46,13 @@
           </association>
         </field>
         <field>
+          <name>suppressedViolations</name>
+          <association xml.tagName="suppressedviolation" xml.itemsStyle="flat">
+            <type>SuppressedViolation</type>
+            <multiplicity>*</multiplicity>
+          </association>
+        </field>
+        <field>
           <name>errors</name>
           <association xml.tagName="error" xml.itemsStyle="flat">
             <type>ProcessingError</type>
@@ -127,6 +134,27 @@
       </codeSegments>
     </class>
     <class>
+        <name>SuppressedViolation</name>
+        <fields>
+            <field xml.attribute="true">
+                <name>filename</name>
+                <type>String</type>
+            </field>
+            <field xml.tagName="suppressiontype" xml.attribute="true">
+                <name>suppressionType</name>
+                <type>String</type>
+            </field>
+            <field xml.tagName="msg" xml.attribute="true">
+                <name>ruleMessage</name>
+                <type>String</type>
+            </field>
+            <field xml.tagName="usermsg" xml.attribute="true">
+                <name>userMessage</name>
+                <type>String</type>
+            </field>
+        </fields>
+    </class>
+    <class>
         <name>ProcessingError</name>
         <fields>
             <field xml.attribute="true">
diff --git a/src/main/resources/pmd-report.properties b/src/main/resources/pmd-report.properties
index afe9460..98858b9 100644
--- a/src/main/resources/pmd-report.properties
+++ b/src/main/resources/pmd-report.properties
@@ -27,6 +27,11 @@
 report.pmd.violationsByPriority=Violations By Priority
 report.pmd.priority=Priority
 report.pmd.noProblems=PMD found no problems in your source code.
+report.pmd.suppressedViolations.title=Suppressed Violations
+report.pmd.suppressedViolations.column.filename=Filename
+report.pmd.suppressedViolations.column.ruleMessage=Rule message
+report.pmd.suppressedViolations.column.suppressionType=Suppression type
+report.pmd.suppressedViolations.column.userMessage=Reason
 report.pmd.processingErrors.title=Processing Errors
 report.pmd.processingErrors.column.filename=Filename
 report.pmd.processingErrors.column.problem=Problem
diff --git a/src/main/resources/pmd-report_de.properties b/src/main/resources/pmd-report_de.properties
index 83f72d4..a3dfea6 100644
--- a/src/main/resources/pmd-report_de.properties
+++ b/src/main/resources/pmd-report_de.properties
@@ -27,6 +27,11 @@
 report.pmd.violationsByPriority=Verst\u00f6\u00dfe nach Priorit\u00e4t
 report.pmd.priority=Priorit\u00e4t
 report.pmd.noProblems=PMD hat keine Probleme in dem Quellcode gefunden.
+report.pmd.suppressedViolations.title=Unterdr\u00fcckte Verst\u00f6\u00dfe
+report.pmd.suppressedViolations.column.filename=Datei
+report.pmd.suppressedViolations.column.ruleMessage=Regel
+report.pmd.suppressedViolations.column.suppressionType=Art der Unterdr\u00fcckung
+report.pmd.suppressedViolations.column.userMessage=Grund
 report.pmd.processingErrors.title=Verarbeitungsprobleme
 report.pmd.processingErrors.column.filename=Datei
 report.pmd.processingErrors.column.problem=Problem
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 db8f94f..c8ddae3 100644
--- a/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
+++ b/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
@@ -530,6 +530,51 @@
         String str = readFile( generatedFile );
 
         // check that there is no violation reported for "unusedVar2" - as it is suppressed
+        assertFalse( str.contains( "Avoid unused private fields such as 'unusedVar2'.\n </violation>" ) );
+        // but it appears as suppressed
+        assertTrue( str.contains( "suppressiontype=\"nopmd\" msg=\"Avoid unused private fields such as 'unusedVar2'.\"" ));
+
+        generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
+        renderer( mojo, generatedFile );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+
+        // check if there's a link to the JXR files
+        str = readFile( generatedFile );
+
+        assertTrue( str.contains( "/xref/def/configuration/AppSample.html#L27" ) );
+        // suppressed violation
+        assertTrue( str.contains( "Avoid unused private fields such as 'unusedVar2'." ) );
+    }
+
+    public void testSuppressMarkerConfigurationWithoutRendering()
+        throws Exception
+    {
+        File testPom =
+            new File( getBasedir(),
+                      "src/test/resources/unit/default-configuration/pmd-with-suppressMarker-no-render-plugin-config.xml" );
+        PmdReport mojo = (PmdReport) lookupMojo( "pmd", testPom );
+        mojo.execute();
+
+        // check if the PMD files were generated
+        File generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/pmd.xml" );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+
+        String str = readFile( generatedFile );
+
+        // check that there is no violation reported for "unusedVar2" - as it is suppressed
+        assertFalse( str.contains( "Avoid unused private fields such as 'unusedVar2'.\n </violation>" ) );
+        // but it appears as suppressed
+        assertTrue( str.contains( "suppressiontype=\"nopmd\" msg=\"Avoid unused private fields such as 'unusedVar2'.\"" ));
+
+        generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
+        renderer( mojo, generatedFile );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+
+        // check if there's a link to the JXR files
+        str = readFile( generatedFile );
+
+        assertTrue( str.contains( "/xref/def/configuration/AppSample.html#L27" ) );
+        // suppressed violations are not rendered
         assertFalse( str.contains( "Avoid unused private fields such as 'unusedVar2'." ) );
     }
 
diff --git a/src/test/resources/unit/default-configuration/pmd-with-suppressMarker-no-render-plugin-config.xml b/src/test/resources/unit/default-configuration/pmd-with-suppressMarker-no-render-plugin-config.xml
new file mode 100644
index 0000000..1f94bf2
--- /dev/null
+++ b/src/test/resources/unit/default-configuration/pmd-with-suppressMarker-no-render-plugin-config.xml
@@ -0,0 +1,70 @@
+<!--
+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.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>def.configuration</groupId>
+  <artifactId>suppressMarker-no-render-configuration</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <inceptionYear>2006</inceptionYear>
+  <name>Maven PMD Plugin SuppressMarker No Render Configuration Test</name>
+  <url>http://maven.apache.org</url>
+  <build>
+    <finalName>default-configuration</finalName>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <configuration>
+          <project implementation="org.apache.maven.plugins.pmd.stubs.DefaultConfigurationMavenProjectStub"/>
+          <outputDirectory>${basedir}/target/test/unit/default-configuration/target/site</outputDirectory>
+          <targetDirectory>${basedir}/target/test/unit/default-configuration/target</targetDirectory>
+          <rulesetsTargetDirectory>${basedir}/target/test/unit/default-configuration/target/pmd/rulesets</rulesetsTargetDirectory>
+          <format>xml</format>
+          <linkXRef>true</linkXRef>
+          <xrefLocation>${basedir}/target/test/unit/default-configuration/target/site/xref</xrefLocation>
+          <sourceEncoding>UTF-8</sourceEncoding>
+
+          <suppressMarker>SUPPRESSME</suppressMarker>
+          <renderSuppressedViolations>false</renderSuppressedViolations>
+
+          <compileSourceRoots>
+            <compileSourceRoot>${basedir}/src/test/resources/unit/default-configuration/</compileSourceRoot>
+          </compileSourceRoots>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>pmd</groupId>
+            <artifactId>pmd</artifactId>
+            <version>3.6</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>