You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ad...@apache.org on 2020/10/16 11:12:09 UTC

[maven-pmd-plugin] 02/02: [MPMD-304] - maven-pmd-plugin should be toolchains-aware

This is an automated email from the ASF dual-hosted git repository.

adangel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-pmd-plugin.git

commit f39c0018e622e6c12a3a3df3a2a92bfb0ceeab44
Author: Andreas Dangel <ad...@apache.org>
AuthorDate: Fri Oct 16 13:06:53 2020 +0200

    [MPMD-304] - maven-pmd-plugin should be toolchains-aware
    
    Separates the execution of PMD and CPD from PmdReport/CpdReport
    into separate classes PmdExecutor/CpdExecutor
    under package o.a.m.p.pmd.exec.
    
    All the necessary plugin properties/parameters are handed over as
    PmdRequest/CpdRequest.
    
    If a toolchain is in use, then PmdExecutor/CpdExecutor is called
    in a forked JVM. PmdRequest/CpdRequest is serialized/deserialized.
    
    Without a toolchain, PmdExecutor/CpdExecutor is directly called.
    
    The result is provided for reporting via PmdResult/CpdResult. These
    classes are just wrappers around the files `target/pmd.xml` and
    `target/cpd.xml` which are always created. Basically, PMD serializes
    the results into these XML files and m-pmd-p picks it up from there
    (like the check goal).
    
    The public method `PmdReport.getPMDConfiguration()` is gone, since
    PMDConfiguration is created now by PmdExecutor and is not exposed
    anymore. There is no direct replacement for this method.
    
    PmdReportGenerator and CpdReportGenerator now work with the classes
    generated by modello (in other words, they work now with the XML
    format of PMD) instead of using PMD classes directly. This further
    decouples the plugin from PMD.
    
    Closes #31
---
 pom.xml                                            |  20 +-
 src/it/MPMD-244-logging/invoker.properties         |   1 +
 .../invoker.properties                             |  17 +-
 src/it/MPMD-304-toolchain-support/pom.xml          |  93 ++++
 src/it/MPMD-304-toolchain-support/selector.groovy  |  65 +++
 .../src/main/java/sample/Name.java                 |  36 ++
 .../src/main/java/sample/Name2.java                |  36 ++
 .../src/main/java/sample/Sample.java               |  46 ++
 .../toolchains.windows.xml                         |  36 ++
 src/it/MPMD-304-toolchain-support/verify.groovy    |  49 ++
 .../maven/plugins/pmd/AbstractPmdReport.java       | 173 ++++---
 .../org/apache/maven/plugins/pmd/CpdReport.java    | 254 ++---------
 .../maven/plugins/pmd/CpdReportGenerator.java      |  30 +-
 .../maven/plugins/pmd/PmdCollectingRenderer.java   |  15 +-
 .../org/apache/maven/plugins/pmd/PmdReport.java    | 465 ++++---------------
 .../maven/plugins/pmd/PmdReportGenerator.java      |  56 +--
 .../apache/maven/plugins/pmd/exec/CpdExecutor.java | 347 ++++++++++++++
 .../apache/maven/plugins/pmd/exec/CpdRequest.java  | 209 +++++++++
 .../apache/maven/plugins/pmd/exec/CpdResult.java   |  69 +++
 .../apache/maven/plugins/pmd/exec/Executor.java    | 184 ++++++++
 .../apache/maven/plugins/pmd/exec/PmdExecutor.java | 501 +++++++++++++++++++++
 .../apache/maven/plugins/pmd/exec/PmdRequest.java  | 300 ++++++++++++
 .../apache/maven/plugins/pmd/exec/PmdResult.java   |  94 ++++
 src/main/mdo/pmd.mdo                               |  34 +-
 src/site/apt/examples/targetJdk.apt.vm             |  16 +-
 src/site/apt/index.apt.vm                          |   2 +-
 src/site/site.xml                                  |   2 +-
 .../maven/plugins/pmd/AbstractPmdReportTest.java   |  12 +-
 .../maven/plugins/pmd/CapturingPrintStream.java    |  82 ++++
 .../apache/maven/plugins/pmd/CpdReportTest.java    |  86 +---
 .../apache/maven/plugins/pmd/PmdReportTest.java    |  92 ++--
 .../resources/simplelogger.properties}             |  17 +-
 32 files changed, 2576 insertions(+), 863 deletions(-)

diff --git a/pom.xml b/pom.xml
index a325c5b..8444dfe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -132,12 +132,18 @@ under the License.
       <artifactId>maven-common-artifact-filters</artifactId>
       <version>3.1.0</version>
     </dependency>
+    <dependency>
+        <groupId>org.apache.maven</groupId>
+        <artifactId>maven-embedder</artifactId>
+        <version>3.1.0</version>
+        <scope>provided</scope>
+    </dependency>
 
     <!-- pmd -->
     <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.7</version>
+        <version>3.8.1</version>
     </dependency>
     <dependency>
       <groupId>net.sourceforge.pmd</groupId>
@@ -200,6 +206,11 @@ under the License.
       <artifactId>maven-reporting-impl</artifactId>
       <version>3.0.0</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-shared-utils</artifactId>
+      <version>3.2.1</version>
+    </dependency>
 
     <!-- plexus -->
     <dependency>
@@ -244,6 +255,12 @@ under the License.
       <version>2.5</version>
       <!-- scope>test</scope> Required by PMD transitively. -->
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>1.7.5</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -342,6 +359,7 @@ under the License.
             <artifactId>maven-invoker-plugin</artifactId>
             <configuration>
               <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <debug>false</debug>
               <goals>
                 <goal>clean</goal>
                 <goal>site</goal>
diff --git a/src/it/MPMD-244-logging/invoker.properties b/src/it/MPMD-244-logging/invoker.properties
index ea9f79e..d57dce3 100644
--- a/src/it/MPMD-244-logging/invoker.properties
+++ b/src/it/MPMD-244-logging/invoker.properties
@@ -17,3 +17,4 @@
 
 invoker.goals = clean pmd:check
 invoker.maven.version = 3.1.0+
+invoker.debug = true
diff --git a/src/it/MPMD-244-logging/invoker.properties b/src/it/MPMD-304-toolchain-support/invoker.properties
similarity index 53%
copy from src/it/MPMD-244-logging/invoker.properties
copy to src/it/MPMD-304-toolchain-support/invoker.properties
index ea9f79e..198db8b 100644
--- a/src/it/MPMD-244-logging/invoker.properties
+++ b/src/it/MPMD-304-toolchain-support/invoker.properties
@@ -15,5 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-invoker.goals = clean pmd:check
-invoker.maven.version = 3.1.0+
+invoker.java.version = 1.7+
+
+# available toolchains under linux:
+# https://github.com/apache/infrastructure-p6/blob/production/modules/build_nodes/files/toolchains.xml
+# the jdk toolchain "11:oracle" is selected in pom.xml
+
+# since the toolchains are only configured under linux slaves
+# we don't use invoker selections here, but selector.groovy
+#invoker.toolchain.jdk.version = 11
+#invoker.toolchain.jdk.vendor = oracle
+
+# the file toolchains.xml will be created by selector.groovy
+# - for linux, ${user.home}/.m2/toolchains.xml will be copied
+# - for windows, a new file will be created using toolchains.windows.xml, see selector.groovy
+invoker.goals = clean verify --toolchains toolchains.xml
diff --git a/src/it/MPMD-304-toolchain-support/pom.xml b/src/it/MPMD-304-toolchain-support/pom.xml
new file mode 100644
index 0000000..5ea8c4c
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.maven.plugins.pmd.it</groupId>
+  <artifactId>MPMD-304-toolchain-support</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>11</java.version>
+  </properties>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.8.0</version>
+          <configuration>
+            <target>${java.version}</target>
+            <source>${java.version}</source>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>@project.version@</version>
+        <configuration>
+          <failOnViolation>false</failOnViolation>
+          <printFailingErrors>true</printFailingErrors>
+          <targetJdk>${java.version}</targetJdk>
+          <sourceEncoding>UTF-8</sourceEncoding>
+          <minimumTokens>10</minimumTokens>
+        </configuration>
+        <executions>
+          <execution>
+            <id>default</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>check</goal>
+              <goal>cpd-check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-toolchains-plugin</artifactId>
+        <version>3.0.0</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>toolchain</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <toolchains>
+            <jdk>
+              <version>${java.version}</version>
+              <vendor>oracle</vendor>
+            </jdk>
+          </toolchains>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/it/MPMD-304-toolchain-support/selector.groovy b/src/it/MPMD-304-toolchain-support/selector.groovy
new file mode 100644
index 0000000..55abd8b
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/selector.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+File testToolchains = new File( basedir, 'toolchains.xml' )
+
+File userToolchains = new File( System.getProperty( 'user.home' ), '.m2/toolchains.xml' )
+if ( userToolchains.exists() )
+{
+    System.out.println( "INFO: Copying ${userToolchains.absolutePath} to ${testToolchains.absolutePath}" )
+    testToolchains.text = userToolchains.text
+}
+else
+{
+    System.out.println( "WARNING: File ${userToolchains.absolutePath} not found" )
+    if ( System.getProperty( 'os.name' ).startsWith( 'Windows' ) )
+    {
+        String jdk11Windows = 'f:\\jenkins\\tools\\java\\latest11'
+        File windowsToolchains = new File( basedir, 'toolchains.windows.xml' )
+        System.out.println( "INFO: Creating ${testToolchains.absolutePath} with jdk:11:oracle=${jdk11Windows}" )
+
+        String placeholder = '@jdk.home@'
+        String replacement = jdk11Windows
+        // extra escaping of backslashes in the path for Windows
+        replacement = replacement.replaceAll("\\\\", "\\\\\\\\")
+        testToolchains.text = windowsToolchains.text.replaceAll( placeholder, replacement )
+        System.out.println( "Replaced '${placeholder}' with '${replacement}' in '${testToolchains.absolutePath}'." )
+    }
+}
+
+if ( testToolchains.exists() )
+{
+    def toolchains = new XmlParser().parseText( testToolchains.text )
+    def result = toolchains.children().find { toolchain ->
+            toolchain.type.text() == 'jdk' &&
+            toolchain.provides.version.text() == '11' &&
+            toolchain.provides.vendor.text() == 'oracle'
+    }
+    if ( !result )
+    {
+        System.out.println( "WARNING: No jdk toolchain for 11:oracle found" )
+        return false
+    }
+
+    System.out.println( "INFO: Found toolchain: ${result}" )
+    return true
+}
+
+System.out.println( "WARNING: Skipping integration test due to missing toolchain" )
+return false
diff --git a/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name.java b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name.java
new file mode 100644
index 0000000..1b68095
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name.java
@@ -0,0 +1,36 @@
+package sample;
+
+/*
+ * 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.
+ */
+
+public class Name extends Thread
+{
+    private final String name;
+
+    public Name( String name )
+    {
+        this.name = name;
+    }
+
+    @Override
+    public String toString()
+    {
+        return name;
+    }
+}
\ No newline at end of file
diff --git a/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name2.java b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name2.java
new file mode 100644
index 0000000..2ce276b
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Name2.java
@@ -0,0 +1,36 @@
+package sample;
+
+/*
+ * 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.
+ */
+
+public class Name2 extends Thread
+{
+    private final String name;
+
+    public Name2( String name )
+    {
+        this.name = name;
+    }
+
+    @Override
+    public String toString()
+    {
+        return name;
+    }
+}
\ No newline at end of file
diff --git a/src/it/MPMD-304-toolchain-support/src/main/java/sample/Sample.java b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Sample.java
new file mode 100644
index 0000000..edcb5da
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/src/main/java/sample/Sample.java
@@ -0,0 +1,46 @@
+package sample;
+
+/*
+ * 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.
+ */
+import java.util.function.Function;
+
+// this class trigger PMD rule category/java/codestyle.xml/ExtendsObject
+public class Sample extends Object
+{
+    public static void main( String ... args )
+    {
+        new Sample();
+    }
+
+    public Sample()
+    {
+        // this triggers category/java/multithreading.xml/DontCallThreadRun
+        // it only works, if the auxclass path could be loaded,
+        // by using the correct jdk toolchain
+        new Name( "foo" ).run();
+        Name name = getName( new Name( "World" ) );
+        Function<Name, String> greeter = (var n) -> "Hello " + n;
+        System.out.println( greeter.apply( name ) );
+    }
+
+    private Name getName( Name name )
+    {
+        return new Name( name.toString() + "!" );
+    }
+}
diff --git a/src/it/MPMD-304-toolchain-support/toolchains.windows.xml b/src/it/MPMD-304-toolchain-support/toolchains.windows.xml
new file mode 100644
index 0000000..e3acb90
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/toolchains.windows.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF8"?>
+
+<!--
+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.
+-->
+
+<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">
+    <toolchain>
+        <type>jdk</type>
+        <provides>
+            <version>11</version>
+            <vendor>oracle</vendor>
+        </provides>
+        <configuration>
+            <!-- this placeholder will be replaced by selector.groovy -->
+            <jdkHome>@jdk.home@</jdkHome>
+        </configuration>
+    </toolchain>
+</toolchains>
diff --git a/src/it/MPMD-304-toolchain-support/verify.groovy b/src/it/MPMD-304-toolchain-support/verify.groovy
new file mode 100644
index 0000000..7370225
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/verify.groovy
@@ -0,0 +1,49 @@
+
+/*
+ * 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.
+ */
+
+File buildLog = new File( basedir, 'build.log' )
+assert buildLog.exists()
+
+assert buildLog.text.contains( '[INFO] Toolchain in maven-pmd-plugin: JDK[' )
+assert buildLog.text.contains( '[INFO] PMD Failure: sample.Sample:24 Rule:ExtendsObject' )
+assert buildLog.text.contains( '[INFO] PMD Failure: sample.Sample:36 Rule:DontCallThreadRun' )
+assert buildLog.text.contains( '[INFO] You have 2 PMD violations.' )
+assert buildLog.text.contains( '[INFO] You have 1 CPD duplication' )
+
+File pmdReport = new File( basedir, 'target/pmd.xml' )
+assert pmdReport.exists()
+assert pmdReport.text.contains( '<violation beginline="24" endline="24" begincolumn="29" endcolumn="34" rule="ExtendsObject"' )
+assert pmdReport.text.contains( '<violation beginline="36" endline="36" begincolumn="9" endcolumn="31" rule="DontCallThreadRun"' )
+
+File pmdSite = new File( basedir, 'target/site/pmd.html' )
+assert pmdSite.exists()
+assert pmdSite.text.contains( 'Sample.java' )
+assert pmdSite.text.contains( 'ExtendsObject' )
+assert pmdSite.text.contains( 'DontCallThreadRun' )
+
+File cpdReport = new File( basedir, 'target/cpd.xml' )
+assert cpdReport.exists()
+assert cpdReport.text.contains( 'Name.java' )
+assert cpdReport.text.contains( 'Name2.java' )
+
+File cpdSite = new File( basedir, 'target/site/cpd.html' )
+assert cpdSite.exists()
+assert cpdSite.text.contains( 'Name.java' )
+assert cpdSite.text.contains( 'Name2.java' )
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
index 469cc35..15bb1cd 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
@@ -21,6 +21,8 @@ package org.apache.maven.plugins.pmd;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -29,24 +31,23 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.logging.SimpleFormatter;
-
-import net.sourceforge.pmd.PMD;
 
 import org.apache.maven.doxia.siterenderer.Renderer;
+import org.apache.maven.execution.MavenSession;
 import org.apache.maven.model.ReportPlugin;
+import org.apache.maven.model.Reporting;
 import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.reporting.AbstractMavenReport;
+import org.apache.maven.toolchain.Toolchain;
+import org.apache.maven.toolchain.ToolchainManager;
 import org.codehaus.plexus.util.FileUtils;
 import org.codehaus.plexus.util.PathTool;
 import org.codehaus.plexus.util.ReaderFactory;
 import org.codehaus.plexus.util.StringUtils;
-import org.slf4j.bridge.SLF4JBridgeHandler;
+
+import net.sourceforge.pmd.PMDVersion;
 
 /**
  * Base class for the PMD reports.
@@ -57,6 +58,10 @@ import org.slf4j.bridge.SLF4JBridgeHandler;
 public abstract class AbstractPmdReport
     extends AbstractMavenReport
 {
+    // ----------------------------------------------------------------------
+    // Configurables
+    // ----------------------------------------------------------------------
+
     /**
      * The output directory for the intermediate XML report.
      */
@@ -72,18 +77,6 @@ public abstract class AbstractPmdReport
     protected File outputDirectory;
 
     /**
-     * Site rendering component for generating the HTML report.
-     */
-    @Component
-    private Renderer siteRenderer;
-
-    /**
-     * The project to analyse.
-     */
-    @Parameter( defaultValue = "${project}", readonly = true, required = true )
-    protected MavenProject project;
-
-    /**
      * Set the output format type, in addition to the HTML report. Must be one of: "none", "csv", "xml", "txt" or the
      * full class name of the PMD renderer to use. See the net.sourceforge.pmd.renderers package javadoc for available
      * renderers. XML is produced in any case, since this format is needed
@@ -188,12 +181,6 @@ public abstract class AbstractPmdReport
     private String outputEncoding;
 
     /**
-     * The projects in the reactor for aggregation report.
-     */
-    @Parameter( property = "reactorProjects", readonly = true )
-    protected List<MavenProject> reactorProjects;
-
-    /**
      * Whether to include the xml files generated by PMD/CPD in the site.
      *
      * @since 3.0
@@ -236,11 +223,48 @@ public abstract class AbstractPmdReport
     protected boolean showPmdLog = true;
 
     /**
-     * This holds a strong reference in case we configured the logger to
-     * redirect to slf4j. See {@link #showPmdLog}. Without a strong reference,
-     * the logger might be garbage collected and the redirect to slf4j is gone.
+     * <p>
+     * Specify the requirements for this jdk toolchain.
+     * This overrules the toolchain selected by the maven-toolchain-plugin.
+     * </p>
+     * <strong>note:</strong> requires at least Maven 3.3.1
+     *
+     * @since 3.14.0
+     */
+    @Parameter
+    private Map<String, String> jdkToolchain;
+
+    // ----------------------------------------------------------------------
+    // Read-only parameters
+    // ----------------------------------------------------------------------
+
+    /**
+     * The project to analyse.
+     */
+    @Parameter( defaultValue = "${project}", readonly = true, required = true )
+    protected MavenProject project;
+
+    /**
+     * The projects in the reactor for aggregation report.
+     */
+    @Parameter( property = "reactorProjects", readonly = true )
+    protected List<MavenProject> reactorProjects;
+
+    /**
+     * The current build session instance. This is used for
+     * toolchain manager API calls and for dependency resolver API calls.
+     */
+    @Parameter( defaultValue = "${session}", required = true, readonly = true )
+    protected MavenSession session;
+
+    /**
+     * Site rendering component for generating the HTML report.
      */
-    private Logger julLogger;
+    @Component
+    private Renderer siteRenderer;
+
+    @Component
+    private ToolchainManager toolchainManager;
 
     /** The files that are being analyzed. */
     protected Map<File, PmdFileInfo> filesToProcess;
@@ -285,7 +309,10 @@ public abstract class AbstractPmdReport
             else
             {
                 // Not yet generated - check if the report is on its way
-                List<ReportPlugin> reportPlugins = project.getReportPlugins();
+                Reporting reporting = project.getModel().getReporting();
+                List<ReportPlugin> reportPlugins = reporting != null
+                        ? reporting.getPlugins()
+                        : Collections.<ReportPlugin>emptyList();
                 for ( ReportPlugin plugin : reportPlugins )
                 {
                     String artifactId = plugin.getArtifactId();
@@ -483,11 +510,6 @@ public abstract class AbstractPmdReport
         return StringUtils.join( patterns.iterator(), "," );
     }
 
-    protected boolean isHtml()
-    {
-        return "html".equals( format );
-    }
-
     protected boolean isXml()
     {
         return "xml".equals( format );
@@ -556,54 +578,63 @@ public abstract class AbstractPmdReport
         return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
     }
 
-    protected void setupPmdLogging()
+    protected String determineCurrentRootLogLevel()
     {
-        if ( !showPmdLog )
+        String logLevel = System.getProperty( "org.slf4j.simpleLogger.defaultLogLevel" );
+        if ( logLevel == null )
         {
-            return;
+            logLevel = System.getProperty( "maven.logging.root.level" );
         }
-
-        Logger logger = Logger.getLogger( "net.sourceforge.pmd" );
-
-        boolean slf4jBridgeAlreadyAdded = false;
-        for ( Handler handler : logger.getHandlers() )
-        {
-            if ( handler instanceof SLF4JBridgeHandler )
-            {
-                slf4jBridgeAlreadyAdded = true;
-                break;
-            }
-        }
-
-        if ( slf4jBridgeAlreadyAdded )
+        if ( logLevel == null )
         {
-            return;
+            // TODO: logback level
+            logLevel = "info";
         }
-
-        SLF4JBridgeHandler handler = new SLF4JBridgeHandler();
-        SimpleFormatter formatter = new SimpleFormatter();
-        handler.setFormatter( formatter );
-        logger.setUseParentHandlers( false );
-        logger.addHandler( handler );
-        handler.setLevel( Level.ALL );
-        logger.setLevel( Level.ALL );
-        julLogger = logger;
-        julLogger.fine(  "Configured jul-to-slf4j bridge for " + logger.getName() );
+        return logLevel;
     }
 
     static String getPmdVersion()
     {
-        try
-        {
-            return (String) PMD.class.getField( "VERSION" ).get( null );
-        }
-        catch ( IllegalAccessException e )
+        return PMDVersion.VERSION;
+    }
+
+    //TODO remove the part with ToolchainManager lookup once we depend on
+    //3.0.9 (have it as prerequisite). Define as regular component field then.
+    protected final Toolchain getToolchain()
+    {
+        Toolchain tc = null;
+
+        if ( jdkToolchain != null )
         {
-            throw new RuntimeException( "PMD VERSION field not accessible", e );
+            // Maven 3.3.1 has plugin execution scoped Toolchain Support
+            try
+            {
+                Method getToolchainsMethod =
+                    toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
+                                                           Map.class );
+
+                @SuppressWarnings( "unchecked" )
+                List<Toolchain> tcs =
+                    (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
+                                                                  jdkToolchain );
+
+                if ( tcs != null && !tcs.isEmpty() )
+                {
+                    tc = tcs.get( 0 );
+                }
+            }
+            catch ( NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
+                | InvocationTargetException e )
+            {
+                // ignore
+            }
         }
-        catch ( NoSuchFieldException e )
+
+        if ( tc == null )
         {
-            throw new RuntimeException( "PMD VERSION field not found", e );
+            tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
         }
+
+        return tc;
     }
 }
\ No newline at end of file
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 6bd73b2..4b3ad5b 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
@@ -20,39 +20,24 @@ package org.apache.maven.plugins.pmd;
  */
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
 import java.util.ResourceBundle;
 
-import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.pmd.exec.CpdExecutor;
+import org.apache.maven.plugins.pmd.exec.CpdRequest;
+import org.apache.maven.plugins.pmd.exec.CpdResult;
 import org.apache.maven.reporting.MavenReportException;
-import org.codehaus.plexus.util.FileUtils;
+import org.apache.maven.shared.utils.logging.MessageUtils;
+import org.apache.maven.toolchain.Toolchain;
 import org.codehaus.plexus.util.StringUtils;
 import org.codehaus.plexus.util.WriterFactory;
 
-import net.sourceforge.pmd.cpd.CPD;
-import net.sourceforge.pmd.cpd.CPDConfiguration;
-import net.sourceforge.pmd.cpd.CSVRenderer;
-import net.sourceforge.pmd.cpd.EcmascriptLanguage;
-import net.sourceforge.pmd.cpd.JSPLanguage;
-import net.sourceforge.pmd.cpd.JavaLanguage;
 import net.sourceforge.pmd.cpd.JavaTokenizer;
-import net.sourceforge.pmd.cpd.Language;
-import net.sourceforge.pmd.cpd.LanguageFactory;
-import net.sourceforge.pmd.cpd.Match;
-import net.sourceforge.pmd.cpd.SimpleRenderer;
-import net.sourceforge.pmd.cpd.XMLRenderer;
 import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
 
 /**
@@ -117,11 +102,12 @@ public class CpdReport
     @Parameter( property = "cpd.ignoreAnnotations", defaultValue = "false" )
     private boolean ignoreAnnotations;
 
-    /** The CPD instance used to analyze the files. Will itself collect the duplicated code matches. */
-    private CPD cpd;
-
-    /** Helper to exclude duplications from the result. */
-    private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile = new ExcludeDuplicationsFromFile();
+    /**
+     * 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}
@@ -192,10 +178,10 @@ public class CpdReport
         {
             try
             {
-                executeCpdWithClassloader();
+                executeCpd();
                 if ( skipEmptyReport )
                 {
-                    result = cpd.getMatches().hasNext();
+                    result = cpdResult.hasDuplications();
                     if ( result )
                     {
                         getLog().debug( "Skipping report since skipEmptyReport is true and there are no CPD issues." );
@@ -210,92 +196,60 @@ public class CpdReport
         return result;
     }
 
-    private void executeCpdWithClassloader()
-        throws MavenReportException
-    {
-        ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
-        try
-        {
-            Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
-            executeCpd();
-        }
-        finally
-        {
-            Thread.currentThread().setContextClassLoader( origLoader );
-        }
-    }
-
     private void executeCpd()
         throws MavenReportException
     {
-        if ( cpd != null )
+        if ( cpdResult != null )
         {
             // CPD has already been run
             getLog().debug( "CPD has already been run - skipping redundant execution." );
             return;
         }
 
-        setupPmdLogging();
-
-        Properties p = new Properties();
+        Properties languageProperties = new Properties();
         if ( ignoreLiterals )
         {
-            p.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
+            languageProperties.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
         }
         if ( ignoreIdentifiers )
         {
-            p.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
+            languageProperties.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
         }
         if ( ignoreAnnotations )
         {
-            p.setProperty( JavaTokenizer.IGNORE_ANNOTATIONS, "true" );
+            languageProperties.setProperty( JavaTokenizer.IGNORE_ANNOTATIONS, "true" );
         }
         try
         {
-            if ( filesToProcess == null )
-            {
-                filesToProcess = getFilesToProcess();
-            }
-
-            try
-            {
-                excludeDuplicationsFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
-            }
-            catch ( MojoExecutionException e )
-            {
-                throw new MavenReportException( "Error loading exclusions", e );
-            }
-
-            String encoding = determineEncoding( !filesToProcess.isEmpty() );
-            Language cpdLanguage;
-            if ( "java".equals ( language ) || null == language )
-            {
-                cpdLanguage = new JavaLanguage( p );
-            }
-            else if ( "javascript".equals( language ) )
+            filesToProcess = getFilesToProcess();
+
+            CpdRequest request = new CpdRequest();
+            request.setMinimumTokens( minimumTokens );
+            request.setLanguage( language );
+            request.setLanguageProperties( languageProperties );
+            request.setSourceEncoding( determineEncoding( !filesToProcess.isEmpty() ) );
+            request.addFiles( filesToProcess.keySet() );
+            
+            request.setShowPmdLog( showPmdLog );
+            request.setColorizedLog( MessageUtils.isColorEnabled() );
+            request.setLogLevel( determineCurrentRootLogLevel() );
+            
+            request.setExcludeFromFailureFile( excludeFromFailureFile );
+            request.setTargetDirectory( targetDirectory.getAbsolutePath() );
+            request.setOutputEncoding( getOutputEncoding() );
+            request.setFormat( format );
+            request.setIncludeXmlInSite( includeXmlInSite );
+            request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
+
+            Toolchain tc = getToolchain();
+            if ( tc != null )
             {
-                cpdLanguage = new EcmascriptLanguage();
+                getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
+                String javaExecutable = tc.findTool( "java" ); //NOI18N
+                request.setJavaExecutable( javaExecutable );
             }
-            else if ( "jsp".equals( language ) )
-            {
-                cpdLanguage = new JSPLanguage();
-            }
-            else
-            {
-                cpdLanguage = LanguageFactory.createLanguage( language, p );
-            }
-
-            CPDConfiguration cpdConfiguration = new CPDConfiguration();
-            cpdConfiguration.setMinimumTileSize( minimumTokens );
-            cpdConfiguration.setLanguage( cpdLanguage );
-            cpdConfiguration.setSourceEncoding( encoding );
-
-            cpd = new CPD( cpdConfiguration );
 
-            for ( File file : filesToProcess.keySet() )
-            {
-                cpd.add( file );
-            }
+            cpdResult = CpdExecutor.execute( request );
         }
         catch ( UnsupportedEncodingException e )
         {
@@ -305,50 +259,12 @@ public class CpdReport
         {
             throw new MavenReportException( e.getMessage(), e );
         }
-        getLog().debug( "Executing CPD..." );
-        cpd.go();
-        getLog().debug( "CPD finished." );
-
-        // always create XML format. we need to output it even if the file list is empty or we have no duplications
-        // so the "check" goals can check for violations
-        writeXmlReport( cpd );
-
-        // html format is handled by maven site report, xml format has already been rendered
-        if ( !isHtml() && !isXml() )
-        {
-            writeFormattedReport( cpd );
-        }
-    }
-
-    private Iterator<Match> filterMatches( Iterator<Match> matches )
-    {
-        getLog().debug( "Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
-            + " configured exclusions." );
-
-        List<Match> filteredMatches = new ArrayList<>();
-        int excludedDuplications = 0;
-        while ( matches.hasNext() )
-        {
-            Match match = matches.next();
-            if ( excludeDuplicationsFromFile.isExcludedFromFailure( match ) )
-            {
-                excludedDuplications++;
-            }
-            else
-            {
-                filteredMatches.add( match );
-            }
-        }
-
-        getLog().debug( "Excluded " + excludedDuplications + " duplications." );
-        return filteredMatches.iterator();
     }
 
     private void generateMavenSiteReport( Locale locale )
     {
         CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ), aggregate );
-        Iterator<Match> matches = cpd.getMatches();
-        gen.generate( filterMatches( matches ) );
+        gen.generate( cpdResult.getDuplications() );
     }
 
     private String determineEncoding( boolean showWarn )
@@ -372,53 +288,6 @@ public class CpdReport
         return encoding;
     }
 
-    private void writeFormattedReport( CPD cpd )
-        throws MavenReportException
-    {
-        CPDRenderer r = createRenderer();
-        writeReport( cpd, r, format );
-
-    }
-
-    void writeXmlReport( CPD cpd ) throws MavenReportException
-    {
-        File targetFile = writeReport( cpd, new XMLRenderer( getOutputEncoding() ), "xml" );
-        if ( includeXmlInSite )
-        {
-            File siteDir = getReportOutputDirectory();
-            siteDir.mkdirs();
-            try
-            {
-                FileUtils.copyFile( targetFile, new File( siteDir, "cpd.xml" ) );
-            }
-            catch ( IOException e )
-            {
-                throw new MavenReportException( e.getMessage(), e );
-            }
-        }
-    }
-
-    private File writeReport( CPD cpd, CPDRenderer r, String extension ) throws MavenReportException
-    {
-        if ( r == null )
-        {
-            return null;
-        }
-
-        File targetFile = new File( targetDirectory, "cpd." + extension );
-        targetDirectory.mkdirs();
-        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
-        {
-            r.render( filterMatches( cpd.getMatches() ), writer );
-            writer.flush();
-        }
-        catch ( IOException ioe )
-        {
-            throw new MavenReportException( ioe.getMessage(), ioe );
-        }
-        return targetFile;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -437,36 +306,11 @@ public class CpdReport
      *
      * @return the renderer based on the configured output
      * @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
+     * @deprecated Use {@link CpdExecutor#createRenderer(String, String)} instead.
      */
-    public CPDRenderer createRenderer()
-        throws MavenReportException
+    @Deprecated
+    public CPDRenderer createRenderer() throws MavenReportException
     {
-        CPDRenderer renderer = null;
-        if ( "xml".equals( format ) )
-        {
-            renderer = new XMLRenderer( getOutputEncoding() );
-        }
-        else if ( "csv".equals( format ) )
-        {
-            renderer = new CSVRenderer();
-        }
-        else if ( "txt".equals( format ) )
-        {
-            renderer = new SimpleRenderer();
-        }
-        else if ( !"".equals( format ) && !"none".equals( format ) )
-        {
-            try
-            {
-                renderer = (CPDRenderer) Class.forName( format ).getConstructor().newInstance();
-            }
-            catch ( Exception e )
-            {
-                throw new MavenReportException( "Can't find CPD custom format " + format + ": "
-                    + e.getClass().getName(), e );
-            }
-        }
-
-        return renderer;
+        return CpdExecutor.createRenderer( format, getOutputEncoding() );
     }
 }
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java b/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java
index 389d6ca..34a0702 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdReportGenerator.java
@@ -20,15 +20,13 @@ package org.apache.maven.plugins.pmd;
  */
 
 import java.io.File;
-import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
 
-import net.sourceforge.pmd.cpd.Mark;
-import net.sourceforge.pmd.cpd.Match;
-import net.sourceforge.pmd.cpd.TokenEntry;
-
 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;
 
@@ -107,17 +105,17 @@ public class CpdReportGenerator
     /**
      * Method that generates a line of CPD report according to a TokenEntry.
      */
-    private void generateFileLine( TokenEntry tokenEntry )
+    private void generateFileLine( CpdFile duplicationMark )
     {
         // Get information for report generation
-        String filename = tokenEntry.getTokenSrcID();
+        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 = tokenEntry.getBeginLine();
+        int line = duplicationMark.getLine();
 
         sink.tableRow();
         sink.tableCell();
@@ -149,24 +147,22 @@ public class CpdReportGenerator
     /**
      * Method that generates the contents of the CPD report
      *
-     * @param matches the found duplications
+     * @param duplications the found duplications
      */
-    public void generate( Iterator<Match> matches )
+    public void generate( List<Duplication> duplications )
     {
         beginDocument();
 
-        if ( !matches.hasNext() )
+        if ( duplications.isEmpty() )
         {
             sink.paragraph();
             sink.text( bundle.getString( "report.cpd.noProblems" ) );
             sink.paragraph_();
         }
 
-        while ( matches.hasNext() )
+        for ( Duplication duplication : duplications )
         {
-            Match match = matches.next();
-
-            String code = match.getSourceCodeSlice();
+            String code = duplication.getCodefragment();
 
             sink.table();
             sink.tableRow();
@@ -185,10 +181,8 @@ public class CpdReportGenerator
             sink.tableRow_();
 
             // Iterating on every token entry
-            for ( Iterator<Mark> occurrences = match.iterator(); occurrences.hasNext(); )
+            for ( CpdFile mark : duplication.getFiles() )
             {
-
-                TokenEntry mark = occurrences.next().getToken();
                 generateFileLine( mark );
             }
 
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 e55b779..2370ead 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdCollectingRenderer.java
@@ -22,17 +22,16 @@ package org.apache.maven.plugins.pmd;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 
+import org.codehaus.plexus.util.StringUtils;
+
 import net.sourceforge.pmd.Report;
 import net.sourceforge.pmd.Report.ProcessingError;
 import net.sourceforge.pmd.RuleViolation;
 import net.sourceforge.pmd.renderers.AbstractRenderer;
 import net.sourceforge.pmd.util.datasource.DataSource;
 
-import org.codehaus.plexus.util.StringUtils;
-
 
 /**
  * A PMD renderer, that collects all violations and processing errors
@@ -56,14 +55,8 @@ public class PmdCollectingRenderer extends AbstractRenderer
     @Override
     public void renderFileReport( Report report ) throws IOException
     {
-        for ( RuleViolation v : report )
-        {
-            violations.add( v );
-        }
-        for ( Iterator<ProcessingError> it = report.errors(); it.hasNext(); )
-        {
-            errors.add( it.next() );
-        }
+        violations.addAll( report.getViolations() );
+        errors.addAll( report.getProcessingErrors() );
     }
 
     /**
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 f4d7f49..ba06cf8 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
@@ -20,26 +20,21 @@ package org.apache.maven.plugins.pmd;
  */
 
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintStream;
-import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.ResourceBundle;
 
 import org.apache.maven.doxia.sink.Sink;
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.MojoExecutionException;
 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.annotations.ResolutionScope;
+import org.apache.maven.plugins.pmd.exec.PmdExecutor;
+import org.apache.maven.plugins.pmd.exec.PmdRequest;
+import org.apache.maven.plugins.pmd.exec.PmdResult;
 import org.apache.maven.project.DefaultProjectBuildingRequest;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuildingRequest;
@@ -50,37 +45,17 @@ import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
 import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
+import org.apache.maven.shared.utils.logging.MessageUtils;
+import org.apache.maven.toolchain.Toolchain;
 import org.codehaus.plexus.resource.ResourceManager;
 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
 import org.codehaus.plexus.resource.loader.FileResourceLoader;
 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
-import org.codehaus.plexus.util.FileUtils;
 import org.codehaus.plexus.util.ReaderFactory;
 import org.codehaus.plexus.util.StringUtils;
 
-import net.sourceforge.pmd.PMD;
-import net.sourceforge.pmd.PMDConfiguration;
-import net.sourceforge.pmd.Report;
-import net.sourceforge.pmd.RuleContext;
-import net.sourceforge.pmd.RulePriority;
-import net.sourceforge.pmd.RuleSetFactory;
-import net.sourceforge.pmd.RuleSetNotFoundException;
 import net.sourceforge.pmd.RuleSetReferenceId;
-import net.sourceforge.pmd.RuleViolation;
-import net.sourceforge.pmd.benchmark.Benchmarker;
-import net.sourceforge.pmd.benchmark.TextReport;
-import net.sourceforge.pmd.lang.LanguageRegistry;
-import net.sourceforge.pmd.lang.LanguageVersion;
-import net.sourceforge.pmd.renderers.CSVRenderer;
-import net.sourceforge.pmd.renderers.HTMLRenderer;
 import net.sourceforge.pmd.renderers.Renderer;
-import net.sourceforge.pmd.renderers.TextRenderer;
-import net.sourceforge.pmd.renderers.XMLRenderer;
-import net.sourceforge.pmd.util.ClasspathClassLoader;
-import net.sourceforge.pmd.util.IOUtil;
-import net.sourceforge.pmd.util.ResourceLoader;
-import net.sourceforge.pmd.util.datasource.DataSource;
-import net.sourceforge.pmd.util.datasource.FileDataSource;
 
 /**
  * Creates a PMD report.
@@ -182,17 +157,6 @@ public class PmdReport
     private String suppressMarker;
 
     /**
-     */
-    @Component
-    private ResourceManager locator;
-
-    /** The PMD renderer for collecting violations. */
-    private PmdCollectingRenderer renderer;
-
-    /** Helper to exclude violations given as a properties file. */
-    private final ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
-
-    /**
      * per default pmd executions error are ignored to not break the whole
      *
      * @since 3.1
@@ -261,11 +225,22 @@ public class PmdReport
     @Parameter( property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets" )
     private File rulesetsTargetDirectory;
 
+    /**
+     * Used to locate configured rulesets. The rulesets could be on the plugin
+     * classpath or in the local project file system.
+     */
+    @Component
+    private ResourceManager locator;
+
     @Component
     private DependencyResolver dependencyResolver;
 
-    @Parameter( defaultValue = "${session}", required = true, readonly = true )
-    private MavenSession session;
+    /**
+     * 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}
@@ -349,10 +324,10 @@ public class PmdReport
         {
             try
             {
-                executePmdWithClassloader();
+                executePmd();
                 if ( skipEmptyReport )
                 {
-                    result = renderer.hasViolations();
+                    result = pmdResult.hasViolations();
                     if ( result )
                     {
                         getLog().debug( "Skipping report since skipEmptyReport is true and "
@@ -368,27 +343,10 @@ public class PmdReport
         return result;
     }
 
-    private void executePmdWithClassloader()
-        throws MavenReportException
-    {
-        ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
-        try
-        {
-            Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
-            executePmd();
-        }
-        finally
-        {
-            Thread.currentThread().setContextClassLoader( origLoader );
-        }
-    }
-
     private void executePmd()
         throws MavenReportException
     {
-        setupPmdLogging();
-
-        if ( renderer != null )
+        if ( pmdResult != null )
         {
             // PMD has already been run
             getLog().debug( "PMD has already been run - skipping redundant execution." );
@@ -397,49 +355,7 @@ public class PmdReport
 
         try
         {
-            excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
-        }
-        catch ( MojoExecutionException e )
-        {
-            throw new MavenReportException( "Unable to load exclusions", e );
-        }
-
-        // configure ResourceManager
-        locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
-        locator.addSearchPath( "url", "" );
-        locator.setOutputDirectory( rulesetsTargetDirectory );
-
-        renderer = new PmdCollectingRenderer();
-        PMDConfiguration pmdConfiguration = getPMDConfiguration();
-
-        String[] sets = new String[rulesets.length];
-        try
-        {
-            for ( int idx = 0; idx < rulesets.length; idx++ )
-            {
-                String set = rulesets[idx];
-                getLog().debug( "Preparing ruleset: " + set );
-                RuleSetReferenceId id = new RuleSetReferenceId( set );
-                File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
-                if ( null == ruleset )
-                {
-                    throw new MavenReportException( "Could not resolve " + set );
-                }
-                sets[idx] = ruleset.getAbsolutePath();
-            }
-        }
-        catch ( ResourceNotFoundException | FileResourceCreationException e )
-        {
-            throw new MavenReportException( e.getMessage(), e );
-        }
-        pmdConfiguration.setRuleSets( StringUtils.join( sets, "," ) );
-
-        try
-        {
-            if ( filesToProcess == null )
-            {
-                filesToProcess = getFilesToProcess();
-            }
+            filesToProcess = getFilesToProcess();
 
             if ( filesToProcess.isEmpty() && !"java".equals( language ) )
             {
@@ -452,139 +368,90 @@ public class PmdReport
             throw new MavenReportException( "Can't get file list", e );
         }
 
-        String encoding = getSourceEncoding();
-        if ( StringUtils.isEmpty( encoding ) )
-        {
-            encoding = ReaderFactory.FILE_ENCODING;
-            if ( !filesToProcess.isEmpty() )
-            {
-                getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
-                               + ", i.e. build is platform dependent!" );
-            }
-        }
-        pmdConfiguration.setSourceEncoding( encoding );
 
-        List<DataSource> dataSources = new ArrayList<>( filesToProcess.size() );
-        for ( File f : filesToProcess.keySet() )
-        {
-            dataSources.add( new FileDataSource( f ) );
-        }
+        PmdRequest request = new PmdRequest();
+        request.setLanguageAndVersion( language, targetJdk );
+        request.setRulesets( resolveRulesets() );
+        request.setAuxClasspath( typeResolution ? determineAuxClasspath() : null );
+        request.setSourceEncoding( getSourceEncoding() );
+        request.addFiles( filesToProcess.keySet() );
+        request.setMinimumPriority( minimumPriority );
+        request.setSuppressMarker( suppressMarker );
+        request.setBenchmarkOutputLocation( benchmark ? benchmarkOutputFilename : null );
+        request.setAnalysisCacheLocation( analysisCache ? analysisCacheLocation : null );
+        request.setExcludeFromFailureFile( excludeFromFailureFile );
 
-        if ( sets.length > 0 )
-        {
-            processFilesWithPMD( pmdConfiguration, dataSources );
-        }
-        else
-        {
-            getLog().debug( "Skipping PMD execution as no rulesets are defined." );
-        }
+        request.setTargetDirectory( targetDirectory.getAbsolutePath() );
+        request.setOutputEncoding( getOutputEncoding() );
+        request.setFormat( format );
+        request.setShowPmdLog( showPmdLog );
+        request.setColorizedLog( MessageUtils.isColorEnabled() );
+        request.setSkipPmdError( skipPmdError );
+        request.setIncludeXmlInSite( includeXmlInSite );
+        request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
+        request.setLogLevel( determineCurrentRootLogLevel() );
 
-        if ( renderer.hasErrors() )
+        Toolchain tc = getToolchain();
+        if ( tc != null )
         {
-            if ( !skipPmdError )
-            {
-                getLog().error( "PMD processing errors:" );
-                getLog().error( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
-                throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
-            }
-            getLog().warn( "There are " + renderer.getErrors().size() + " PMD processing errors:" );
-            getLog().warn( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
+            getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
+            String javaExecutable = tc.findTool( "java" ); //NOI18N
+            request.setJavaExecutable( javaExecutable );
         }
 
-        removeExcludedViolations( renderer.getViolations() );
-
-        // always write XML report, as this might be needed by the check mojo
-        // we need to output it even if the file list is empty or we have no violations
-        // so the "check" goals can check for violations
-        Report report = renderer.asReport();
-        writeXmlReport( report );
-
-        // write any other format except for xml and html. xml has just been produced.
-        // html format is produced by the maven site formatter. Excluding html here
-        // avoids using PMD's own html formatter, which doesn't fit into the maven site
-        // considering the html/css styling
-        if ( !isHtml() && !isXml() )
-        {
-            writeFormattedReport( report );
-        }
-
-        if ( benchmark )
-        {
-            try ( PrintStream benchmarkFileStream = new PrintStream( benchmarkOutputFilename ) )
-            {
-                ( new TextReport() ).generate( Benchmarker.values(), benchmarkFileStream );
-            }
-            catch ( FileNotFoundException fnfe )
-            {
-                getLog().error( "Unable to generate benchmark file: " + benchmarkOutputFilename, fnfe );
-            }
-        }
+        pmdResult = PmdExecutor.execute( request );
     }
 
-    private void removeExcludedViolations( List<RuleViolation> violations )
+    protected String getSourceEncoding()
     {
-        getLog().debug( "Removing excluded violations. Using " + excludeFromFile.countExclusions()
-            + " configured exclusions." );
-        int violationsBefore = violations.size();
-
-        Iterator<RuleViolation> iterator = violations.iterator();
-        while ( iterator.hasNext() )
+        String encoding = super.getSourceEncoding();
+        if ( StringUtils.isEmpty( encoding ) )
         {
-            RuleViolation rv = iterator.next();
-            if ( excludeFromFile.isExcludedFromFailure( rv ) )
+            encoding = ReaderFactory.FILE_ENCODING;
+            if ( !filesToProcess.isEmpty() )
             {
-                iterator.remove();
+                getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+                               + ", i.e. build is platform dependent!" );
             }
         }
-
-        int numberOfExcludedViolations = violationsBefore - violations.size();
-        getLog().debug( "Excluded " + numberOfExcludedViolations + " violations." );
+        return encoding;
     }
 
-    private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources )
-            throws MavenReportException
+
+    /**
+     * Resolves the configured rulesets and copies them as files into the {@link #rulesetsTargetDirectory}.
+     *
+     * @return comma separated list of absolute file paths of ruleset files
+     * @throws MavenReportException if a ruleset could not be found
+     */
+    private String resolveRulesets() throws MavenReportException
     {
-        RuleSetFactory ruleSetFactory = new RuleSetFactory( new ResourceLoader(),
-                RulePriority.valueOf( this.minimumPriority ), true, true );
-        try
-        {
-            // load the ruleset once to log out any deprecated rules as warnings
-            ruleSetFactory.createRuleSets( pmdConfiguration.getRuleSets() );
-        }
-        catch ( RuleSetNotFoundException e1 )
-        {
-            throw new MavenReportException( "The ruleset could not be loaded", e1 );
-        }
+        // configure ResourceManager
+        locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
+        locator.addSearchPath( "url", "" );
+        locator.setOutputDirectory( rulesetsTargetDirectory );
 
+        String[] sets = new String[rulesets.length];
         try
         {
-            getLog().debug( "Executing PMD..." );
-            RuleContext ruleContext = new RuleContext();
-            PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
-                              Arrays.<Renderer>asList( renderer ) );
-
-            if ( getLog().isDebugEnabled() )
-            {
-                getLog().debug( "PMD finished. Found " + renderer.getViolations().size() + " violations." );
-            }
-        }
-        catch ( Exception e )
-        {
-            String message = "Failure executing PMD: " + e.getLocalizedMessage();
-            if ( !skipPmdError )
+            for ( int idx = 0; idx < rulesets.length; idx++ )
             {
-                throw new MavenReportException( message, e );
+                String set = rulesets[idx];
+                getLog().debug( "Preparing ruleset: " + set );
+                RuleSetReferenceId id = new RuleSetReferenceId( set );
+                File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
+                if ( null == ruleset )
+                {
+                    throw new MavenReportException( "Could not resolve " + set );
+                }
+                sets[idx] = ruleset.getAbsolutePath();
             }
-            getLog().warn( message, e );
         }
-        finally
+        catch ( ResourceNotFoundException | FileResourceCreationException e )
         {
-            ClassLoader classLoader = pmdConfiguration.getClassLoader();
-            if ( classLoader instanceof ClasspathClassLoader )
-            {
-                IOUtil.tryCloseClassLoader( classLoader );
-            }
+            throw new MavenReportException( e.getMessage(), e );
         }
+        return StringUtils.join( sets, "," );
     }
 
     private void generateMavenSiteReport( Locale locale )
@@ -595,10 +462,10 @@ public class PmdReport
         doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
         doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
         doxiaRenderer.setFiles( filesToProcess );
-        doxiaRenderer.setViolations( renderer.getViolations() );
+        doxiaRenderer.setViolations( pmdResult.getViolations() );
         if ( renderProcessingErrors )
         {
-            doxiaRenderer.setProcessingErrors( renderer.getErrors() );
+            doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
         }
 
         try
@@ -646,130 +513,7 @@ public class PmdReport
         return loc;
     }
 
-    private File writeReport( Report report, Renderer r, String extension ) throws MavenReportException
-    {
-        if ( r == null )
-        {
-            return null;
-        }
-
-        File targetFile = new File( targetDirectory, "pmd." + extension );
-        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
-        {
-            targetDirectory.mkdirs();
-
-            r.setWriter( writer );
-            r.start();
-            r.renderFileReport( report );
-            r.end();
-            r.flush();
-        }
-        catch ( IOException ioe )
-        {
-            throw new MavenReportException( ioe.getMessage(), ioe );
-        }
-
-        return targetFile;
-    }
-
-    /**
-     * Use the PMD renderers to render in any format aside from HTML and XML.
-     *
-     * @param report
-     * @throws MavenReportException
-     */
-    private void writeFormattedReport( Report report )
-        throws MavenReportException
-    {
-        Renderer r = createRenderer();
-        writeReport( report, r, format );
-    }
-
-    /**
-     * Use the PMD XML renderer to create the XML report format used by the check mojo later on.
-     *
-     * @param report
-     * @throws MavenReportException
-     */
-    private void writeXmlReport( Report report ) throws MavenReportException
-    {
-        File targetFile = writeReport( report, new XMLRenderer( getOutputEncoding() ), "xml" );
-        if ( includeXmlInSite )
-        {
-            File siteDir = getReportOutputDirectory();
-            siteDir.mkdirs();
-            try
-            {
-                FileUtils.copyFile( targetFile, new File( siteDir, "pmd.xml" ) );
-            }
-            catch ( IOException e )
-            {
-                throw new MavenReportException( e.getMessage(), e );
-            }
-        }
-    }
-
-    /**
-     * Constructs the PMD configuration class, passing it an argument that configures the target JDK.
-     *
-     * @return the resulting PMD
-     * @throws org.apache.maven.reporting.MavenReportException if targetJdk is not supported
-     */
-    public PMDConfiguration getPMDConfiguration()
-        throws MavenReportException
-    {
-        PMDConfiguration configuration = new PMDConfiguration();
-        LanguageVersion languageVersion = null;
-
-        if ( ( "java".equals( language ) || null == language ) && null != targetJdk )
-        {
-            languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "java " + targetJdk );
-            if ( languageVersion == null )
-            {
-                throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
-            }
-        }
-        else if ( "javascript".equals( language ) || "ecmascript".equals( language ) )
-        {
-            languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "ecmascript" );
-        }
-        else if ( "jsp".equals( language ) )
-        {
-            languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "jsp" );
-        }
-
-        if ( languageVersion != null )
-        {
-            getLog().debug( "Using language " + languageVersion );
-            configuration.setDefaultLanguageVersion( languageVersion );
-        }
-
-        if ( typeResolution )
-        {
-            configureTypeResolution( configuration );
-        }
-
-        if ( null != suppressMarker )
-        {
-            configuration.setSuppressMarker( suppressMarker );
-        }
-
-        configuration.setBenchmark( benchmark );
-
-        if ( analysisCache )
-        {
-            configuration.setAnalysisCacheLocation( analysisCacheLocation );
-            getLog().debug( "Using analysis cache location: " + analysisCacheLocation );
-        }
-        else
-        {
-            configuration.setIgnoreIncrementalAnalysis( true );
-        }
-
-        return configuration;
-    }
-
-    private void configureTypeResolution( PMDConfiguration configuration ) throws MavenReportException
+    private String determineAuxClasspath() throws MavenReportException
     {
         try
         {
@@ -837,7 +581,7 @@ public class PmdReport
                 getLog().debug( "Using aux classpath: " + classpath );
             }
             String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
-            configuration.prependClasspath( path );
+            return path;
         }
         catch ( Exception e )
         {
@@ -864,40 +608,11 @@ public class PmdReport
      *
      * @return the renderer based on the configured output
      * @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
+     * @deprecated Use {@link PmdExecutor#createRenderer(String, String)} instead.
      */
-    public final Renderer createRenderer()
-        throws MavenReportException
+    @Deprecated
+    public final Renderer createRenderer() throws MavenReportException
     {
-        Renderer result = null;
-        if ( "xml".equals( format ) )
-        {
-            result = new XMLRenderer( getOutputEncoding() );
-        }
-        else if ( "txt".equals( format ) )
-        {
-            result = new TextRenderer();
-        }
-        else if ( "csv".equals( format ) )
-        {
-            result = new CSVRenderer();
-        }
-        else if ( "html".equals( format ) )
-        {
-            result = new HTMLRenderer();
-        }
-        else if ( !"".equals( format ) && !"none".equals( format ) )
-        {
-            try
-            {
-                result = (Renderer) Class.forName( format ).getConstructor().newInstance();
-            }
-            catch ( Exception e )
-            {
-                throw new MavenReportException( "Can't find PMD custom format " + format + ": "
-                    + e.getClass().getName(), e );
-            }
-        }
-
-        return result;
+        return PmdExecutor.createRenderer( format, getOutputEncoding() );
     }
 }
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 83e32ce..f1dae2a 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReportGenerator.java
@@ -34,11 +34,11 @@ import java.util.Set;
 
 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.Violation;
 import org.codehaus.plexus.util.StringUtils;
 
-import net.sourceforge.pmd.Report.ProcessingError;
 import net.sourceforge.pmd.RulePriority;
-import net.sourceforge.pmd.RuleViolation;
 
 /**
  * Render the PMD violations into Doxia events.
@@ -56,7 +56,7 @@ public class PmdReportGenerator
 
     private ResourceBundle bundle;
 
-    private Set<RuleViolation> violations = new HashSet<>();
+    private Set<Violation> violations = new HashSet<>();
 
     private List<ProcessingError> processingErrors = new ArrayList<>();
 
@@ -83,12 +83,12 @@ public class PmdReportGenerator
         return bundle.getString( "report.pmd.title" );
     }
 
-    public void setViolations( Collection<RuleViolation> violations )
+    public void setViolations( Collection<Violation> violations )
     {
         this.violations = new HashSet<>( violations );
     }
 
-    public List<RuleViolation> getViolations()
+    public List<Violation> getViolations()
     {
         return new ArrayList<>( violations );
     }
@@ -183,30 +183,30 @@ public class PmdReportGenerator
         sink.section_( level );
     }
 
-    private void processSingleRuleViolation( RuleViolation ruleViolation, PmdFileInfo fileInfo )
+    private void processSingleRuleViolation( Violation ruleViolation, PmdFileInfo fileInfo )
     {
         sink.tableRow();
         sink.tableCell();
-        sink.link( ruleViolation.getRule().getExternalInfoUrl() );
-        sink.text( ruleViolation.getRule().getName() );
+        sink.link( ruleViolation.getExternalInfoUrl() );
+        sink.text( ruleViolation.getRule() );
         sink.link_();
         sink.tableCell_();
         sink.tableCell();
-        sink.text( ruleViolation.getDescription() );
+        sink.text( ruleViolation.getText() );
         sink.tableCell_();
 
         if ( this.renderRuleViolationPriority )
         {
             sink.tableCell();
-            sink.text( String.valueOf( ruleViolation.getRule().getPriority().getPriority() ) );
+            sink.text( String.valueOf( RulePriority.valueOf( ruleViolation.getPriority() ).getPriority() ) );
             sink.tableCell_();
         }
 
         sink.tableCell();
 
-        int beginLine = ruleViolation.getBeginLine();
+        int beginLine = ruleViolation.getBeginline();
         outputLineLink( beginLine, fileInfo );
-        int endLine = ruleViolation.getEndLine();
+        int endLine = ruleViolation.getEndline();
         if ( endLine != beginLine )
         {
             sink.text( "&#x2013;" ); // \u2013 is a medium long dash character
@@ -230,7 +230,7 @@ public class PmdReportGenerator
 
         // TODO files summary
 
-        List<RuleViolation> violations2 = new ArrayList<>( violations );
+        List<Violation> violations2 = new ArrayList<>( violations );
         renderViolationsTable( 2, violations2 );
 
         sink.section1_();
@@ -251,22 +251,22 @@ public class PmdReportGenerator
         sink.text( bundle.getString( "report.pmd.violationsByPriority" ) );
         sink.sectionTitle1_();
 
-        Map<RulePriority, List<RuleViolation>> violationsByPriority = new HashMap<>();
-        for ( RuleViolation violation : violations )
+        Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
+        for ( Violation violation : violations )
         {
-            RulePriority priority = violation.getRule().getPriority();
-            List<RuleViolation> violationSegment = violationsByPriority.get( priority );
+            RulePriority priority = RulePriority.valueOf( violation.getPriority() );
+            List<Violation> violationSegment = violationsByPriority.get( priority );
             if ( violationSegment == null )
             {
                 violationSegment = new ArrayList<>();
                 violationsByPriority.put( priority, violationSegment );
             }
-            violationsByPriority.get( violation.getRule().getPriority() ).add( violation );
+            violationSegment.add( violation );
         }
 
         for ( RulePriority priority : RulePriority.values() )
         {
-            List<RuleViolation> violationsWithPriority = violationsByPriority.get( priority );
+            List<Violation> violationsWithPriority = violationsByPriority.get( priority );
             if ( violationsWithPriority == null || violationsWithPriority.isEmpty() )
             {
                 continue;
@@ -294,18 +294,18 @@ public class PmdReportGenerator
         this.renderRuleViolationPriority = oldPriorityColumn;
     }
 
-    private void renderViolationsTable( int level, List<RuleViolation> violationSegment )
+    private void renderViolationsTable( int level, List<Violation> violationSegment )
     throws IOException
     {
-        Collections.sort( violationSegment, new Comparator<RuleViolation>()
+        Collections.sort( violationSegment, new Comparator<Violation>()
         {
             /** {@inheritDoc} */
-            public int compare( RuleViolation o1, RuleViolation o2 )
+            public int compare( Violation o1, Violation o2 )
             {
-                int filenames = o1.getFilename().compareTo( o2.getFilename() );
+                int filenames = o1.getFileName().compareTo( o2.getFileName() );
                 if ( filenames == 0 )
                 {
-                    return o1.getBeginLine() - o2.getBeginLine();
+                    return o1.getBeginline() - o2.getBeginline();
                 }
                 else
                 {
@@ -316,9 +316,9 @@ public class PmdReportGenerator
 
         boolean fileSectionStarted = false;
         String previousFilename = null;
-        for ( RuleViolation ruleViolation : violationSegment )
+        for ( Violation ruleViolation : violationSegment )
         {
-            String currentFn = ruleViolation.getFilename();
+            String currentFn = ruleViolation.getFileName();
             PmdFileInfo fileInfo = determineFileInfo( currentFn );
 
             if ( !currentFn.equalsIgnoreCase( previousFilename ) && fileSectionStarted )
@@ -371,7 +371,7 @@ public class PmdReportGenerator
             @Override
             public int compare( ProcessingError e1, ProcessingError e2 )
             {
-                return e1.getFile().compareTo( e2.getFile() );
+                return e1.getFilename().compareTo( e2.getFilename() );
             }
         } );
 
@@ -402,7 +402,7 @@ public class PmdReportGenerator
 
     private void processSingleProcessingError( ProcessingError error ) throws IOException
     {
-        String filename = error.getFile();
+        String filename = error.getFilename();
         PmdFileInfo fileInfo = determineFileInfo( filename );
         filename = makeFileSectionName( shortenFilename( filename, fileInfo ), fileInfo );
 
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java
new file mode 100644
index 0000000..4905eb9
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdExecutor.java
@@ -0,0 +1,347 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile;
+import org.apache.maven.reporting.MavenReportException;
+import org.codehaus.plexus.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.sourceforge.pmd.cpd.CPD;
+import net.sourceforge.pmd.cpd.CPDConfiguration;
+import net.sourceforge.pmd.cpd.CSVRenderer;
+import net.sourceforge.pmd.cpd.EcmascriptLanguage;
+import net.sourceforge.pmd.cpd.JSPLanguage;
+import net.sourceforge.pmd.cpd.JavaLanguage;
+import net.sourceforge.pmd.cpd.Language;
+import net.sourceforge.pmd.cpd.LanguageFactory;
+import net.sourceforge.pmd.cpd.Match;
+import net.sourceforge.pmd.cpd.SimpleRenderer;
+import net.sourceforge.pmd.cpd.XMLRenderer;
+import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
+
+/**
+ * Executes CPD with the configuration provided via {@link CpdRequest}.
+ */
+public class CpdExecutor extends Executor
+{
+    private static final Logger LOG = LoggerFactory.getLogger( CpdExecutor.class );
+
+    public static CpdResult execute( CpdRequest request ) throws MavenReportException
+    {
+        if ( request.getJavaExecutable() != null )
+        {
+            return fork( request );
+        }
+
+        ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader( CpdExecutor.class.getClassLoader() );
+            CpdExecutor cpdExecutor = new CpdExecutor( request );
+            return cpdExecutor.run();
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader( origLoader );
+        }
+    }
+
+    private static CpdResult fork( CpdRequest request )
+            throws MavenReportException
+    {
+        File basePmdDir = new File ( request.getTargetDirectory(), "pmd" );
+        basePmdDir.mkdirs();
+        File cpdRequestFile = new File( basePmdDir, "cpdrequest.bin" );
+        try ( ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( cpdRequestFile ) ) )
+        {
+            out.writeObject( request );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+
+        String classpath = buildClasspath();
+        ProcessBuilder pb = new ProcessBuilder();
+        // note: using env variable instead of -cp cli arg to avoid length limitations under Windows
+        pb.environment().put( "CLASSPATH", classpath );
+        pb.command().add( request.getJavaExecutable() );
+        pb.command().add( CpdExecutor.class.getName() );
+        pb.command().add( cpdRequestFile.getAbsolutePath() );
+
+        LOG.debug( "Executing: CLASSPATH={}, command={}", classpath, pb.command() );
+        try
+        {
+            final Process p = pb.start();
+            // Note: can't use pb.inheritIO(), since System.out/System.err has been modified after process start
+            // and inheritIO would only inherit file handles, not the changed streams.
+            ProcessStreamHandler.start( p.getInputStream(), System.out );
+            ProcessStreamHandler.start( p.getErrorStream(), System.err );
+            int exit = p.waitFor();
+            LOG.debug( "CpdExecutor exit code: {}", exit );
+            if ( exit != 0 )
+            {
+                throw new MavenReportException( "CpdExecutor exited with exit code " + exit );
+            }
+            return new CpdResult( new File( request.getTargetDirectory(), "cpd.xml" ), request.getOutputEncoding() );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+        catch ( InterruptedException e )
+        {
+            Thread.currentThread().interrupt();
+            throw new MavenReportException( e.getMessage(), e );
+        }
+    }
+
+    /**
+     * Execute CPD analysis from CLI.
+     *
+     * <p>
+     * Single arg with the filename to the serialized {@link CpdRequest}.
+     *
+     * <p>
+     * Exit-code: 0 = success, 1 = failure in executing
+     *
+     * @param args
+     */
+    public static void main( String[] args )
+    {
+        File requestFile = new File( args[0] );
+        try ( ObjectInputStream in = new ObjectInputStream( new FileInputStream( requestFile ) ) )
+        {
+            CpdRequest request = (CpdRequest) in.readObject();
+            CpdExecutor cpdExecutor = new CpdExecutor( request );
+            cpdExecutor.setupLogLevel( request.getLogLevel() );
+            cpdExecutor.run();
+            System.exit( 0 );
+        }
+        catch ( IOException | ClassNotFoundException | MavenReportException e )
+        {
+            LOG.error( e.getMessage(), e );
+        }
+        System.exit( 1 );
+    }
+
+    private final CpdRequest request;
+
+    /** Helper to exclude duplications from the result. */
+    private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile = new ExcludeDuplicationsFromFile();
+
+    public CpdExecutor( CpdRequest request )
+    {
+        this.request = Objects.requireNonNull( request );
+    }
+
+    private CpdResult run() throws MavenReportException
+    {
+        setupPmdLogging( request.isShowPmdLog(), request.isColorizedLog(), request.getLogLevel() );
+
+        try
+        {
+            excludeDuplicationsFromFile.loadExcludeFromFailuresData( request.getExcludeFromFailureFile() );
+        }
+        catch ( MojoExecutionException e )
+        {
+            throw new MavenReportException( "Error loading exclusions", e );
+        }
+
+        CPDConfiguration cpdConfiguration = new CPDConfiguration();
+        cpdConfiguration.setMinimumTileSize( request.getMinimumTokens() );
+        
+        Language cpdLanguage;
+        if ( "java".equals ( request.getLanguage() ) || null == request.getLanguage() )
+        {
+            cpdLanguage = new JavaLanguage( request.getLanguageProperties() );
+        }
+        else if ( "javascript".equals( request.getLanguage() ) )
+        {
+            cpdLanguage = new EcmascriptLanguage();
+        }
+        else if ( "jsp".equals( request.getLanguage() ) )
+        {
+            cpdLanguage = new JSPLanguage();
+        }
+        else
+        {
+            cpdLanguage = LanguageFactory.createLanguage( request.getLanguage(), request.getLanguageProperties() );
+        }
+        
+        cpdConfiguration.setLanguage( cpdLanguage );
+        cpdConfiguration.setSourceEncoding( request.getSourceEncoding() );
+
+        CPD cpd = new CPD( cpdConfiguration );
+        try
+        {
+            cpd.add( request.getFiles() );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+
+        LOG.debug( "Executing CPD..." );
+        cpd.go();
+        LOG.debug( "CPD finished." );
+
+        // always create XML format. we need to output it even if the file list is empty or we have no duplications
+        // so the "check" goals can check for violations
+        writeXmlReport( cpd );
+
+        // html format is handled by maven site report, xml format has already been rendered
+        String format = request.getFormat();
+        if ( !"html".equals( format ) && !"xml".equals( format ) )
+        {
+            writeFormattedReport( cpd );
+        }
+
+        return new CpdResult( new File( request.getTargetDirectory(), "cpd.xml" ), request.getOutputEncoding() );
+    }
+
+    private void writeXmlReport( CPD cpd ) throws MavenReportException
+    {
+        File targetFile = writeReport( cpd, new XMLRenderer( request.getOutputEncoding() ), "xml" );
+        if ( request.isIncludeXmlInSite() )
+        {
+            File siteDir = new File( request.getReportOutputDirectory() );
+            siteDir.mkdirs();
+            try
+            {
+                FileUtils.copyFile( targetFile, new File( siteDir, "cpd.xml" ) );
+            }
+            catch ( IOException e )
+            {
+                throw new MavenReportException( e.getMessage(), e );
+            }
+        }
+    }
+
+    private File writeReport( CPD cpd, CPDRenderer r, String extension ) throws MavenReportException
+    {
+        if ( r == null )
+        {
+            return null;
+        }
+
+        File targetDir = new File( request.getTargetDirectory() );
+        targetDir.mkdirs();
+        File targetFile = new File( targetDir, "cpd." + extension );
+        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ),
+                request.getOutputEncoding() ) )
+        {
+            r.render( filterMatches( cpd.getMatches() ), writer );
+            writer.flush();
+        }
+        catch ( IOException ioe )
+        {
+            throw new MavenReportException( ioe.getMessage(), ioe );
+        }
+        return targetFile;
+    }
+
+    private void writeFormattedReport( CPD cpd )
+            throws MavenReportException
+        {
+            CPDRenderer r = createRenderer( request.getFormat(), request.getOutputEncoding() );
+            writeReport( cpd, r, request.getFormat() );
+
+        }
+
+    /**
+     * Create and return the correct renderer for the output type.
+     *
+     * @return the renderer based on the configured output
+     * @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
+     */
+    public static CPDRenderer createRenderer( String format, String outputEncoding )
+        throws MavenReportException
+    {
+        CPDRenderer renderer = null;
+        if ( "xml".equals( format ) )
+        {
+            renderer = new XMLRenderer( outputEncoding );
+        }
+        else if ( "csv".equals( format ) )
+        {
+            renderer = new CSVRenderer();
+        }
+        else if ( "txt".equals( format ) )
+        {
+            renderer = new SimpleRenderer();
+        }
+        else if ( !"".equals( format ) && !"none".equals( format ) )
+        {
+            try
+            {
+                renderer = (CPDRenderer) Class.forName( format ).getConstructor().newInstance();
+            }
+            catch ( Exception e )
+            {
+                throw new MavenReportException( "Can't find CPD custom format " + format + ": "
+                    + e.getClass().getName(), e );
+            }
+        }
+
+        return renderer;
+    }
+
+    private Iterator<Match> filterMatches( Iterator<Match> matches )
+    {
+        LOG.debug( "Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
+            + " configured exclusions." );
+
+        List<Match> filteredMatches = new ArrayList<>();
+        int excludedDuplications = 0;
+        while ( matches.hasNext() )
+        {
+            Match match = matches.next();
+            if ( excludeDuplicationsFromFile.isExcludedFromFailure( match ) )
+            {
+                excludedDuplications++;
+            }
+            else
+            {
+                filteredMatches.add( match );
+            }
+        }
+
+        LOG.debug( "Excluded " + excludedDuplications + " duplications." );
+        return filteredMatches.iterator();
+    }
+
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdRequest.java b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdRequest.java
new file mode 100644
index 0000000..694625b
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdRequest.java
@@ -0,0 +1,209 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Data object to store all configuration options needed to execute CPD
+ * as a separate process.
+ * 
+ * <p>This class is intended to be serialized and read back.
+ * 
+ * <p>Some properties might be optional and can be <code>null</code>.
+ */
+public class CpdRequest implements Serializable
+{
+    private static final long serialVersionUID = -7585852992660240668L;
+
+    private String javaExecutable;
+
+    private int minimumTokens;
+    private String language;
+    private Properties languageProperties;
+    private String sourceEncoding;
+    private List<File> files = new ArrayList<>();
+
+    private boolean showPmdLog;
+    private boolean colorizedLog;
+    private String logLevel;
+
+    private String excludeFromFailureFile;
+    private String targetDirectory;
+    private String outputEncoding;
+    private String format;
+    private boolean includeXmlInSite;
+    private String reportOutputDirectory;
+
+    public void setJavaExecutable( String javaExecutable )
+    {
+        this.javaExecutable = javaExecutable;
+    }
+
+    public void setMinimumTokens( int minimumTokens )
+    {
+        this.minimumTokens = minimumTokens;
+    }
+
+    public void setLanguage( String language )
+    {
+        this.language = language;
+    }
+
+    public void setLanguageProperties( Properties languageProperties )
+    {
+        this.languageProperties = languageProperties;
+    }
+
+    public void setSourceEncoding( String sourceEncoding )
+    {
+        this.sourceEncoding = sourceEncoding;
+    }
+
+    public void addFiles( Collection<File> files )
+    {
+        this.files.addAll( files );
+    }
+
+    public void setExcludeFromFailureFile( String excludeFromFailureFile )
+    {
+        this.excludeFromFailureFile = excludeFromFailureFile;
+    }
+
+    public void setTargetDirectory( String targetDirectory )
+    {
+        this.targetDirectory = targetDirectory;
+    }
+
+    public void setOutputEncoding( String outputEncoding )
+    {
+        this.outputEncoding = outputEncoding;
+    }
+
+    public void setFormat( String format )
+    {
+        this.format = format;
+    }
+
+    public void setIncludeXmlInSite( boolean includeXmlInSite )
+    {
+        this.includeXmlInSite = includeXmlInSite;
+    }
+
+    public void setReportOutputDirectory( String reportOutputDirectory )
+    {
+        this.reportOutputDirectory = reportOutputDirectory;
+    }
+
+    public void setShowPmdLog( boolean showPmdLog )
+    {
+        this.showPmdLog = showPmdLog;
+    }
+
+    public void setColorizedLog( boolean colorizedLog )
+    {
+        this.colorizedLog = colorizedLog;
+    }
+
+    public void setLogLevel( String logLevel )
+    {
+        this.logLevel = logLevel;
+    }
+
+    public String getJavaExecutable()
+    {
+        return javaExecutable;
+    }
+
+    public int getMinimumTokens()
+    {
+        return minimumTokens;
+    }
+
+    public String getLanguage()
+    {
+        return language;
+    }
+
+    public Properties getLanguageProperties()
+    {
+        return languageProperties;
+    }
+
+    public String getSourceEncoding()
+    {
+        return sourceEncoding;
+    }
+
+    public List<File> getFiles()
+    {
+        return files;
+    }
+
+    public String getExcludeFromFailureFile()
+    {
+        return excludeFromFailureFile;
+    }
+
+    public String getTargetDirectory()
+    {
+        return targetDirectory;
+    }
+
+    public String getOutputEncoding()
+    {
+        return outputEncoding;
+    }
+
+    public String getFormat()
+    {
+        return format;
+    }
+
+    public boolean isIncludeXmlInSite()
+    {
+        return includeXmlInSite;
+    }
+
+    public String getReportOutputDirectory()
+    {
+        return reportOutputDirectory;
+    }
+
+    public boolean isShowPmdLog()
+    {
+        return showPmdLog;
+    }
+
+    public boolean isColorizedLog()
+    {
+        return colorizedLog;
+    }
+
+    public String getLogLevel()
+    {
+        return logLevel;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/CpdResult.java b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdResult.java
new file mode 100644
index 0000000..6ea18ce
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/CpdResult.java
@@ -0,0 +1,69 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.plugins.pmd.model.CpdErrorDetail;
+import org.apache.maven.plugins.pmd.model.Duplication;
+import org.apache.maven.plugins.pmd.model.io.xpp3.CpdXpp3Reader;
+import org.apache.maven.reporting.MavenReportException;
+
+/**
+ * Provides access to the result of the CPD analysis.
+ */
+public class CpdResult
+{
+    private final List<Duplication> duplications = new ArrayList<>();
+
+    public CpdResult( File report, String encoding ) throws MavenReportException
+    {
+        loadResult( report, encoding );
+    }
+
+    public List<Duplication> getDuplications()
+    {
+        return duplications;
+    }
+
+    public boolean hasDuplications()
+    {
+        return !duplications.isEmpty();
+    }
+
+    private void loadResult( File report, String encoding ) throws MavenReportException
+    {
+        try ( Reader reader1 = new InputStreamReader( new FileInputStream( report ), encoding ) )
+        {
+            CpdXpp3Reader reader = new CpdXpp3Reader();
+            CpdErrorDetail details = reader.read( reader1, false );
+            duplications.addAll( details.getDuplications() );
+        }
+        catch ( Exception e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/Executor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/Executor.java
new file mode 100644
index 0000000..ca17511
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/Executor.java
@@ -0,0 +1,184 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.SimpleFormatter;
+
+import org.apache.maven.cli.logging.Slf4jConfiguration;
+import org.apache.maven.cli.logging.Slf4jConfigurationFactory;
+import org.apache.maven.shared.utils.logging.MessageUtils;
+import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+
+abstract class Executor
+{
+    private static final Logger LOG = LoggerFactory.getLogger( Executor.class );
+
+    /**
+     * This holds a strong reference in case we configured the logger to
+     * redirect to slf4j. See {@link #showPmdLog}. Without a strong reference,
+     * the logger might be garbage collected and the redirect to slf4j is gone.
+     */
+    private java.util.logging.Logger julLogger;
+
+    protected void setupPmdLogging( boolean showPmdLog, boolean colorizedLog, String logLevel )
+    {
+        MessageUtils.setColorEnabled( colorizedLog );
+
+        if ( !showPmdLog )
+        {
+            return;
+        }
+
+        java.util.logging.Logger logger = java.util.logging.Logger.getLogger( "net.sourceforge.pmd" );
+
+        boolean slf4jBridgeAlreadyAdded = false;
+        for ( Handler handler : logger.getHandlers() )
+        {
+            if ( handler instanceof SLF4JBridgeHandler )
+            {
+                slf4jBridgeAlreadyAdded = true;
+                break;
+            }
+        }
+
+        if ( slf4jBridgeAlreadyAdded )
+        {
+            return;
+        }
+
+        SLF4JBridgeHandler handler = new SLF4JBridgeHandler();
+        SimpleFormatter formatter = new SimpleFormatter();
+        handler.setFormatter( formatter );
+        logger.setUseParentHandlers( false );
+        logger.addHandler( handler );
+        handler.setLevel( Level.ALL );
+        logger.setLevel( Level.ALL );
+        julLogger = logger;
+        julLogger.fine( "Configured jul-to-slf4j bridge for " + logger.getName() );
+    }
+
+    protected void setupLogLevel( String logLevel )
+    {
+        ILoggerFactory slf4jLoggerFactory = LoggerFactory.getILoggerFactory();
+        Slf4jConfiguration slf4jConfiguration = Slf4jConfigurationFactory
+                .getConfiguration( slf4jLoggerFactory );
+        if ( "debug".equals( logLevel ) )
+        {
+            slf4jConfiguration
+                    .setRootLoggerLevel( Slf4jConfiguration.Level.DEBUG );
+        }
+        else if ( "info".equals( logLevel ) )
+        {
+            slf4jConfiguration
+                    .setRootLoggerLevel( Slf4jConfiguration.Level.INFO );
+        }
+        else
+        {
+            slf4jConfiguration
+                    .setRootLoggerLevel( Slf4jConfiguration.Level.ERROR );
+        }
+        slf4jConfiguration.activate();
+    }
+
+    protected static String buildClasspath()
+    {
+        StringBuilder classpath = new StringBuilder();
+
+        // plugin classpath needs to come first
+        ClassLoader pluginClassloader = Executor.class.getClassLoader();
+        buildClasspath( classpath, pluginClassloader );
+
+        ClassLoader coreClassloader = ConsoleLogger.class.getClassLoader();
+        buildClasspath( classpath, coreClassloader );
+
+        return classpath.toString();
+    }
+
+    private static void buildClasspath( StringBuilder classpath, ClassLoader cl )
+    {
+        if ( cl instanceof URLClassLoader )
+        {
+            for ( URL url : ( (URLClassLoader) cl ).getURLs() )
+            {
+                String urlString = url.toString();
+                if ( urlString.startsWith( "file:" ) )
+                {
+                    String f = urlString.substring( 5 ); //  strip "file:"
+                    classpath.append( f ).append( File.pathSeparatorChar );
+                }
+            }
+        }
+    }
+
+    protected static class ProcessStreamHandler implements Runnable
+    {
+        private static final int BUFFER_SIZE = 8192;
+
+        private final BufferedInputStream in;
+        private final BufferedOutputStream out;
+
+        public static void start( InputStream in, OutputStream out )
+        {
+            Thread t = new Thread( new ProcessStreamHandler( in, out ) );
+            t.start();
+        }
+
+        private ProcessStreamHandler( InputStream in, OutputStream out )
+        {
+            this.in = new BufferedInputStream( in );
+            this.out = new BufferedOutputStream( out );
+        }
+
+        @Override
+        public void run()
+        {
+            byte[] buffer = new byte[BUFFER_SIZE];
+            try
+            {
+                int count = in.read( buffer );
+                while ( count != -1 )
+                {
+                    out.write( buffer, 0, count );
+                    out.flush();
+                    count = in.read( buffer );
+                }
+                out.flush();
+            }
+            catch ( IOException e )
+            {
+                LOG.error( e.getMessage(), e );
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java
new file mode 100644
index 0000000..b6f1603
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java
@@ -0,0 +1,501 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.pmd.ExcludeViolationsFromFile;
+import org.apache.maven.plugins.pmd.PmdCollectingRenderer;
+import org.apache.maven.reporting.MavenReportException;
+import org.codehaus.plexus.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.sourceforge.pmd.PMD;
+import net.sourceforge.pmd.PMDConfiguration;
+import net.sourceforge.pmd.Report;
+import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.RulePriority;
+import net.sourceforge.pmd.RuleSetFactory;
+import net.sourceforge.pmd.RuleSetNotFoundException;
+import net.sourceforge.pmd.RuleViolation;
+import net.sourceforge.pmd.RulesetsFactoryUtils;
+import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
+import net.sourceforge.pmd.benchmark.TimeTracker;
+import net.sourceforge.pmd.benchmark.TimingReport;
+import net.sourceforge.pmd.benchmark.TimingReportRenderer;
+import net.sourceforge.pmd.lang.Language;
+import net.sourceforge.pmd.lang.LanguageRegistry;
+import net.sourceforge.pmd.lang.LanguageVersion;
+import net.sourceforge.pmd.renderers.CSVRenderer;
+import net.sourceforge.pmd.renderers.HTMLRenderer;
+import net.sourceforge.pmd.renderers.Renderer;
+import net.sourceforge.pmd.renderers.TextRenderer;
+import net.sourceforge.pmd.renderers.XMLRenderer;
+import net.sourceforge.pmd.util.datasource.DataSource;
+import net.sourceforge.pmd.util.datasource.FileDataSource;
+
+/**
+ * Executes PMD with the configuration provided via {@link PmdRequest}.
+ */
+public class PmdExecutor extends Executor
+{
+    private static final Logger LOG = LoggerFactory.getLogger( PmdExecutor.class );
+
+    public static PmdResult execute( PmdRequest request ) throws MavenReportException
+    {
+        if ( request.getJavaExecutable() != null )
+        {
+            return fork( request );
+        }
+
+        // make sure the class loaders are correct and call this in the same JVM
+        ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader( PmdExecutor.class.getClassLoader() );
+            PmdExecutor executor = new PmdExecutor( request );
+            return executor.run();
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader( origLoader );
+        }
+    }
+
+    private static PmdResult fork( PmdRequest request )
+            throws MavenReportException
+    {
+        File basePmdDir = new File ( request.getTargetDirectory(), "pmd" );
+        basePmdDir.mkdirs();
+        File pmdRequestFile = new File( basePmdDir, "pmdrequest.bin" );
+        try ( ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( pmdRequestFile ) ) )
+        {
+            out.writeObject( request );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+
+        String classpath = buildClasspath();
+        ProcessBuilder pb = new ProcessBuilder();
+        // note: using env variable instead of -cp cli arg to avoid length limitations under Windows
+        pb.environment().put( "CLASSPATH", classpath );
+        pb.command().add( request.getJavaExecutable() );
+        pb.command().add( PmdExecutor.class.getName() );
+        pb.command().add( pmdRequestFile.getAbsolutePath() );
+
+        LOG.debug( "Executing: CLASSPATH={}, command={}", classpath, pb.command() );
+        try
+        {
+            final Process p = pb.start();
+            // Note: can't use pb.inheritIO(), since System.out/System.err has been modified after process start
+            // and inheritIO would only inherit file handles, not the changed streams.
+            ProcessStreamHandler.start( p.getInputStream(), System.out );
+            ProcessStreamHandler.start( p.getErrorStream(), System.err );
+            int exit = p.waitFor();
+            LOG.debug( "PmdExecutor exit code: {}", exit );
+            if ( exit != 0 )
+            {
+                throw new MavenReportException( "PmdExecutor exited with exit code " + exit );
+            }
+            return new PmdResult( new File( request.getTargetDirectory(), "pmd.xml" ), request.getOutputEncoding() );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+        catch ( InterruptedException e )
+        {
+            Thread.currentThread().interrupt();
+            throw new MavenReportException( e.getMessage(), e );
+        }
+    }
+
+    /**
+     * Execute PMD analysis from CLI.
+     * 
+     * <p>
+     * Single arg with the filename to the serialized {@link PmdRequest}.
+     * 
+     * <p>
+     * Exit-code: 0 = success, 1 = failure in executing
+     * 
+     * @param args
+     */
+    public static void main( String[] args )
+    {
+        File requestFile = new File( args[0] );
+        try ( ObjectInputStream in = new ObjectInputStream( new FileInputStream( requestFile ) ) )
+        {
+            PmdRequest request = (PmdRequest) in.readObject();
+            PmdExecutor pmdExecutor = new PmdExecutor( request );
+            pmdExecutor.setupLogLevel( request.getLogLevel() );
+            pmdExecutor.run();
+            System.exit( 0 );
+        }
+        catch ( IOException | ClassNotFoundException | MavenReportException e )
+        {
+            LOG.error( e.getMessage(), e );
+        }
+        System.exit( 1 );
+    }
+
+    private final PmdRequest request;
+
+    public PmdExecutor( PmdRequest request )
+    {
+        this.request = Objects.requireNonNull( request );
+    }
+
+    private PmdResult run() throws MavenReportException
+    {
+        setupPmdLogging( request.isShowPmdLog(), request.isColorizedLog(), request.getLogLevel() );
+
+        PMDConfiguration configuration = new PMDConfiguration();
+        LanguageVersion languageVersion = null;
+        Language language = LanguageRegistry
+                .findLanguageByTerseName( request.getLanguage() != null ? request.getLanguage() : "java" );
+        if ( language == null )
+        {
+            throw new MavenReportException( "Unsupported language: " + request.getLanguage() );
+        }
+        if ( request.getLanguageVersion() != null )
+        {
+            languageVersion = language.getVersion( request.getLanguageVersion() );
+            if ( languageVersion == null )
+            {
+                throw new MavenReportException( "Unsupported targetJdk value '" + request.getLanguageVersion() + "'." );
+            }
+        }
+        else
+        {
+            languageVersion = language.getDefaultVersion();
+        }
+        LOG.debug( "Using language " + languageVersion );
+        configuration.setDefaultLanguageVersion( languageVersion );
+
+        try
+        {
+            configuration.prependClasspath( request.getAuxClasspath() );
+        }
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+        if ( request.getSuppressMarker() != null )
+        {
+            configuration.setSuppressMarker( request.getSuppressMarker() );
+        }
+        if ( request.getAnalysisCacheLocation() != null )
+        {
+            configuration.setAnalysisCacheLocation( request.getAnalysisCacheLocation() );
+            LOG.debug( "Using analysis cache location: " + request.getAnalysisCacheLocation() );
+        }
+        else
+        {
+            configuration.setIgnoreIncrementalAnalysis( true );
+        }
+
+        configuration.setRuleSets( request.getRulesets() );
+        if ( request.getBenchmarkOutputLocation() != null )
+        {
+            configuration.setBenchmark( true );
+        }
+        List<File> files = request.getFiles();
+        List<DataSource> dataSources = new ArrayList<>( files.size() );
+        for ( File f : files )
+        {
+            dataSources.add( new FileDataSource( f ) );
+        }
+
+        PmdCollectingRenderer renderer = new PmdCollectingRenderer();
+
+        if ( StringUtils.isBlank( request.getRulesets() ) )
+        {
+            LOG.debug( "Skipping PMD execution as no rulesets are defined." );
+        }
+        else
+        {
+            if ( request.getBenchmarkOutputLocation() != null )
+            {
+                TimeTracker.startGlobalTracking();
+            }
+
+            try
+            {
+                processFilesWithPMD( configuration, dataSources, renderer );
+            }
+            finally
+            {
+                if ( request.getAuxClasspath() != null )
+                {
+                    ClassLoader classLoader = configuration.getClassLoader();
+                    if ( classLoader instanceof Closeable )
+                    {
+                        IOUtils.closeQuietly( (Closeable) classLoader );
+                    }
+                }
+                if ( request.getBenchmarkOutputLocation() != null )
+                {
+                    TimingReport timingReport = TimeTracker.stopGlobalTracking();
+                    writeBenchmarkReport( timingReport, request.getBenchmarkOutputLocation(),
+                            request.getOutputEncoding() );
+                }
+            }
+        }
+
+        if ( renderer.hasErrors() )
+        {
+            if ( !request.isSkipPmdError() )
+            {
+                LOG.error( "PMD processing errors:" );
+                LOG.error( renderer.getErrorsAsString( request.isDebugEnabled() ) );
+                throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
+            }
+            LOG.warn( "There are {} PMD processing errors:", renderer.getErrors().size() );
+            LOG.warn( renderer.getErrorsAsString( request.isDebugEnabled() ) );
+        }
+
+        removeExcludedViolations( renderer.getViolations() );
+
+        Report report = renderer.asReport();
+        // always write XML report, as this might be needed by the check mojo
+        // we need to output it even if the file list is empty or we have no violations
+        // so the "check" goals can check for violations
+        writeXmlReport( report );
+
+        // write any other format except for xml and html. xml has just been produced.
+        // html format is produced by the maven site formatter. Excluding html here
+        // avoids using PMD's own html formatter, which doesn't fit into the maven site
+        // considering the html/css styling
+        String format = request.getFormat();
+        if ( !"html".equals( format ) && !"xml".equals( format ) )
+        {
+            writeFormattedReport( report );
+        }
+
+        return new PmdResult( new File( request.getTargetDirectory(), "pmd.xml" ), request.getOutputEncoding() );
+    }
+
+    private void writeBenchmarkReport( TimingReport timingReport, String benchmarkOutputLocation, String encoding )
+    {
+        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( benchmarkOutputLocation ), encoding ) )
+        {
+            final TimingReportRenderer renderer = new TextTimingReportRenderer();
+            renderer.render( timingReport, writer );
+        }
+        catch ( IOException e )
+        {
+            LOG.error( "Unable to generate benchmark file: {}", benchmarkOutputLocation, e );
+        }
+    }
+
+    private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources,
+            PmdCollectingRenderer renderer ) throws MavenReportException
+    {
+        RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.createFactory(
+                RulePriority.valueOf( request.getMinimumPriority() ), true, true );
+        try
+        {
+            // load the ruleset once to log out any deprecated rules as warnings
+            ruleSetFactory.createRuleSets( pmdConfiguration.getRuleSets() );
+        }
+        catch ( RuleSetNotFoundException e1 )
+        {
+            throw new MavenReportException( "The ruleset could not be loaded", e1 );
+        }
+
+        try
+        {
+            LOG.debug( "Executing PMD..." );
+            RuleContext ruleContext = new RuleContext();
+            PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
+                    Arrays.<Renderer>asList( renderer ) );
+
+            LOG.debug( "PMD finished. Found {} violations.", renderer.getViolations().size() );
+        }
+        catch ( Exception e )
+        {
+            String message = "Failure executing PMD: " + e.getLocalizedMessage();
+            if ( !request.isSkipPmdError() )
+            {
+                throw new MavenReportException( message, e );
+            }
+            LOG.warn( message, e );
+        }
+    }
+
+    /**
+     * Use the PMD XML renderer to create the XML report format used by the
+     * check mojo later on.
+     *
+     * @param report
+     * @throws MavenReportException
+     */
+    private void writeXmlReport( Report report ) throws MavenReportException
+    {
+        File targetFile = writeReport( report, new XMLRenderer( request.getOutputEncoding() ), "xml" );
+        if ( request.isIncludeXmlInSite() )
+        {
+            File siteDir = new File( request.getReportOutputDirectory() );
+            siteDir.mkdirs();
+            try
+            {
+                FileUtils.copyFile( targetFile, new File( siteDir, "pmd.xml" ) );
+            }
+            catch ( IOException e )
+            {
+                throw new MavenReportException( e.getMessage(), e );
+            }
+        }
+    }
+
+    private File writeReport( Report report, Renderer r, String extension ) throws MavenReportException
+    {
+        if ( r == null )
+        {
+            return null;
+        }
+
+        File targetDir = new File( request.getTargetDirectory() );
+        targetDir.mkdirs();
+        File targetFile = new File( targetDir, "pmd." + extension );
+        try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ),
+                request.getOutputEncoding() ) )
+        {
+            r.setWriter( writer );
+            r.start();
+            r.renderFileReport( report );
+            r.end();
+            r.flush();
+        }
+        catch ( IOException ioe )
+        {
+            throw new MavenReportException( ioe.getMessage(), ioe );
+        }
+
+        return targetFile;
+    }
+
+    /**
+     * Use the PMD renderers to render in any format aside from HTML and XML.
+     *
+     * @param report
+     * @throws MavenReportException
+     */
+    private void writeFormattedReport( Report report )
+            throws MavenReportException
+    {
+        Renderer renderer = createRenderer( request.getFormat(), request.getOutputEncoding() );
+        writeReport( report, renderer, request.getFormat() );
+    }
+
+    /**
+     * Create and return the correct renderer for the output type.
+     *
+     * @return the renderer based on the configured output
+     * @throws org.apache.maven.reporting.MavenReportException
+     *             if no renderer found for the output type
+     */
+    public static Renderer createRenderer( String format, String outputEncoding ) throws MavenReportException
+    {
+        Renderer result = null;
+        if ( "xml".equals( format ) )
+        {
+            result = new XMLRenderer( outputEncoding );
+        }
+        else if ( "txt".equals( format ) )
+        {
+            result = new TextRenderer();
+        }
+        else if ( "csv".equals( format ) )
+        {
+            result = new CSVRenderer();
+        }
+        else if ( "html".equals( format ) )
+        {
+            result = new HTMLRenderer();
+        }
+        else if ( !"".equals( format ) && !"none".equals( format ) )
+        {
+            try
+            {
+                result = (Renderer) Class.forName( format ).getConstructor().newInstance();
+            }
+            catch ( Exception e )
+            {
+                throw new MavenReportException(
+                        "Can't find PMD custom format " + format + ": " + e.getClass().getName(), e );
+            }
+        }
+
+        return result;
+    }
+
+    private void removeExcludedViolations( List<RuleViolation> violations )
+            throws MavenReportException
+    {
+        ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
+
+        try
+        {
+            excludeFromFile.loadExcludeFromFailuresData( request.getExcludeFromFailureFile() );
+        }
+        catch ( MojoExecutionException e )
+        {
+            throw new MavenReportException( "Unable to load exclusions", e );
+        }
+
+        LOG.debug( "Removing excluded violations. Using {} configured exclusions.",
+                excludeFromFile.countExclusions() );
+        int violationsBefore = violations.size();
+
+        Iterator<RuleViolation> iterator = violations.iterator();
+        while ( iterator.hasNext() )
+        {
+            RuleViolation rv = iterator.next();
+            if ( excludeFromFile.isExcludedFromFailure( rv ) )
+            {
+                iterator.remove();
+            }
+        }
+
+        int numberOfExcludedViolations = violationsBefore - violations.size();
+        LOG.debug( "Excluded {} violations.", numberOfExcludedViolations );
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java
new file mode 100644
index 0000000..276f495
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java
@@ -0,0 +1,300 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Data object to store all configuration options needed to execute PMD
+ * as a separate process.
+ * 
+ * <p>This class is intended to be serialized and read back.
+ * 
+ * <p>Some properties might be optional and can be <code>null</code>.
+ */
+public class PmdRequest implements Serializable
+{
+    private static final long serialVersionUID = -6324416880563476455L;
+
+    private String javaExecutable;
+
+    private String language;
+    private String languageVersion;
+    private int minimumPriority;
+    private String auxClasspath;
+    private String suppressMarker;
+    private String analysisCacheLocation;
+    private String rulesets;
+    private String sourceEncoding;
+    private List<File> files = new ArrayList<>();
+
+    private boolean showPmdLog;
+    private boolean colorizedLog;
+    private String logLevel;
+    private boolean skipPmdError;
+
+    private String excludeFromFailureFile;
+    private String targetDirectory;
+    private String outputEncoding;
+    private String format;
+    private String benchmarkOutputLocation;
+    private boolean includeXmlInSite;
+    private String reportOutputDirectory;
+
+    /**
+     * Configure language and language version.
+     *
+     * @param language the language
+     * @param targetJdk the language version, optional, can be <code>null</code>
+     */
+    public void setLanguageAndVersion( String language, String targetJdk )
+    {
+        if ( "java".equals( language ) || null == language )
+        {
+            this.language = "java";
+            this.languageVersion = targetJdk;
+        }
+        else if ( "javascript".equals( language ) || "ecmascript".equals( language ) )
+        {
+            this.language = "ecmascript";
+        }
+        else if ( "jsp".equals( language ) )
+        {
+            this.language = "jsp";
+        }
+        else
+        {
+            this.language = language;
+        }
+    }
+
+    public void setJavaExecutable( String javaExecutable )
+    {
+        this.javaExecutable = javaExecutable;
+    }
+
+    public void setMinimumPriority( int minimumPriority )
+    {
+        this.minimumPriority = minimumPriority;
+    }
+
+    public void setAuxClasspath( String auxClasspath )
+    {
+        this.auxClasspath = auxClasspath;
+    }
+
+    public void setSuppressMarker( String suppressMarker )
+    {
+        this.suppressMarker = suppressMarker;
+    }
+
+    public void setAnalysisCacheLocation( String analysisCacheLocation )
+    {
+        this.analysisCacheLocation = analysisCacheLocation;
+    }
+
+    public void setRulesets( String rulesets )
+    {
+        this.rulesets = rulesets;
+    }
+
+    public void setSourceEncoding( String sourceEncoding )
+    {
+        this.sourceEncoding = sourceEncoding;
+    }
+
+    public void addFiles( Collection<File> files )
+    {
+        this.files.addAll( files );
+    }
+
+    public void setBenchmarkOutputLocation( String benchmarkOutputLocation )
+    {
+        this.benchmarkOutputLocation = benchmarkOutputLocation;
+    }
+
+    public void setTargetDirectory( String targetDirectory )
+    {
+        this.targetDirectory = targetDirectory;
+    }
+
+    public void setOutputEncoding( String outputEncoding )
+    {
+        this.outputEncoding = outputEncoding;
+    }
+
+    public void setFormat( String format )
+    {
+        this.format = format;
+    }
+
+    public void setShowPmdLog( boolean showPmdLog )
+    {
+        this.showPmdLog = showPmdLog;
+    }
+
+    public void setColorizedLog( boolean colorizedLog )
+    {
+        this.colorizedLog = colorizedLog;
+    }
+
+    public void setLogLevel( String logLevel )
+    {
+        this.logLevel = logLevel;
+    }
+
+    public void setSkipPmdError( boolean skipPmdError )
+    {
+        this.skipPmdError = skipPmdError;
+    }
+
+    public void setIncludeXmlInSite( boolean includeXmlInSite )
+    {
+        this.includeXmlInSite = includeXmlInSite;
+    }
+
+    public void setReportOutputDirectory( String reportOutputDirectory )
+    {
+        this.reportOutputDirectory = reportOutputDirectory;
+    }
+
+    public void setExcludeFromFailureFile( String excludeFromFailureFile )
+    {
+        this.excludeFromFailureFile = excludeFromFailureFile;
+    }
+
+
+
+
+
+    public String getJavaExecutable()
+    {
+        return javaExecutable;
+    }
+
+    public String getLanguage()
+    {
+        return language;
+    }
+
+    public String getLanguageVersion()
+    {
+        return languageVersion;
+    }
+
+    public int getMinimumPriority()
+    {
+        return minimumPriority;
+    }
+
+    public String getAuxClasspath()
+    {
+        return auxClasspath;
+    }
+
+    public String getSuppressMarker()
+    {
+        return suppressMarker;
+    }
+
+    public String getAnalysisCacheLocation()
+    {
+        return analysisCacheLocation;
+    }
+
+    public String getRulesets()
+    {
+        return rulesets;
+    }
+
+    public String getSourceEncoding()
+    {
+        return sourceEncoding;
+    }
+
+    public List<File> getFiles()
+    {
+        return files;
+    }
+
+    public String getBenchmarkOutputLocation()
+    {
+        return benchmarkOutputLocation;
+    }
+
+    public String getTargetDirectory()
+    {
+        return targetDirectory;
+    }
+
+    public String getOutputEncoding()
+    {
+        return outputEncoding;
+    }
+
+    public String getFormat()
+    {
+        return format;
+    }
+
+    public boolean isShowPmdLog()
+    {
+        return showPmdLog;
+    }
+
+    public boolean isColorizedLog()
+    {
+        return colorizedLog;
+    }
+
+    public String getLogLevel()
+    {
+        return logLevel;
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return "debug".equals( logLevel );
+    }
+
+    public boolean isSkipPmdError()
+    {
+        return skipPmdError;
+    }
+
+    public boolean isIncludeXmlInSite()
+    {
+        return includeXmlInSite;
+    }
+
+    public String getReportOutputDirectory()
+    {
+        return reportOutputDirectory;
+    }
+
+    public String getExcludeFromFailureFile()
+    {
+        return excludeFromFailureFile;
+    }
+}
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
new file mode 100644
index 0000000..504f633
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java
@@ -0,0 +1,94 @@
+package org.apache.maven.plugins.pmd.exec;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+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.Violation;
+import org.apache.maven.plugins.pmd.model.io.xpp3.PmdXpp3Reader;
+import org.apache.maven.reporting.MavenReportException;
+
+/**
+ * Provides access to the result of the pmd analysis.
+ */
+public class PmdResult
+{
+    private final List<ProcessingError> processingErrors = new ArrayList<>();
+    private final List<Violation> violations = new ArrayList<>();
+
+    public static final PmdResult EMPTY = new PmdResult();
+
+    private PmdResult()
+    {
+    }
+
+    public PmdResult( File pmdFile, String encoding ) throws MavenReportException
+    {
+        loadResult( pmdFile, encoding );
+    }
+
+    public boolean hasViolations()
+    {
+        return !violations.isEmpty();
+    }
+
+    private void loadResult( File pmdFile, String encoding ) throws MavenReportException
+    {
+        try ( Reader reader1 = new InputStreamReader( new FileInputStream( pmdFile ), encoding ) )
+        {
+            PmdXpp3Reader reader = new PmdXpp3Reader();
+            PmdErrorDetail details = reader.read( reader1, false );
+            processingErrors.addAll( details.getErrors() );
+
+            for ( PmdFile file : details.getFiles() )
+            {
+                String filename = file.getName();
+                for ( Violation violation : file.getViolations() )
+                {
+                    violation.setFileName( filename );
+                    violations.add( violation );
+                }
+            }
+        }
+        catch ( Exception e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+    }
+
+    public Collection<Violation> getViolations()
+    {
+        return violations;
+    }
+
+    public Collection<ProcessingError> getErrors()
+    {
+        return processingErrors;
+    }
+}
diff --git a/src/main/mdo/pmd.mdo b/src/main/mdo/pmd.mdo
index 52bb58d..2c2a084 100644
--- a/src/main/mdo/pmd.mdo
+++ b/src/main/mdo/pmd.mdo
@@ -45,15 +45,13 @@ under the License.
             <multiplicity>*</multiplicity>
           </association>
         </field>
-        <!-- 
         <field>
           <name>errors</name>
           <association xml.tagName="error" xml.itemsStyle="flat">
-            <type>PmdError</type>
+            <type>ProcessingError</type>
             <multiplicity>*</multiplicity>
           </association>
         </field>
-         -->
       </fields>
     </class>
     <class>
@@ -79,6 +77,10 @@ under the License.
           <name>beginline</name>
           <type>int</type>
         </field>
+        <field xml.attribute="true">
+            <name>endline</name>
+            <type>int</type>
+        </field>
         <field xml.tagName="class" xml.attribute="true">
           <name>violationClass</name>
           <type>String</type>
@@ -95,6 +97,10 @@ under the License.
           <name>priority</name>
           <type>int</type>
         </field>
+        <field xml.attribute="true">
+            <name>externalInfoUrl</name>
+            <type>String</type>
+        </field>
         <field xml.content="true">
           <name>text</name>
           <type>String</type>
@@ -120,8 +126,22 @@ under the License.
         </codeSegment>
       </codeSegments>
     </class>
-  </classes>   
-    
- 
-    
+    <class>
+        <name>ProcessingError</name>
+        <fields>
+            <field xml.attribute="true">
+                <name>filename</name>
+                <type>String</type>
+            </field>
+            <field xml.attribute="true">
+                <name>msg</name>
+                <type>String</type>
+            </field>
+            <field xml.content="true">
+                <name>detail</name>
+                <type>String</type>
+            </field>
+        </fields>
+    </class>
+  </classes>
 </model>
\ No newline at end of file
diff --git a/src/site/apt/examples/targetJdk.apt.vm b/src/site/apt/examples/targetJdk.apt.vm
index 6e5f367..bd4f20f 100644
--- a/src/site/apt/examples/targetJdk.apt.vm
+++ b/src/site/apt/examples/targetJdk.apt.vm
@@ -1,9 +1,10 @@
  ------
- Target JDK
+ Target JDK and Toolchains
  ------
  Dennis Lundberg
+ Andreas Dangel
  ------
- 2008-01-02
+ 2020-10-02
  ------
 
  ~~ Licensed to the Apache Software Foundation (ASF) under one
@@ -52,6 +53,17 @@ Target JDK
 </project>
 +--------------------+
 
+Using Maven Toolchains
 
+ Since version 3.14.0 of the PMD plugin, toolchains are supported. This helps if the build system is
+ running a different JDK than being used for compiling. PMD reads the class files for
+ {{{../pmd-mojo.html#typeResolution}type resolution}} and this fails with ClassFormatErrors
+ if the JDK version is incorrect.
 
+ To set this up, refer to the {{{/guides/mini/guide-using-toolchains.html}Guide to Using Toolchains}}, which makes use
+ of the {{{/plugins/maven-toolchains-plugin/}Maven Toolchains Plugin}}.
 
+ With the maven-toolchains-plugin you configure 1 default JDK toolchain for all related maven-plugins.
+ Since maven-pmd-plugin 3.14.0 when using with Maven 3.3.1+ it is also possible to give the plugin its own
+ toolchain, which can be useful in case of different JDK calls per execution block (e.g. the test sources require a
+ different JDK compared to the main sources).
diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm
index ff3bceb..348c892 100644
--- a/src/site/apt/index.apt.vm
+++ b/src/site/apt/index.apt.vm
@@ -88,7 +88,7 @@ ${project.name}
 
   * {{{./examples/removeReport.html}Remove Report}}
 
-  * {{{./examples/targetJdk.html}Target JDK}}
+  * {{{./examples/targetJdk.html}Target JDK and Toolchains}}
 
   * {{{./examples/usingRuleSets.html}Using Rule Sets}}
 
diff --git a/src/site/site.xml b/src/site/site.xml
index 455f516..c7c6561 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -36,7 +36,7 @@ under the License.
       <item name="Upgrading PMD at Runtime" href="examples/upgrading-PMD-at-runtime.html"/>
       <item name="Multimodule Configuration" href="examples/multi-module-config.html"/>
       <item name="Remove Report" href="examples/removeReport.html"/>
-      <item name="Target JDK" href="examples/targetJdk.html"/>
+      <item name="Target JDK and Toolchains" href="examples/targetJdk.html"/>
       <item name="Using Rule Sets" href="examples/usingRuleSets.html"/>
       <item name="Violation Checking" href="examples/violationChecking.html"/>
       <item name="Analyzing JavaScript" href="examples/javascriptReport.html"/>
diff --git a/src/test/java/org/apache/maven/plugins/pmd/AbstractPmdReportTest.java b/src/test/java/org/apache/maven/plugins/pmd/AbstractPmdReportTest.java
index 3c9af8f..64f961e 100644
--- a/src/test/java/org/apache/maven/plugins/pmd/AbstractPmdReportTest.java
+++ b/src/test/java/org/apache/maven/plugins/pmd/AbstractPmdReportTest.java
@@ -25,9 +25,9 @@ import java.io.Writer;
 import java.util.Locale;
 
 import org.apache.maven.doxia.site.decoration.DecorationModel;
+import org.apache.maven.doxia.siterenderer.DocumentContent;
 import org.apache.maven.doxia.siterenderer.RendererException;
 import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
-import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
 import org.apache.maven.plugin.testing.AbstractMojoTestCase;
 import org.codehaus.plexus.util.WriterFactory;
 
@@ -39,6 +39,14 @@ import org.codehaus.plexus.util.WriterFactory;
 public abstract class AbstractPmdReportTest
     extends AbstractMojoTestCase
 {
+    @Override
+    protected void setUp()
+        throws Exception
+    {
+        super.setUp();
+        CapturingPrintStream.init( true );
+    }
+
     /**
      * Renderer the sink from the report mojo.
      *
@@ -59,7 +67,7 @@ public abstract class AbstractPmdReportTest
 
         try ( Writer writer = WriterFactory.newXmlWriter( outputHtml ) )
         {
-            mojo.getSiteRenderer().generateDocument( writer, (SiteRendererSink) mojo.getSink(), context );
+            mojo.getSiteRenderer().mergeDocumentIntoSite( writer, (DocumentContent) mojo.getSink(), context );
         }
     }
 
diff --git a/src/test/java/org/apache/maven/plugins/pmd/CapturingPrintStream.java b/src/test/java/org/apache/maven/plugins/pmd/CapturingPrintStream.java
new file mode 100644
index 0000000..09adfd5
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/pmd/CapturingPrintStream.java
@@ -0,0 +1,82 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import java.io.PrintStream;
+
+import org.slf4j.impl.MavenSlf4jSimpleFriend;
+
+/**
+ * Captures log output from simple slf4j for asserting in unit tests.
+ */
+class CapturingPrintStream extends PrintStream {
+    private final boolean quiet;
+    private StringBuilder buffer = new StringBuilder();
+
+    private CapturingPrintStream( boolean quiet ) {
+        super( System.out, true );
+        this.quiet = quiet;
+    }
+
+    @Override
+    public void println( String x )
+    {
+        if ( !quiet )
+        {
+            super.println( x );
+        }
+        buffer.append( x ).append( System.lineSeparator() );
+    }
+
+    public static void init( boolean quiet )
+    {
+        CapturingPrintStream capture = get();
+        if ( capture != null )
+        {
+            capture.buffer.setLength( 0 );
+        }
+        else
+        {
+            capture = new CapturingPrintStream( quiet );
+            System.setOut( capture );
+            MavenSlf4jSimpleFriend.init();
+        }
+    }
+
+    public static CapturingPrintStream get()
+    {
+        if ( System.out instanceof CapturingPrintStream )
+        {
+            return (CapturingPrintStream) System.out;
+        }
+        return null;
+    }
+
+    public static String getOutput()
+    {
+        CapturingPrintStream stream = get();
+        if ( stream != null )
+        {
+            stream.flush();
+            return stream.buffer.toString();
+        }
+        return "";
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java b/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java
index 91d504a..c016283 100644
--- a/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java
+++ b/src/test/java/org/apache/maven/plugins/pmd/CpdReportTest.java
@@ -23,23 +23,11 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
-import net.sourceforge.pmd.PMD;
-import net.sourceforge.pmd.cpd.CPD;
-import net.sourceforge.pmd.cpd.CPDConfiguration;
-import net.sourceforge.pmd.cpd.JavaLanguage;
-import net.sourceforge.pmd.cpd.Mark;
-import net.sourceforge.pmd.cpd.Match;
-import net.sourceforge.pmd.cpd.SourceCode;
-import net.sourceforge.pmd.cpd.TokenEntry;
-
 import org.apache.commons.lang3.StringUtils;
 import org.codehaus.plexus.util.FileUtils;
 import org.w3c.dom.Document;
@@ -88,14 +76,8 @@ public class CpdReportTest
         // check the contents of cpd.html
         String str = readFile( new File( getBasedir(), "target/test/unit/default-configuration/target/site/cpd.html" ) );
         assertTrue( lowerCaseContains( str, "AppSample.java" ) );
-
-        str = readFile( new File( getBasedir(), "target/test/unit/default-configuration/target/site/cpd.html" ) );
         assertTrue( lowerCaseContains( str, "App.java" ) );
-
-        str = readFile( new File( getBasedir(), "target/test/unit/default-configuration/target/site/cpd.html" ) );
         assertTrue( lowerCaseContains( str, "public String dup( String str )" ) );
-
-        str = readFile( new File( getBasedir(), "target/test/unit/default-configuration/target/site/cpd.html" ) );
         assertTrue( lowerCaseContains( str, "tmp = tmp + str.substring( i, i + 1);" ) );
     }
 
@@ -216,35 +198,6 @@ public class CpdReportTest
         return str.toString();
     }
 
-    private CPD prepareMockCpd( String duplicatedCodeFragment )
-    {
-        TokenEntry tFirstEntry = new TokenEntry( "public java", "MyClass.java", 2 );
-        TokenEntry tSecondEntry = new TokenEntry( "public java", "MyClass3.java", 2 );
-        SourceCode sourceCodeFirst = new SourceCode(new SourceCode.StringCodeLoader(
-                PMD.EOL + duplicatedCodeFragment + PMD.EOL, "MyClass.java"));
-        SourceCode sourceCodeSecond = new SourceCode(new SourceCode.StringCodeLoader(
-                PMD.EOL + duplicatedCodeFragment + PMD.EOL, "MyClass3.java"));
-
-        List<Match> tList = new ArrayList<>();
-        Mark tFirstMark = new Mark( tFirstEntry );
-        tFirstMark.setSourceCode(sourceCodeFirst);
-        tFirstMark.setLineCount(1);
-        Mark tSecondMark = new Mark( tSecondEntry );
-        tSecondMark.setSourceCode(sourceCodeSecond);
-        tSecondMark.setLineCount(1);
-        Match tMatch = new Match( 2, tFirstMark, tSecondMark );
-        tList.add( tMatch );
-
-        CPDConfiguration cpdConfiguration = new CPDConfiguration();
-        cpdConfiguration.setMinimumTileSize( 100 );
-        cpdConfiguration.setLanguage( new JavaLanguage() );
-        cpdConfiguration.setEncoding( "UTF-8" );
-        CPD tCpd = new MockCpd( cpdConfiguration, tList.iterator() );
-
-        tCpd.go();
-        return tCpd;
-    }
-
     public void testWriteNonHtml()
         throws Exception
     {
@@ -253,10 +206,7 @@ public class CpdReportTest
                       "src/test/resources/unit/default-configuration/cpd-default-configuration-plugin-config.xml" );
         CpdReport mojo = (CpdReport) lookupMojo( "cpd", testPom );
         assertNotNull( mojo );
-
-        String duplicatedCodeFragment = "// ----- duplicated code example -----";
-        CPD tCpd = prepareMockCpd( duplicatedCodeFragment );
-        mojo.writeXmlReport( tCpd );
+        mojo.execute();
 
         File tReport = new File( getBasedir(), "target/test/unit/default-configuration/target/cpd.xml" );
 
@@ -265,9 +215,10 @@ public class CpdReportTest
         assertNotNull( pmdCpdDocument );
 
         String str = readFile( tReport );
-        assertTrue( lowerCaseContains( str, "MyClass.java" ) );
-        assertTrue( lowerCaseContains( str, "MyClass3.java" ) );
-        assertTrue( lowerCaseContains( str, duplicatedCodeFragment ) );
+        assertTrue( lowerCaseContains( str, "AppSample.java" ) );
+        assertTrue( lowerCaseContains( str, "App.java" ) );
+        assertTrue( lowerCaseContains( str, "public String dup( String str )" ) );
+        assertTrue( lowerCaseContains( str, "tmp = tmp + str.substring( i, i + 1);" ) );
     }
 
     /**
@@ -282,10 +233,7 @@ public class CpdReportTest
                           "src/test/resources/unit/default-configuration/cpd-report-include-xml-in-site-plugin-config.xml" );
         CpdReport mojo = (CpdReport) lookupMojo( "cpd", testPom );
         assertNotNull( mojo );
-
-        String duplicatedCodeFragment = "// ----- duplicated code example -----";
-        CPD tCpd = prepareMockCpd( duplicatedCodeFragment );
-        mojo.writeXmlReport( tCpd );
+        mojo.execute();
 
         File tReport = new File( getBasedir(), "target/test/unit/default-configuration/target/cpd.xml" );
         assertTrue( FileUtils.fileExists( tReport.getAbsolutePath() ) );
@@ -301,6 +249,7 @@ public class CpdReportTest
         assertTrue( FileUtils.fileExists( siteReport.getAbsolutePath() ) );
         String siteReportContent = readFile( siteReport );
         assertTrue( siteReportContent.contains( "</pmd-cpd>" ) );
+        assertEquals( str, siteReportContent );
     }
 
 
@@ -406,25 +355,4 @@ public class CpdReportTest
         String str = readFile( generatedFile );
         assertEquals( 0, StringUtils.countMatches( str, "<duplication" ) );
     }
-
-    public static class MockCpd
-        extends CPD
-    {
-
-        private Iterator<Match> matches;
-
-        public MockCpd( CPDConfiguration configuration, Iterator<Match> tMatch )
-        {
-            super( configuration );
-            matches = tMatch;
-        }
-
-        @Override
-        public Iterator<Match> getMatches()
-        {
-            return matches;
-        }
-
-    }
-
 }
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 3bb5b04..4718d87 100644
--- a/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
+++ b/src/test/java/org/apache/maven/plugins/pmd/PmdReportTest.java
@@ -20,11 +20,9 @@ package org.apache.maven.plugins.pmd;
  */
 
 import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.PrintStream;
 import java.net.ServerSocket;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -32,6 +30,7 @@ import java.util.Locale;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.plugins.pmd.exec.PmdExecutor;
 import org.apache.maven.reporting.MavenReportException;
 import org.codehaus.plexus.util.FileUtils;
 
@@ -47,6 +46,7 @@ import com.github.tomakehurst.wiremock.client.WireMock;
 public class PmdReportTest
     extends AbstractPmdReportTest
 {
+
     /**
      * {@inheritDoc}
      */
@@ -578,33 +578,23 @@ public class PmdReportTest
                 "src/test/resources/unit/processing-error/pmd-processing-error-skip-plugin-config.xml" );
         PmdReport mojo = (PmdReport) lookupMojo( "pmd", testPom );
 
-        PrintStream originalOut = System.out;
-        ByteArrayOutputStream logging = new ByteArrayOutputStream();
-        System.setOut( new PrintStream( logging ) );
-
-        try {
-            mojo.execute();
-            String output = logging.toString();
-            assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
-
-            File generatedFile = new File( getBasedir(), "target/test/unit/parse-error/target/pmd.xml" );
-            assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
-            String str = readFile( generatedFile );
-            assertTrue( str.contains( "Error while parsing" ) );
-            // The parse exception must be in the XML report
-            assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
+        mojo.execute();
+        String output = CapturingPrintStream.getOutput();
+        assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
 
-            generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
-            renderer( mojo, generatedFile );
-            assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
-            str = readFile( generatedFile );
-            // The parse exception must also be in the HTML report
-            assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
+        File generatedFile = new File( getBasedir(), "target/test/unit/parse-error/target/pmd.xml" );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+        String str = readFile( generatedFile );
+        assertTrue( str.contains( "Error while parsing" ) );
+        // The parse exception must be in the XML report
+        assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
 
-        } finally {
-            System.setOut( originalOut );
-            System.out.println( logging.toString() );
-        }
+        generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
+        renderer( mojo, generatedFile );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+        str = readFile( generatedFile );
+        // The parse exception must also be in the HTML report
+        assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
     }
 
     public void testPMDProcessingErrorWithDetailsNoReport()
@@ -614,33 +604,23 @@ public class PmdReportTest
                 "src/test/resources/unit/processing-error/pmd-processing-error-no-report-plugin-config.xml" );
         PmdReport mojo = (PmdReport) lookupMojo( "pmd", testPom );
 
-        PrintStream originalOut = System.out;
-        ByteArrayOutputStream logging = new ByteArrayOutputStream();
-        System.setOut( new PrintStream( logging ) );
-
-        try {
-            mojo.execute();
-            String output = logging.toString();
-            assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
-
-            File generatedFile = new File( getBasedir(), "target/test/unit/parse-error/target/pmd.xml" );
-            assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
-            String str = readFile( generatedFile );
-            assertTrue( str.contains( "Error while parsing" ) );
-            // The parse exception must be in the XML report
-            assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
+        mojo.execute();
+        String output = CapturingPrintStream.getOutput();
+        assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
 
-            generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
-            renderer( mojo, generatedFile );
-            assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
-            str = readFile( generatedFile );
-            // The parse exception must NOT be in the HTML report, since reportProcessingErrors is false
-            assertFalse( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
+        File generatedFile = new File( getBasedir(), "target/test/unit/parse-error/target/pmd.xml" );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+        String str = readFile( generatedFile );
+        assertTrue( str.contains( "Error while parsing" ) );
+        // The parse exception must be in the XML report
+        assertTrue( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
 
-        } finally {
-            System.setOut( originalOut );
-            System.out.println( logging.toString() );
-        }
+        generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
+        renderer( mojo, generatedFile );
+        assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+        str = readFile( generatedFile );
+        // The parse exception must NOT be in the HTML report, since reportProcessingErrors is false
+        assertFalse( str.contains( "ParseException: Encountered \"\" at line 23, column 5." ) );
     }
 
     public void testPMDExcludeRootsShouldExcludeSubdirectories() throws Exception {
@@ -675,17 +655,13 @@ public class PmdReportTest
 
     public void testCustomRenderer() throws MavenReportException
     {
-        PmdReport pmdReport = new PmdReport();
-        pmdReport.format = "net.sourceforge.pmd.renderers.TextRenderer";
-        final Renderer renderer = pmdReport.createRenderer();
+        final Renderer renderer = PmdExecutor.createRenderer( "net.sourceforge.pmd.renderers.TextRenderer", "UTF-8" );
         assertNotNull(renderer);
     }
 
     public void testCodeClimateRenderer() throws MavenReportException
     {
-        PmdReport pmdReport = new PmdReport();
-        pmdReport.format = "net.sourceforge.pmd.renderers.CodeClimateRenderer";
-        final Renderer renderer = pmdReport.createRenderer();
+        final Renderer renderer = PmdExecutor.createRenderer( "net.sourceforge.pmd.renderers.CodeClimateRenderer", "UTF-8" );
         assertNotNull(renderer);
     }
 }
diff --git a/src/it/MPMD-244-logging/invoker.properties b/src/test/resources/simplelogger.properties
similarity index 51%
copy from src/it/MPMD-244-logging/invoker.properties
copy to src/test/resources/simplelogger.properties
index ea9f79e..64b331b 100644
--- a/src/it/MPMD-244-logging/invoker.properties
+++ b/src/test/resources/simplelogger.properties
@@ -15,5 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-invoker.goals = clean pmd:check
-invoker.maven.version = 3.1.0+
+org.slf4j.simpleLogger.defaultLogLevel=info
+org.slf4j.simpleLogger.showDateTime=false
+org.slf4j.simpleLogger.showThreadName=false
+org.slf4j.simpleLogger.showLogName=false
+org.slf4j.simpleLogger.logFile=System.out
+org.slf4j.simpleLogger.cacheOutputStream=true
+org.slf4j.simpleLogger.levelInBrackets=true
+org.slf4j.simpleLogger.log.Sisu=info
+org.slf4j.simpleLogger.warnLevelString=WARNING
+
+# MNG-6181: mvn -X also prints all debug logging from HttpClient
+# Be aware that the shaded packages are used
+# org.apache.http -> org.apache.maven.wagon.providers.http.httpclient
+org.slf4j.simpleLogger.log.org.apache.maven.wagon.providers.http.httpclient=off
+org.slf4j.simpleLogger.log.org.apache.maven.wagon.providers.http.httpclient.wire=off