Merge pull request #33 from apache/unused
[MPMD-298] remove httpclient dependency
diff --git a/README.md b/README.md
index 4ba5215..f930f97 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,8 @@
[![ASF Jira](https://img.shields.io/endpoint?url=https%3A%2F%2Fmaven.apache.org%2Fbadges%2Fasf_jira-MPMD.json)][jira]
[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)][license]
[![Maven Central](https://img.shields.io/maven-central/v/org.apache.maven.plugins/maven-pmd-plugin.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.apache.maven.plugins/maven-pmd-plugin)
-[![Jenkins Status](https://img.shields.io/jenkins/s/https/builds.apache.org/job/maven-box/job/maven-pmd-plugin/job/master.svg?)][build]
-[![Jenkins tests](https://img.shields.io/jenkins/t/https/builds.apache.org/job/maven-box/job/maven-pmd-plugin/job/master.svg?)][test-results]
+[![Jenkins Status](https://img.shields.io/jenkins/s/https/ci-builds.apache.org/job/Maven/job/maven-box/job/maven-pmd-plugin/job/master.svg?)][build]
+[![Jenkins tests](https://img.shields.io/jenkins/t/https/ci-builds.apache.org/job/Maven/job/maven-box/job/maven-pmd-plugin/job/master.svg?)][test-results]
You have found a bug or you have an idea for a cool new feature? Contributing
@@ -95,5 +95,5 @@
[code-style]: https://maven.apache.org/developers/conventions/code.html
[cla]: https://www.apache.org/licenses/#clas
[maven-wiki]: https://cwiki.apache.org/confluence/display/MAVEN/Index
-[test-results]: https://builds.apache.org/job/maven-box/job/maven-pmd-plugin/job/master/lastCompletedBuild/testReport/
-[build]: https://builds.apache.org/job/maven-box/job/maven-pmd-plugin/job/master/
+[test-results]: https://ci-builds.apache.org/job/Maven/job/maven-box/job/maven-pmd-plugin/job/master/lastCompletedBuild/testReport/
+[build]: https://ci-builds.apache.org/job/Maven/job/maven-box/job/maven-pmd-plugin/job/master/
diff --git a/pom.xml b/pom.xml
index 6d29fad..ce41830 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
</parent>
<artifactId>maven-pmd-plugin</artifactId>
- <version>3.14.0-SNAPSHOT</version>
+ <version>3.15.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Apache Maven PMD Plugin</name>
@@ -75,7 +75,7 @@
</issueManagement>
<ciManagement>
<system>Jenkins</system>
- <url>https://builds.apache.org/job/maven-box/job/maven-pmd-plugin/</url>
+ <url>https://ci-builds.apache.org/job/Maven/job/maven-box/job/maven-pmd-plugin/</url>
</ciManagement>
<distributionManagement>
<site>
@@ -89,10 +89,10 @@
<doxiaVersion>1.9.1</doxiaVersion>
<doxiaSitetoolsVersion>1.9.2</doxiaSitetoolsVersion>
<javaVersion>7</javaVersion><!-- Because PMD 5.4+ requires Java 7 -->
- <pmdVersion>6.26.0</pmdVersion>
+ <pmdVersion>6.29.0</pmdVersion>
<sitePluginVersion>3.7.1</sitePluginVersion>
<projectInfoReportsPluginVersion>3.0.0</projectInfoReportsPluginVersion>
- <project.build.outputTimestamp>2020-04-07T21:04:00Z</project.build.outputTimestamp>
+ <project.build.outputTimestamp>2020-10-24T17:10:38Z</project.build.outputTimestamp>
</properties>
<dependencies>
@@ -132,18 +132,18 @@
<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-text</artifactId>
- <!-- overwrite version from pmd-core. 1.3 is still compatible with java7, 1.6 not. -->
- <version>1.3</version>
- </dependency>
- <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>
@@ -206,6 +206,11 @@
<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 +249,12 @@
<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 +353,7 @@
<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-165/src/main/java/test/MyClass.java b/src/it/MPMD-165/src/main/java/test/MyClass.java
index 5e8d6f1..6c4c05c 100644
--- a/src/it/MPMD-165/src/main/java/test/MyClass.java
+++ b/src/it/MPMD-165/src/main/java/test/MyClass.java
@@ -24,7 +24,7 @@
public static void main( String[] args )
{
- return;
+ return; // This is a UnnecessaryReturn violation
}
}
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-290-cpd-for-csharp/invoker.properties b/src/it/MPMD-290-cpd-for-csharp/invoker.properties
new file mode 100644
index 0000000..8e84538
--- /dev/null
+++ b/src/it/MPMD-290-cpd-for-csharp/invoker.properties
@@ -0,0 +1,21 @@
+# 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.
+
+invoker.maven.version = 3.3+
+invoker.goals = clean verify
+invoker.buildResult = failure
+invoker.debug = true
diff --git a/src/it/MPMD-290-cpd-for-csharp/pom.xml b/src/it/MPMD-290-cpd-for-csharp/pom.xml
new file mode 100644
index 0000000..38907b4
--- /dev/null
+++ b/src/it/MPMD-290-cpd-for-csharp/pom.xml
@@ -0,0 +1,68 @@
+<?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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.plugins.pmd.its</groupId>
+ <artifactId>MPMD-290-cpd-for-csharp</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <description>
+ Use CPD via m-pmd-p to analyze duplications in c# files.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>@project.version@</version>
+ <configuration>
+ <language>cs</language>
+ <minimumTokens>10</minimumTokens>
+ <includes>
+ <include>**/*.cs</include>
+ </includes>
+ <compileSourceRoots>
+ <compileSourceRoot>${basedir}/src/main/cs</compileSourceRoot>
+ </compileSourceRoots>
+ <printFailingErrors>true</printFailingErrors>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>cpd-check</goal>
+ </goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>net.sourceforge.pmd</groupId>
+ <artifactId>pmd-cs</artifactId>
+ <version>@pmdVersion@</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample1.cs b/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample1.cs
new file mode 100644
index 0000000..de5f199
--- /dev/null
+++ b/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample1.cs
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+class Sample1 {
+ public void bar() {
+ int x = 1;
+ int y = 2;
+ int z = x + y;
+ }
+}
\ No newline at end of file
diff --git a/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample2.cs b/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample2.cs
new file mode 100644
index 0000000..8730046
--- /dev/null
+++ b/src/it/MPMD-290-cpd-for-csharp/src/main/cs/Sample2.cs
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+class Sample2 {
+ public void bar() {
+ int x = 1;
+ int y = 2;
+ int z = x + y;
+ }
+}
\ No newline at end of file
diff --git a/src/it/MPMD-290-cpd-for-csharp/verify.groovy b/src/it/MPMD-290-cpd-for-csharp/verify.groovy
new file mode 100644
index 0000000..18b724a
--- /dev/null
+++ b/src/it/MPMD-290-cpd-for-csharp/verify.groovy
@@ -0,0 +1,36 @@
+
+/*
+ * 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] CPD Failure: Found 7 lines of duplicated code at locations" )
+assert buildLog.text.contains( "[DEBUG] PMD failureCount: 1, warningCount: 0" )
+
+File cpdXml = new File( basedir, 'target/cpd.xml' )
+assert cpdXml.exists()
+
+// no duplication for the license header - if this is reported, then CPD uses the wrong language/tokenizer
+assert !cpdXml.text.contains( '<duplication lines="20" tokens="148">' )
+assert !cpdXml.text.contains( 'line="1"' )
+
+// the only valid duplication
+assert cpdXml.text.contains( '<duplication lines="7" tokens="26">' )
+assert cpdXml.text.contains( 'line="20"' )
diff --git a/src/it/MPMD-302-JDK15/invoker.properties b/src/it/MPMD-302-JDK15/invoker.properties
new file mode 100644
index 0000000..c0309db
--- /dev/null
+++ b/src/it/MPMD-302-JDK15/invoker.properties
@@ -0,0 +1,19 @@
+# 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.
+
+invoker.java.version = 15+
+invoker.goals = clean verify
diff --git a/src/it/MPMD-302-JDK15/pom.xml b/src/it/MPMD-302-JDK15/pom.xml
new file mode 100644
index 0000000..37ea830
--- /dev/null
+++ b/src/it/MPMD-302-JDK15/pom.xml
@@ -0,0 +1,82 @@
+<?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-302-JDK15</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>15</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>
+ <skipPmdError>false</skipPmdError>
+ <skip>false</skip>
+ <failOnViolation>true</failOnViolation>
+ <failurePriority>4</failurePriority>
+ <targetJdk>${java.version}</targetJdk>
+ <sourceEncoding>UTF-8</sourceEncoding>
+ <minimumTokens>100</minimumTokens>
+ <excludes>
+ <exclude>**/*Bean.java</exclude>
+ <exclude>**/generated/*.java</exclude>
+ </excludes>
+ <excludeRoots>
+ <excludeRoot>target/generated-sources/stubs</excludeRoot>
+ </excludeRoots>
+ <rulesets/>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/App.java b/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/App.java
new file mode 100644
index 0000000..8e4972d
--- /dev/null
+++ b/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/App.java
@@ -0,0 +1,25 @@
+package com.mycompany.app;
+
+/*
+ * 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 App
+{
+
+}
diff --git a/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/Foo.java b/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/Foo.java
new file mode 100644
index 0000000..2a5a2bc
--- /dev/null
+++ b/src/it/MPMD-302-JDK15/src/main/java/com/mycompany/app/Foo.java
@@ -0,0 +1,28 @@
+/*
+ * 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.ArrayList;
+
+public class Foo
+{
+ public Foo( final ArrayList<String> foo )
+ {
+ }
+
+}
diff --git a/src/it/MPMD-302-JDK15/verify.groovy b/src/it/MPMD-302-JDK15/verify.groovy
new file mode 100644
index 0000000..1cd85d4
--- /dev/null
+++ b/src/it/MPMD-302-JDK15/verify.groovy
@@ -0,0 +1,22 @@
+
+/*
+ * 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()
\ No newline at end of file
diff --git a/src/it/MPMD-304-toolchain-support/invoker.properties b/src/it/MPMD-304-toolchain-support/invoker.properties
new file mode 100644
index 0000000..198db8b
--- /dev/null
+++ b/src/it/MPMD-304-toolchain-support/invoker.properties
@@ -0,0 +1,32 @@
+# 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.
+
+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 48567a9..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 @@
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.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 @@
public abstract class AbstractPmdReport
extends AbstractMavenReport
{
+ // ----------------------------------------------------------------------
+ // Configurables
+ // ----------------------------------------------------------------------
+
/**
* The output directory for the intermediate XML report.
*/
@@ -72,21 +77,10 @@
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 required if the pmd:check goal is being used.
+ * renderers. XML is produced in any case, since this format is needed
+ * for the check goals (pmd:check, pmd:cpd-check).
*/
@Parameter( property = "format", defaultValue = "xml" )
protected String format = "xml";
@@ -187,12 +181,6 @@
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
@@ -235,11 +223,48 @@
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
*/
- private Logger julLogger;
+ @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.
+ */
+ @Component
+ private Renderer siteRenderer;
+
+ @Component
+ private ToolchainManager toolchainManager;
/** The files that are being analyzed. */
protected Map<File, PmdFileInfo> filesToProcess;
@@ -284,7 +309,10 @@
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();
@@ -482,11 +510,6 @@
return StringUtils.join( patterns.iterator(), "," );
}
- protected boolean isHtml()
- {
- return "html".equals( format );
- }
-
protected boolean isXml()
{
return "xml".equals( format );
@@ -555,54 +578,63 @@
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 ( logLevel == null )
{
- if ( handler instanceof SLF4JBridgeHandler )
- {
- slf4jBridgeAlreadyAdded = true;
- break;
- }
+ // TODO: logback level
+ logLevel = "info";
}
-
- 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() );
+ return logLevel;
}
static String getPmdVersion()
{
- try
+ 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 )
{
- return (String) PMD.class.getField( "VERSION" ).get( null );
+ // 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 ( IllegalAccessException e )
+
+ if ( tc == null )
{
- throw new RuntimeException( "PMD VERSION field not accessible", e );
+ tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
}
- catch ( NoSuchFieldException e )
- {
- throw new RuntimeException( "PMD VERSION field not found", e );
- }
+
+ 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 d9fb606..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,38 +20,24 @@
*/
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.XMLRenderer;
import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
/**
@@ -116,11 +102,12 @@
@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}
@@ -191,10 +178,10 @@
{
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." );
@@ -209,92 +196,60 @@
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();
+
+ 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 )
{
- filesToProcess = getFilesToProcess();
+ getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
+ String javaExecutable = tc.findTool( "java" ); //NOI18N
+ request.setJavaExecutable( javaExecutable );
}
- 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 ) )
- {
- cpdLanguage = new EcmascriptLanguage();
- }
- 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 )
{
@@ -304,50 +259,12 @@
{
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 as already bean 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 )
@@ -371,53 +288,6 @@
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}
*/
@@ -436,32 +306,11 @@
*
* @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 ( !"".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 @@
*/
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 @@
/**
* 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 @@
/**
* 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 @@
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 @@
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 @@
@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 9f008c3..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 @@
*/
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.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.
@@ -97,7 +72,8 @@
* The target JDK to analyze based on. Should match the source used in the compiler plugin. Valid values
* with the default PMD version are
* currently <code>1.3</code>, <code>1.4</code>, <code>1.5</code>, <code>1.6</code>, <code>1.7</code>,
- * <code>1.8</code>, <code>9</code>, <code>10</code>, <code>11</code>, <code>12</code>, and <code>13</code>.
+ * <code>1.8</code>, <code>9</code>, <code>10</code>, <code>11</code>, <code>12</code>, <code>13</code>,
+ * <code>14</code>, and <code>15</code>.
*
* <p> You can override the default PMD version by specifying PMD as a dependency,
* see <a href="examples/upgrading-PMD-at-runtime.html">Upgrading PMD at Runtime</a>.</p>
@@ -181,17 +157,6 @@
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
@@ -260,11 +225,22 @@
@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}
@@ -348,10 +324,10 @@
{
try
{
- executePmdWithClassloader();
+ executePmd();
if ( skipEmptyReport )
{
- result = renderer.hasViolations();
+ result = pmdResult.hasViolations();
if ( result )
{
getLog().debug( "Skipping report since skipEmptyReport is true and "
@@ -367,27 +343,10 @@
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." );
@@ -396,21 +355,82 @@
try
{
- excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
+ filesToProcess = getFilesToProcess();
+
+ if ( filesToProcess.isEmpty() && !"java".equals( language ) )
+ {
+ getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
+ + " (see also build-helper-maven-plugin)" );
+ }
}
- catch ( MojoExecutionException e )
+ catch ( IOException e )
{
- throw new MavenReportException( "Unable to load exclusions", e );
+ throw new MavenReportException( "Can't get file list", e );
}
+
+ 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 );
+
+ 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() );
+
+ Toolchain tc = getToolchain();
+ if ( tc != null )
+ {
+ getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
+ String javaExecutable = tc.findTool( "java" ); //NOI18N
+ request.setJavaExecutable( javaExecutable );
+ }
+
+ pmdResult = PmdExecutor.execute( request );
+ }
+
+ protected String getSourceEncoding()
+ {
+ String encoding = super.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!" );
+ }
+ }
+ return encoding;
+ }
+
+
+ /**
+ * 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
+ {
// 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
{
@@ -431,159 +451,7 @@
{
throw new MavenReportException( e.getMessage(), e );
}
- pmdConfiguration.setRuleSets( StringUtils.join( sets, "," ) );
-
- try
- {
- if ( filesToProcess == null )
- {
- filesToProcess = getFilesToProcess();
- }
-
- if ( filesToProcess.isEmpty() && !"java".equals( language ) )
- {
- getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
- + " (see also build-helper-maven-plugin)" );
- }
- }
- catch ( IOException e )
- {
- 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 ) );
- }
-
- if ( sets.length > 0 )
- {
- processFilesWithPMD( pmdConfiguration, dataSources );
- }
- else
- {
- getLog().debug( "Skipping PMD execution as no rulesets are defined." );
- }
-
- if ( renderer.hasErrors() )
- {
- 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() ) );
- }
-
- 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 as been just produced.
- // html format is produced by the maven site formatter. Excluding html here
- // avoids usind 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 );
- }
- }
- }
-
- private void removeExcludedViolations( List<RuleViolation> violations )
- {
- getLog().debug( "Removing excluded violations. Using " + excludeFromFile.countExclusions()
- + " configured exclusions." );
- 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();
- getLog().debug( "Excluded " + numberOfExcludedViolations + " violations." );
- }
-
- private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources )
- 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 );
- }
-
- 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 )
- {
- throw new MavenReportException( message, e );
- }
- getLog().warn( message, e );
- }
- finally
- {
- ClassLoader classLoader = pmdConfiguration.getClassLoader();
- if ( classLoader instanceof ClasspathClassLoader )
- {
- IOUtil.tryCloseClassLoader( classLoader );
- }
- }
+ return StringUtils.join( sets, "," );
}
private void generateMavenSiteReport( Locale locale )
@@ -594,10 +462,10 @@
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
@@ -645,130 +513,7 @@
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
{
@@ -836,7 +581,7 @@
getLog().debug( "Using aux classpath: " + classpath );
}
String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
- configuration.prependClasspath( path );
+ return path;
}
catch ( Exception e )
{
@@ -863,40 +608,11 @@
*
* @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..a094363 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 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 @@
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 @@
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,45 @@
sink.section_( level );
}
- private void processSingleRuleViolation( RuleViolation ruleViolation, PmdFileInfo fileInfo )
+ private void addRuleName( Violation ruleViolation )
+ {
+ boolean hasUrl = StringUtils.isNotBlank( ruleViolation.getExternalInfoUrl() );
+
+ if ( hasUrl )
+ {
+ sink.link( ruleViolation.getExternalInfoUrl() );
+ }
+
+ sink.text( ruleViolation.getRule() );
+
+ if ( hasUrl )
+ {
+ sink.link_();
+ }
+ }
+
+ private void processSingleRuleViolation( Violation ruleViolation, PmdFileInfo fileInfo )
{
sink.tableRow();
sink.tableCell();
- sink.link( ruleViolation.getRule().getExternalInfoUrl() );
- sink.text( ruleViolation.getRule().getName() );
- sink.link_();
+ addRuleName( ruleViolation );
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( "–" ); // \u2013 is a medium long dash character
@@ -230,7 +245,7 @@
// TODO files summary
- List<RuleViolation> violations2 = new ArrayList<>( violations );
+ List<Violation> violations2 = new ArrayList<>( violations );
renderViolationsTable( 2, violations2 );
sink.section1_();
@@ -251,22 +266,22 @@
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 +309,18 @@
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 +331,9 @@
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 +386,7 @@
@Override
public int compare( ProcessingError e1, ProcessingError e2 )
{
- return e1.getFile().compareTo( e2.getFile() );
+ return e1.getFilename().compareTo( e2.getFilename() );
}
} );
@@ -402,7 +417,7 @@
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..93645d7
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdResult.java
@@ -0,0 +1,154 @@
+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.FilterReader;
+import java.io.IOException;
+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 BomFilter( encoding, 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 );
+ }
+ }
+
+ // Note: This seems to be a bug in PMD's XMLRenderer. The BOM is rendered multiple times.
+ // once at the beginning of the file, which is Ok, but also in the middle of the file.
+ // This filter just skips all BOMs if the encoding is not UTF-8
+ private static class BomFilter extends FilterReader
+ {
+ private static final char BOM = '\uFEFF';
+ private final boolean filter;
+
+ BomFilter( String encoding, Reader in )
+ {
+ super( in );
+ filter = !"UTF-8".equalsIgnoreCase( encoding );
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ int c = super.read();
+
+ if ( !filter )
+ {
+ return c;
+ }
+
+ while ( c != -1 && c == BOM )
+ {
+ c = super.read();
+ }
+ return c;
+ }
+
+ @Override
+ public int read( char[] cbuf, int off, int len ) throws IOException
+ {
+ int count = super.read( cbuf, off, len );
+
+ if ( !filter )
+ {
+ return count;
+ }
+
+ if ( count != -1 )
+ {
+ for ( int i = off; i < off + count; i++ )
+ {
+ if ( cbuf[i] == BOM )
+ {
+ // shift the content one char to the left
+ System.arraycopy( cbuf, i + 1, cbuf, i, off + count - 1 - i );
+ count--;
+ }
+ }
+ }
+ return count;
+ }
+ }
+
+ 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 @@
<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 @@
<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 @@
<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 @@
</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/cpdCsharp.apt.vm b/src/site/apt/examples/cpdCsharp.apt.vm
new file mode 100644
index 0000000..4fbf230
--- /dev/null
+++ b/src/site/apt/examples/cpdCsharp.apt.vm
@@ -0,0 +1,91 @@
+ ------
+ Finding duplicated code in C#
+ ------
+ Andreas Dangel
+ ------
+ 2020-10-02
+ ------
+
+ ~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/doxia/references/apt-format.html
+
+Finding duplicated code in C#
+
+ By default, the maven-pmd-plugin only supports the languages Java, JavaScript and JSP.
+ But {{{https://pmd.github.io/latest/pmd_userdocs_cpd.html#supported-languages}CPD supports many more languages}},
+ e.g. C#. In order to enable C# in your build, you need to
+ configure several parts:
+
+ * Add an additional plugin dependency for c# (pmd-cs module)
+
+ * Select the language <<<cs>>>.
+
+ * Configure the includes filter to consider <<<*.cs>>> (otherwise only java files will be analyzed)
+
+ * Configure the source directory (by default, only <<<src/main/java>>> is analyzed)
+
+
++-----+
+<project>
+ ...
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <language>cs</language>
+ <minimumTokens>10</minimumTokens>
+ <includes>
+ <include>**/*.cs</include>
+ </includes>
+ <compileSourceRoots>
+ <compileSourceRoot>${basedir}/src/main/cs</compileSourceRoot>
+ </compileSourceRoots>
+ <printFailingErrors>true</printFailingErrors>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>cpd-check</goal>
+ </goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>net.sourceforge.pmd</groupId>
+ <artifactId>pmd-cs</artifactId>
+ <version>${pmdVersion}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ ...
+ </plugins>
+ </build>
+</project>
++-----+
+
+ In this example, the C# source files are located in <<<src/main/cs>>>.
+
+ <<Note:>> The version for <<<net.sourceforge.pmd:pmd-cs>>> needs to match the PMD version, that is
+ being used. If you {{{./upgrading-PMD-at-runtime.html}upgrade PMD at runtime}} you'll need to make
+ sure, to change the version here as well.
+
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 @@
</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/examples/upgrading-PMD-at-runtime.apt.vm b/src/site/apt/examples/upgrading-PMD-at-runtime.apt.vm
index 74266c1..7c343b2 100644
--- a/src/site/apt/examples/upgrading-PMD-at-runtime.apt.vm
+++ b/src/site/apt/examples/upgrading-PMD-at-runtime.apt.vm
@@ -88,7 +88,7 @@
*--------------------------------------------------------------------------------*--------------------------------------------------*
| <<maven-pmd-plugin>> | <<PMD>> |
*--------------------------------------------------------------------------------*--------------------------------------------------*
-| {{{https://maven.apache.org/plugins-archives/maven-pmd-plugin-3.14.0/}3.14.0}} | {{{https://pmd.github.io/pmd-6.26.0/}6.26.0}} |
+| {{{https://maven.apache.org/plugins-archives/maven-pmd-plugin-3.14.0/}3.14.0}} | {{{https://pmd.github.io/pmd-6.29.0/}6.29.0}} |
*--------------------------------------------------------------------------------*--------------------------------------------------*
| {{{https://maven.apache.org/plugins-archives/maven-pmd-plugin-3.13.0/}3.13.0}} | {{{https://pmd.github.io/pmd-6.21.0/}6.21.0}} |
*--------------------------------------------------------------------------------*--------------------------------------------------*
diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm
index 38161a7..348c892 100644
--- a/src/site/apt/index.apt.vm
+++ b/src/site/apt/index.apt.vm
@@ -88,7 +88,7 @@
* {{{./examples/removeReport.html}Remove Report}}
- * {{{./examples/targetJdk.html}Target JDK}}
+ * {{{./examples/targetJdk.html}Target JDK and Toolchains}}
* {{{./examples/usingRuleSets.html}Using Rule Sets}}
@@ -98,4 +98,6 @@
* {{{./examples/jspReport.html}Analyzing Java Server Pages Code}}
+ * {{{./examples/cpdCsharp.html}Finding duplicated code in C#}}
+
[]
diff --git a/src/site/site.xml b/src/site/site.xml
index ca64ccb..c7c6561 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -36,12 +36,13 @@
<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"/>
<item name="Analyzing Java Server Pages" href="examples/jspReport.html"/>
<item name="Violations Exclusions" href="examples/violation-exclusions.html"/>
+ <item name="Duplicated code in C#" href="examples/cpdCsharp.html"/>
</menu>
</body>
</project>
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.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 @@
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 @@
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 e64d532..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.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,18 +76,40 @@
// 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);" ) );
}
/**
+ * Test CPDReport with the text renderer given as "format=txt"
+ *
+ * @throws Exception
+ */
+ public void testTxtFormat()
+ throws Exception
+ {
+ File testPom =
+ new File( getBasedir(),
+ "src/test/resources/unit/custom-configuration/cpd-txt-format-configuration-plugin-config.xml" );
+ CpdReport mojo = (CpdReport) lookupMojo( "cpd", testPom );
+ mojo.execute();
+
+ // check if the CPD files were generated
+ File generatedFile = new File( getBasedir(), "target/test/unit/custom-configuration/target/cpd.xml" );
+ assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+ generatedFile = new File( getBasedir(), "target/test/unit/custom-configuration/target/cpd.txt" );
+ assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+
+ // check the contents of cpd.txt
+ String str = readFile( generatedFile );
+ // Contents that should NOT be in the report
+ assertFalse( lowerCaseContains( str, "public static void main( String[] args )" ) );
+ // Contents that should be in the report
+ assertTrue( lowerCaseContains( str, "public void duplicateMethod( int i )" ) );
+ }
+
+ /**
* Test CPDReport using custom configuration
*
* @throws Exception
@@ -188,35 +198,6 @@
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
{
@@ -225,10 +206,7 @@
"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" );
@@ -237,9 +215,10 @@
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);" ) );
}
/**
@@ -254,10 +233,7 @@
"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() ) );
@@ -273,6 +249,7 @@
assertTrue( FileUtils.fileExists( siteReport.getAbsolutePath() ) );
String siteReportContent = readFile( siteReport );
assertTrue( siteReportContent.contains( "</pmd-cpd>" ) );
+ assertEquals( str, siteReportContent );
}
@@ -378,25 +355,4 @@
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..8a4fc0b 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 @@
*/
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 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 @@
public class PmdReportTest
extends AbstractPmdReportTest
{
+
/**
* {@inheritDoc}
*/
@@ -578,33 +578,23 @@
"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 ) );
+ mojo.execute();
+ String output = CapturingPrintStream.getOutput();
+ assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
- 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." ) );
- 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." ) );
-
- 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." ) );
-
- } 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 @@
"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 ) );
+ mojo.execute();
+ String output = CapturingPrintStream.getOutput();
+ assertTrue ( output.contains( "There are 1 PMD processing errors:" ) );
- 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." ) );
- 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." ) );
-
- 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." ) );
-
- } 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,39 @@
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);
}
+
+ public void testPmdReportCustomRulesNoExternalInfoUrl()
+ throws Exception
+ {
+ FileUtils.copyDirectoryStructure( new File( getBasedir(),
+ "src/test/resources/unit/default-configuration/jxr-files" ),
+ new File( getBasedir(), "target/test/unit/default-configuration/target/site" ) );
+
+ File testPom =
+ new File( getBasedir(),
+ "src/test/resources/unit/default-configuration/pmd-report-custom-rules.xml" );
+ PmdReport mojo = (PmdReport) lookupMojo( "pmd", testPom );
+ mojo.execute();
+
+ File generatedFile = new File( getBasedir(), "target/test/unit/default-configuration/target/site/pmd.html" );
+ renderer( mojo, generatedFile );
+ assertTrue( FileUtils.fileExists( generatedFile.getAbsolutePath() ) );
+
+ String str = readFile( generatedFile );
+
+ // custom rule without link
+ assertEquals( 2, StringUtils.countMatches( str, "<td>CustomRule</td>" ) );
+ // standard rule with link
+ assertEquals( 4, StringUtils.countMatches( str, "\">UnusedPrivateField</a></td>" ) );
+ }
+
}
diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..64b331b
--- /dev/null
+++ b/src/test/resources/simplelogger.properties
@@ -0,0 +1,32 @@
+# 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.
+
+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
diff --git a/src/test/resources/unit/custom-configuration/cpd-txt-format-configuration-plugin-config.xml b/src/test/resources/unit/custom-configuration/cpd-txt-format-configuration-plugin-config.xml
new file mode 100644
index 0000000..5ea5415
--- /dev/null
+++ b/src/test/resources/unit/custom-configuration/cpd-txt-format-configuration-plugin-config.xml
@@ -0,0 +1,51 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>custom.configuration</groupId>
+ <artifactId>custom-configuration</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <inceptionYear>2006</inceptionYear>
+ <name>Maven CPD Plugin Txt Format Configuration Test</name>
+ <url>http://maven.apache.org</url>
+ <build>
+ <finalName>txt-format-configuration</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <configuration>
+ <project implementation="org.apache.maven.plugins.pmd.stubs.DefaultConfigurationMavenProjectStub"/>
+ <outputDirectory>${basedir}/target/test/unit/custom-configuration/target/site</outputDirectory>
+ <targetDirectory>${basedir}/target/test/unit/custom-configuration/target</targetDirectory>
+ <format>txt</format>
+ <linkXRef>false</linkXRef>
+ <minimumTokens>30</minimumTokens>
+
+ <compileSourceRoots>
+ <compileSourceRoot>${basedir}/src/test/resources/unit/custom-configuration/</compileSourceRoot>
+ </compileSourceRoots>
+ <sourceEncoding>UTF-8</sourceEncoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/test/resources/unit/default-configuration/pmd-report-custom-rules.xml b/src/test/resources/unit/default-configuration/pmd-report-custom-rules.xml
new file mode 100644
index 0000000..04c2c55
--- /dev/null
+++ b/src/test/resources/unit/default-configuration/pmd-report-custom-rules.xml
@@ -0,0 +1,62 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>def.configuration</groupId>
+ <artifactId>default-configuration</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <inceptionYear>2006</inceptionYear>
+ <name>Maven PMD Plugin Default Configuration Test</name>
+ <url>http://maven.apache.org</url>
+ <build>
+ <finalName>default-configuration</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <configuration>
+ <project implementation="org.apache.maven.plugins.pmd.stubs.DefaultConfigurationMavenProjectStub"/>
+ <outputDirectory>${basedir}/target/test/unit/default-configuration/target/site</outputDirectory>
+ <targetDirectory>${basedir}/target/test/unit/default-configuration/target</targetDirectory>
+ <rulesetsTargetDirectory>${basedir}/target/test/unit/default-configuration/target/pmd/rulesets</rulesetsTargetDirectory>
+ <rulesets>
+ <ruleset>${basedir}/src/test/resources/unit/default-configuration/rulesets/custom-rules.xml</ruleset>
+ </rulesets>
+ <format>xml</format>
+ <linkXRef>true</linkXRef>
+ <xrefLocation>${basedir}/target/test/unit/default-configuration/target/site/xref</xrefLocation>
+ <sourceEncoding>UTF-8</sourceEncoding>
+ <compileSourceRoots>
+ <compileSourceRoot>${basedir}/src/test/resources/unit/default-configuration/</compileSourceRoot>
+ </compileSourceRoots>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
diff --git a/src/test/resources/unit/default-configuration/rulesets/custom-rules.xml b/src/test/resources/unit/default-configuration/rulesets/custom-rules.xml
new file mode 100644
index 0000000..4946c53
--- /dev/null
+++ b/src/test/resources/unit/default-configuration/rulesets/custom-rules.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<ruleset name="Custom PMD Rule Ruleset"
+ xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
+
+ <description>
+ A ruleset with a custom rule without "externalInfoUrl".
+ </description>
+
+ <rule name="CustomRule"
+ language="java"
+ message="custom rule test"
+ class="net.sourceforge.pmd.lang.rule.XPathRule">
+ <description>custom xpath rule test</description>
+ <priority>1</priority>
+ <properties>
+ <property name="xpath">
+ <value>
+<![CDATA[
+//ClassOrInterfaceDeclaration[@SimpleName = 'App']
+]]>
+ </value>
+ </property>
+ </properties>
+ </rule>
+
+ <rule ref="category/java/bestpractices.xml/UnusedPrivateField" />
+</ruleset>