diff --git a/pmd-compat6/README.md b/pmd-compat6/README.md new file mode 100644 index 0000000000..d07fc26518 --- /dev/null +++ b/pmd-compat6/README.md @@ -0,0 +1,35 @@ +# pmd-compat6 + +This module contains classes from PMD6, that have been removed in PMD7 and also restores +some removed methods. + +The goal is, that PMD7 can be used with [Maven PMD Plugin](https://maven.apache.org/plugins/maven-pmd-plugin) +without any further changes to the plugin. + +The plugin uses by default PMD Version 6.55.0, but it can be configured to +[Use a new PMD version at runtime](https://maven.apache.org/plugins/maven-pmd-plugin/examples/upgrading-PMD-at-runtime.html). + +Since PMD7 introduces many incompatible changes, another module is needed to restore +compatibility. This is this module. + +In order to use this compatibility module, it needs to be added as the _first_ dependency +when configuring maven-pmd-plugin. + +It is as simple as adding: + +```xml + + net.sourceforge.pmd + pmd-compat6 + ${pmdVersion} + +``` + +Note: The dependency "pmd-compat6" must be listed _first_ before pmd-core, pmd-java, and the others. + +Once the default version of PMD is upgraded to PMD7 in maven-pmd-plugin +(see [MPMD-379](https://issues.apache.org/jira/projects/MPMD/issues/MPMD-379)), this +compatibility module is no longer needed. + +The primary goal for this module is, to get maven-pmd-plugin working with PMD7. It might +be useful in other contexts, too, but no guarantee is given, that is works. diff --git a/pmd-compat6/pom.xml b/pmd-compat6/pom.xml new file mode 100644 index 0000000000..2410c7982a --- /dev/null +++ b/pmd-compat6/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + net.sourceforge.pmd + pmd + 7.0.0-rc4 + + + pmd-compat6 + 7.0.0-SNAPSHOT + PMD Compatibility Classes for PMD6 + + + 7.0.0-rc4 + 7.0.0-rc4 + 3.21.2 + + + + + net.sourceforge.pmd + pmd-core + ${pmd.version} + + + net.sourceforge.pmd + pmd-java + ${pmd.version} + + + net.sourceforge.pmd + pmd-javascript + ${pmd.version} + + + net.sourceforge.pmd + pmd-jsp + ${pmd.version} + + + + + + + org.apache.maven.plugins + maven-invoker-plugin + 3.6.0 + + ${project.build.directory}/it + src/it/settings.xml + ${project.build.directory}/local-repo + verify.bsh + + + + integration-test + + install + run + + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-java/invoker.properties b/pmd-compat6/src/it/cpd-for-java/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-java/pom.xml b/pmd-compat6/src/it/cpd-for-java/pom.xml new file mode 100644 index 0000000000..ea93703565 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-java + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + java-cpd-check + + cpd-check + + + + + true + false + 5 + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java new file mode 100644 index 0000000000..3df3dde7d9 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java @@ -0,0 +1,10 @@ +package org.example; + +public class ClassA { + public int method1(int a, int b, int c) { + int d = (a + b + c + 1) * 10; + int e = (a + b + c - 1) * 5; + int f = (a + b + c); + return d * e * f + d + e + f; + } +} diff --git a/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java new file mode 100644 index 0000000000..994e917441 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java @@ -0,0 +1,10 @@ +package org.example; + +public class ClassB { + public int method1(int a, int b, int c) { + int d = (a + b + c + 1) * 10; + int e = (a + b + c - 1) * 5; + int f = (a + b + c); + return d * e * f + d + e + f; + } +} diff --git a/pmd-compat6/src/it/cpd-for-java/verify.bsh b/pmd-compat6/src/it/cpd-for-java/verify.bsh new file mode 100644 index 0000000000..c5fff32f9a --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 8 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-java/src/main/java/org/example/ClassA.java line 3")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-java/src/main/java/org/example/ClassA.java\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/invoker.properties b/pmd-compat6/src/it/cpd-for-javascript/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-javascript/pom.xml b/pmd-compat6/src/it/cpd-for-javascript/pom.xml new file mode 100644 index 0000000000..a252b32a34 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-javascript + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + javascript-cpd-check + + cpd-check + + + + + true + false + 5 + javascript + + /category/ecmascript/bestpractices.xml + + + **/*.js + + + ${basedir}/src/main/js + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js new file mode 100644 index 0000000000..d9c86cf398 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js new file mode 100644 index 0000000000..d9c86cf398 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/verify.bsh b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh new file mode 100644 index 0000000000..c400c18faa --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 7 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-javascript/src/main/js/globalVariable.js line 1")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-javascript/src/main/js/globalVariable.js\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/cpd-for-jsp/invoker.properties b/pmd-compat6/src/it/cpd-for-jsp/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-jsp/pom.xml b/pmd-compat6/src/it/cpd-for-jsp/pom.xml new file mode 100644 index 0000000000..1726d641d5 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-jsp + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + jsp-cpd-check + + cpd-check + + + + + true + false + 5 + jsp + + /category/jsp/bestpractices.xml + + + **/*.jsp + + + ${basedir}/src/main/jsp + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp new file mode 100644 index 0000000000..c86ca5aa9c --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp new file mode 100644 index 0000000000..c86ca5aa9c --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/cpd-for-jsp/verify.bsh b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh new file mode 100644 index 0000000000..36b11503dd --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 3 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp line 1")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-java/invoker.properties b/pmd-compat6/src/it/pmd-for-java/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-java/pom.xml b/pmd-compat6/src/it/pmd-for-java/pom.xml new file mode 100644 index 0000000000..7d77929cf9 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-java + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + java-check + + check + + + + + true + false + 5 + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java b/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java new file mode 100644 index 0000000000..a1609790b5 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java @@ -0,0 +1,8 @@ +package org.example; + +public class Main { + public static void main(String[] args) { + String thisIsAUnusedLocalVar = "a"; + System.out.println("Hello world!"); + } +} diff --git a/pmd-compat6/src/it/pmd-for-java/verify.bsh b/pmd-compat6/src/it/pmd-for-java/verify.bsh new file mode 100644 index 0000000000..47eed9d5ea --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: org.example.Main:5 Rule:UnusedLocalVariable")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-javascript/invoker.properties b/pmd-compat6/src/it/pmd-for-javascript/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-javascript/pom.xml b/pmd-compat6/src/it/pmd-for-javascript/pom.xml new file mode 100644 index 0000000000..e1fd383e42 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-javascript + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + javascript-check + + check + + + + + true + false + 5 + javascript + + /category/ecmascript/bestpractices.xml + + + **/*.js + + + ${basedir}/src/main/js + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js b/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js new file mode 100644 index 0000000000..d9c86cf398 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/pmd-for-javascript/verify.bsh b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh new file mode 100644 index 0000000000..54ab5cedb9 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: globalVariable.js:2 Rule:GlobalVariable")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-jsp/invoker.properties b/pmd-compat6/src/it/pmd-for-jsp/invoker.properties new file mode 100644 index 0000000000..0d92d959f3 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-jsp/pom.xml b/pmd-compat6/src/it/pmd-for-jsp/pom.xml new file mode 100644 index 0000000000..3784e88e69 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-jsp + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + jsp-check + + check + + + + + true + false + 5 + jsp + + /category/jsp/bestpractices.xml + + + **/*.jsp + + + ${basedir}/src/main/jsp + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp b/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp new file mode 100644 index 0000000000..c86ca5aa9c --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/pmd-for-jsp/verify.bsh b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh new file mode 100644 index 0000000000..e564c7a950 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: classAttribute.jsp:2 Rule:NoClassAttribute")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/settings.xml b/pmd-compat6/src/it/settings.xml new file mode 100644 index 0000000000..b7724658de --- /dev/null +++ b/pmd-compat6/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + it-repo + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + + it-repo + + diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java new file mode 100644 index 0000000000..87175d7405 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -0,0 +1,588 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: setSourceEncoding + +package net.sourceforge.pmd; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.annotation.DeprecatedUntil700; +import net.sourceforge.pmd.cache.AnalysisCache; +import net.sourceforge.pmd.cache.FileAnalysisCache; +import net.sourceforge.pmd.cache.NoopAnalysisCache; +import net.sourceforge.pmd.internal.util.ClasspathClassLoader; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.renderers.Renderer; +import net.sourceforge.pmd.renderers.RendererFactory; +import net.sourceforge.pmd.util.AssertionUtil; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * This class contains the details for the runtime configuration of a + * PMD run. Once configured, use {@link PmdAnalysis#create(PMDConfiguration)} + * in a try-with-resources to execute the analysis (see {@link PmdAnalysis}). + * + *

Rulesets

+ * + *
    + *
  • You can configure paths to the rulesets to use with {@link #addRuleSet(String)}. + * These can be file paths or classpath resources.
  • + *
  • Use {@link #setMinimumPriority(RulePriority)} to control the minimum priority a + * rule must have to be included. Defaults to the lowest priority, ie all rules are loaded.
  • + *
  • Use {@link #setRuleSetFactoryCompatibilityEnabled(boolean)} to disable the + * compatibility measures for removed and renamed rules in the rulesets that will + * be loaded. + *
+ * + *

Source files

+ * + *
    + *
  • The default encoding of source files is the system default as + * returned by System.getProperty("file.encoding"). + * You can set it with {@link #setSourceEncoding(Charset)}.
  • + *
  • The source files to analyze can be given in many ways. See + * {@link #addInputPath(Path)} {@link #setInputFilePath(Path)}, {@link #setInputUri(URI)}. + *
  • Files are assigned a language based on their name. The language + * version of languages can be given with + * {@link #setDefaultLanguageVersion(LanguageVersion)}. + * The default language assignment can be overridden with + * {@link #setForceLanguageVersion(LanguageVersion)}.
  • + *
+ * + *

Rendering

+ * + *
    + *
  • The renderer format to use for Reports. {@link #getReportFormat()}
  • + *
  • The file to which the Report should render. {@link #getReportFile()}
  • + *
  • Configure the root paths that are used to relativize file names in reports via {@link #addRelativizeRoot(Path)}. + * This enables to get short names in reports.
  • + *
  • The initialization properties to use when creating a Renderer instance. + * {@link #getReportProperties()}
  • + *
  • An indicator of whether to show suppressed Rule violations in Reports. + * {@link #isShowSuppressedViolations()}
  • + *
+ * + *

Language configuration

+ *
    + *
  • Use {@link #setSuppressMarker(String)} to change the comment marker for suppression comments. Defaults to {@value #DEFAULT_SUPPRESS_MARKER}.
  • + *
  • See {@link #setClassLoader(ClassLoader)} and {@link #prependAuxClasspath(String)} for + * information for how to configure classpath for Java analysis.
  • + *
  • You can set additional language properties with {@link #getLanguageProperties(Language)}
  • + *
+ * + *

Miscellaneous

+ *
    + *
  • Use {@link #setThreads(int)} to control the parallelism of the analysis. Defaults + * one thread per available processor. {@link #getThreads()}
  • + *
+ */ +public class PMDConfiguration extends AbstractConfiguration { + private static final LanguageRegistry DEFAULT_REGISTRY = LanguageRegistry.PMD; + + /** The default suppress marker string. */ + public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD"; + private Path reportFile; + + // General behavior options + private String suppressMarker = DEFAULT_SUPPRESS_MARKER; + private int threads = Runtime.getRuntime().availableProcessors(); + private ClassLoader classLoader = getClass().getClassLoader(); + + // Rule and source file options + private List ruleSets = new ArrayList<>(); + private RulePriority minimumPriority = RulePriority.LOW; + private boolean ruleSetFactoryCompatibilityEnabled = true; + + // Reporting options + private String reportFormat; + private Properties reportProperties = new Properties(); + private boolean showSuppressedViolations = false; + private boolean failOnViolation = true; + + private AnalysisCache analysisCache = new NoopAnalysisCache(); + private boolean ignoreIncrementalAnalysis; + + public PMDConfiguration() { + this(DEFAULT_REGISTRY); + } + + public PMDConfiguration(@NonNull LanguageRegistry languageRegistry) { + super(languageRegistry, new SimpleMessageReporter(LoggerFactory.getLogger(PmdAnalysis.class))); + } + + /** + * Get the suppress marker. This is the source level marker used to indicate + * a RuleViolation should be suppressed. + * + * @return The suppress marker. + */ + public String getSuppressMarker() { + return suppressMarker; + } + + /** + * Set the suppress marker. + * + * @param suppressMarker + * The suppress marker to use. + */ + public void setSuppressMarker(String suppressMarker) { + Objects.requireNonNull(suppressMarker, "Suppress marker was null"); + this.suppressMarker = suppressMarker; + } + + /** + * Get the number of threads to use when processing Rules. + * + * @return The number of threads. + */ + public int getThreads() { + return threads; + } + + /** + * Set the number of threads to use when processing Rules. + * + * @param threads + * The number of threads. + */ + public void setThreads(int threads) { + this.threads = threads; + } + + /** + * Get the ClassLoader being used by PMD when processing Rules. + * + * @return The ClassLoader being used + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Set the ClassLoader being used by PMD when processing Rules. Setting a + * value of null will cause the default ClassLoader to be used. + * + * @param classLoader + * The ClassLoader to use + */ + public void setClassLoader(ClassLoader classLoader) { + if (classLoader == null) { + this.classLoader = getClass().getClassLoader(); + } else { + this.classLoader = classLoader; + } + } + + /** + * Prepend the specified classpath like string to the current ClassLoader of + * the configuration. If no ClassLoader is currently configured, the + * ClassLoader used to load the {@link PMDConfiguration} class will be used + * as the parent ClassLoader of the created ClassLoader. + * + *

If the classpath String looks like a URL to a file (i.e. starts with + * file://) the file will be read with each line representing + * an entry on the classpath.

+ * + * @param classpath + * The prepended classpath. + * @throws IOException + * if the given classpath is invalid (e.g. does not exist) + * @see PMDConfiguration#setClassLoader(ClassLoader) + * @see ClasspathClassLoader + * + * @deprecated Use {@link #prependAuxClasspath(String)}, which doesn't + * throw a checked {@link IOException} + */ + @Deprecated + public void prependClasspath(String classpath) throws IOException { + try { + prependAuxClasspath(classpath); + } catch (IllegalArgumentException e) { + throw new IOException(e); + } + } + + /** + * Prepend the specified classpath like string to the current ClassLoader of + * the configuration. If no ClassLoader is currently configured, the + * ClassLoader used to load the {@link PMDConfiguration} class will be used + * as the parent ClassLoader of the created ClassLoader. + * + *

If the classpath String looks like a URL to a file (i.e. starts with + * file://) the file will be read with each line representing + * an entry on the classpath.

+ * + *

You can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows. + * See {@link File#pathSeparator}. + * + * @param classpath The prepended classpath. + * + * @throws IllegalArgumentException if the given classpath is invalid (e.g. does not exist) + * @see PMDConfiguration#setClassLoader(ClassLoader) + */ + public void prependAuxClasspath(String classpath) { + try { + if (classLoader == null) { + classLoader = PMDConfiguration.class.getClassLoader(); + } + if (classpath != null) { + classLoader = new ClasspathClassLoader(classpath, classLoader); + } + } catch (IOException e) { + // Note: IOExceptions shouldn't appear anymore, they should already be converted + // to IllegalArgumentException in ClasspathClassLoader. + throw new IllegalArgumentException(e); + } + } + + /** + * Get the comma separated list of RuleSet URIs. + * + * @return The RuleSet URIs. + * + * @deprecated Use {@link #getRuleSetPaths()} + */ + @Deprecated + @DeprecatedUntil700 + public @Nullable String getRuleSets() { + if (ruleSets.isEmpty()) { + return null; + } + return String.join(",", ruleSets); + } + + /** + * Returns the list of ruleset URIs. + * + * @see RuleSetLoader#loadFromResource(String) + */ + public @NonNull List<@NonNull String> getRuleSetPaths() { + return ruleSets; + } + + /** + * Sets the list of ruleset paths to load when starting the analysis. + * + * @param ruleSetPaths A list of ruleset paths, understandable by {@link RuleSetLoader#loadFromResource(String)}. + * + * @throws NullPointerException If the parameter is null + */ + public void setRuleSets(@NonNull List<@NonNull String> ruleSetPaths) { + AssertionUtil.requireParamNotNull("ruleSetPaths", ruleSetPaths); + AssertionUtil.requireContainsNoNullValue("ruleSetPaths", ruleSetPaths); + this.ruleSets = new ArrayList<>(ruleSetPaths); + } + + /** + * Add a new ruleset paths to load when starting the analysis. + * This list is initially empty. + * + * @param rulesetPath A ruleset path, understandable by {@link RuleSetLoader#loadFromResource(String)}. + * + * @throws NullPointerException If the parameter is null + */ + public void addRuleSet(@NonNull String rulesetPath) { + AssertionUtil.requireParamNotNull("rulesetPath", rulesetPath); + this.ruleSets.add(rulesetPath); + } + + /** + * Set the comma separated list of RuleSet URIs. + * + * @param ruleSets the rulesets to set + * + * @deprecated Use {@link #setRuleSets(List)} or {@link #addRuleSet(String)}. + */ + @Deprecated + @DeprecatedUntil700 + public void setRuleSets(@Nullable String ruleSets) { + if (ruleSets == null) { + this.ruleSets = new ArrayList<>(); + } else { + this.ruleSets = new ArrayList<>(Arrays.asList(ruleSets.split(","))); + } + } + + /** + * Get the minimum priority threshold when loading Rules from RuleSets. + * + * @return The minimum priority threshold. + */ + public RulePriority getMinimumPriority() { + return minimumPriority; + } + + /** + * Set the minimum priority threshold when loading Rules from RuleSets. + * + * @param minimumPriority + * The minimum priority. + */ + public void setMinimumPriority(RulePriority minimumPriority) { + this.minimumPriority = minimumPriority; + } + + + /** + * Create a Renderer instance based upon the configured reporting options. + * No writer is created. + * + * @return renderer + */ + public Renderer createRenderer() { + return createRenderer(false); + } + + /** + * Create a Renderer instance based upon the configured reporting options. + * If withReportWriter then we'll configure it with a writer for the + * reportFile specified. + * + * @param withReportWriter + * whether to configure a writer or not + * @return A Renderer instance. + */ + public Renderer createRenderer(boolean withReportWriter) { + Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties); + renderer.setShowSuppressedViolations(showSuppressedViolations); + if (withReportWriter) { + renderer.setReportFile(getReportFile()); + } + return renderer; + } + + /** + * Get the report format. + * + * @return The report format. + */ + public String getReportFormat() { + return reportFormat; + } + + /** + * Set the report format. This should be a name of a Renderer. + * + * @param reportFormat + * The report format. + * + * @see Renderer + */ + public void setReportFormat(String reportFormat) { + this.reportFormat = reportFormat; + } + + /** + * Get whether the report should show suppressed violations. + * + * @return true if showing suppressed violations, + * false otherwise. + */ + public boolean isShowSuppressedViolations() { + return showSuppressedViolations; + } + + /** + * Set whether the report should show suppressed violations. + * + * @param showSuppressedViolations + * true if showing suppressed violations, + * false otherwise. + */ + public void setShowSuppressedViolations(boolean showSuppressedViolations) { + this.showSuppressedViolations = showSuppressedViolations; + } + + /** + * Get the Report properties. These are used to create the Renderer. + * + * @return The report properties. + */ + public Properties getReportProperties() { + return reportProperties; + } + + /** + * Set the Report properties. These are used to create the Renderer. + * + * @param reportProperties + * The Report properties to set. + */ + public void setReportProperties(Properties reportProperties) { + this.reportProperties = reportProperties; + } + + /** + * Whether PMD should exit with status 4 (the default behavior, true) if + * violations are found or just with 0 (to not break the build, e.g.). + * + * @return failOnViolation + */ + public boolean isFailOnViolation() { + return failOnViolation; + } + + /** + * Sets whether PMD should exit with status 4 (the default behavior, true) + * if violations are found or just with 0 (to not break the build, e.g.). + * + * @param failOnViolation + * failOnViolation + */ + public void setFailOnViolation(boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + /** + * Checks if the rule set factory compatibility feature is enabled. + * + * @return true, if the rule set factory compatibility feature is enabled + * + * @see RuleSetLoader#enableCompatibility(boolean) + */ + public boolean isRuleSetFactoryCompatibilityEnabled() { + return ruleSetFactoryCompatibilityEnabled; + } + + /** + * Sets the rule set factory compatibility feature enabled/disabled. + * + * @param ruleSetFactoryCompatibilityEnabled {@code true} if the feature should be enabled + * + * @see RuleSetLoader#enableCompatibility(boolean) + */ + public void setRuleSetFactoryCompatibilityEnabled(boolean ruleSetFactoryCompatibilityEnabled) { + this.ruleSetFactoryCompatibilityEnabled = ruleSetFactoryCompatibilityEnabled; + } + + /** + * Retrieves the currently used analysis cache. Will never be null. + * + * @return The currently used analysis cache. Never null. + */ + public AnalysisCache getAnalysisCache() { + // Make sure we are not null + if (analysisCache == null || isIgnoreIncrementalAnalysis() && !(analysisCache instanceof NoopAnalysisCache)) { + // sets a noop cache + setAnalysisCache(new NoopAnalysisCache()); + } + + return analysisCache; + } + + /** + * Sets the analysis cache to be used. Setting a + * value of {@code null} will cause a Noop AnalysisCache to be used. + * If incremental analysis was explicitly disabled ({@link #isIgnoreIncrementalAnalysis()}), + * then this method is a noop. + * + * @param cache The analysis cache to be used. + */ + public void setAnalysisCache(final AnalysisCache cache) { + // the doc says it's a noop if incremental analysis was disabled, + // but it's actually the getter that enforces that + this.analysisCache = cache == null ? new NoopAnalysisCache() : cache; + } + + /** + * Sets the location of the analysis cache to be used. This will automatically configure + * and appropriate AnalysisCache implementation. + * + * @param cacheLocation The location of the analysis cache to be used. + */ + public void setAnalysisCacheLocation(final String cacheLocation) { + setAnalysisCache(cacheLocation == null + ? new NoopAnalysisCache() + : new FileAnalysisCache(new File(cacheLocation))); + } + + + /** + * Sets whether the user has explicitly disabled incremental analysis or not. + * If so, incremental analysis is not used, and all suggestions to use it are + * disabled. The analysis cached location is ignored, even if it's specified. + * + * @param noCache Whether to ignore incremental analysis or not + */ + public void setIgnoreIncrementalAnalysis(boolean noCache) { + // see #getAnalysisCache for the implementation. + this.ignoreIncrementalAnalysis = noCache; + } + + + /** + * Returns whether incremental analysis was explicitly disabled by the user + * or not. + * + * @return {@code true} if incremental analysis is explicitly disabled + */ + public boolean isIgnoreIncrementalAnalysis() { + return ignoreIncrementalAnalysis; + } + + + /** + * Get the file to which the report should render. + * + * @return The file to which to render. + * @deprecated Use {@link #getReportFilePath()} + */ + @Deprecated + public String getReportFile() { + return reportFile == null ? null : reportFile.toString(); + } + + /** + * Get the file to which the report should render. + * + * @return The file to which to render. + */ + public Path getReportFilePath() { + return reportFile; + } + + /** + * Set the file to which the report should render. + * + * @param reportFile the file to set + * @deprecated Use {@link #setReportFile(Path)} + */ + @Deprecated + public void setReportFile(String reportFile) { + this.reportFile = reportFile == null ? null : Paths.get(reportFile); + } + + /** + * Set the file to which the report should render. + * + * @param reportFile the file to set + */ + public void setReportFile(Path reportFile) { + this.reportFile = reportFile; + } + + // ------------------- compat extensions -------------------- + @Deprecated + public void setSourceEncoding(String sourceEncoding) { + setSourceEncoding(Charset.forName(Objects.requireNonNull(sourceEncoding))); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java new file mode 100644 index 0000000000..89adae0290 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java @@ -0,0 +1,432 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: filterViolations(net.sourceforge.pmd.util.Predicate filter) + +package net.sourceforge.pmd; + +import static java.util.Collections.synchronizedList; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.sourceforge.pmd.annotation.DeprecatedUntil700; +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.renderers.AbstractAccumulatingRenderer; +import net.sourceforge.pmd.reporting.FileAnalysisListener; +import net.sourceforge.pmd.reporting.GlobalAnalysisListener; +import net.sourceforge.pmd.util.BaseResultProducingCloseable; + +/** + * A {@link Report} collects all informations during a PMD execution. This + * includes violations, suppressed violations, metrics, error during processing + * and configuration errors. + * + *

A report may be created by a {@link GlobalReportBuilderListener} that you + * use as the {@linkplain GlobalAnalysisListener} in {@link PmdAnalysis#performAnalysisAndCollectReport() PMD's entry point}. + * You can also create one manually with {@link #buildReport(Consumer)}. + * + *

For special use cases, like filtering the report after PMD analysis and + * before rendering the report, some transformation operations are provided: + *

    + *
  • {@link #filterViolations(Predicate)}
  • + *
  • {@link #union(Report)}
  • + *
+ * These methods create a new {@link Report} rather than modifying their receiver. + *

+ */ +public final class Report { + // todo move to package reporting + + private final List violations = synchronizedList(new ArrayList<>()); + private final List suppressedRuleViolations = synchronizedList(new ArrayList<>()); + private final List errors = synchronizedList(new ArrayList<>()); + private final List configErrors = synchronizedList(new ArrayList<>()); + + @DeprecatedUntil700 + @InternalApi + public Report() { // NOPMD - UnnecessaryConstructor + // TODO: should be package-private, you have to use a listener to build a report. + } + + /** + * Represents a configuration error. + */ + public static class ConfigurationError { + + private final Rule rule; + private final String issue; + + /** + * Creates a new configuration error for a specific rule. + * + * @param theRule + * the rule which is configured wrongly + * @param theIssue + * the reason, why the configuration is wrong + */ + public ConfigurationError(Rule theRule, String theIssue) { + rule = theRule; + issue = theIssue; + } + + /** + * Gets the wrongly configured rule + * + * @return the wrongly configured rule + */ + public Rule rule() { + return rule; + } + + /** + * Gets the reason for the configuration error. + * + * @return the issue + */ + public String issue() { + return issue; + } + } + + /** + * Represents a processing error, such as a parse error. + */ + public static class ProcessingError { + + private final Throwable error; + private final FileId file; + + /** + * Creates a new processing error + * + * @param error + * the error + * @param file + * the file during which the error occurred + */ + public ProcessingError(Throwable error, FileId file) { + this.error = error; + this.file = file; + } + + public String getMsg() { + return error.getClass().getSimpleName() + ": " + error.getMessage(); + } + + public String getDetail() { + try (StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter)) { + error.printStackTrace(writer); + return stringWriter.toString(); + } catch (IOException e) { + // IOException on close - should never happen when using StringWriter + throw new RuntimeException(e); + } + } + + public FileId getFileId() { + return file; + } + + public Throwable getError() { + return error; + } + } + + /** + * Represents a violation, that has been suppressed. + */ + public static class SuppressedViolation { + + private final RuleViolation rv; + private final String userMessage; + private final ViolationSuppressor suppressor; + + /** + * Creates a suppressed violation. + * + * @param rv The violation, that has been suppressed + * @param suppressor The suppressor which suppressed the violation + * @param userMessage Any relevant info given by the suppressor + */ + public SuppressedViolation(RuleViolation rv, ViolationSuppressor suppressor, String userMessage) { + this.suppressor = suppressor; + this.rv = rv; + this.userMessage = userMessage; + } + + public ViolationSuppressor getSuppressor() { + return suppressor; + } + + public RuleViolation getRuleViolation() { + return this.rv; + } + + public String getUserMessage() { + return userMessage; + } + } + + /** + * Adds a new rule violation to the report and notify the listeners. + * + * @param violation the violation to add + * + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addRuleViolation(RuleViolation violation) { + synchronized (violations) { + // note that this binary search is inefficient as we usually + // report violations file by file. + int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR); + violations.add(index < 0 ? -index - 1 : index, violation); + } + } + + /** + * Adds a new suppressed violation. + */ + private void addSuppressedViolation(SuppressedViolation sv) { + suppressedRuleViolations.add(sv); + } + + /** + * Adds a new configuration error to the report. + * + * @param error the error to add + * + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addConfigError(ConfigurationError error) { + configErrors.add(error); + } + + /** + * Adds a new processing error to the report. + * + * @param error + * the error to add + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addError(ProcessingError error) { + errors.add(error); + } + + /** + * Merges the given report into this report. This might be necessary, if a + * summary over all violations is needed as PMD creates one report per file + * by default. + * + *

This is synchronized on an internal lock (note that other mutation + * operations are not synchronized, todo for pmd 7). + * + * @param r the report to be merged into this. + * + * @see AbstractAccumulatingRenderer + * + * @deprecated Convert Renderer to use the reports. + */ + @Deprecated + public void merge(Report r) { + errors.addAll(r.errors); + configErrors.addAll(r.configErrors); + suppressedRuleViolations.addAll(r.suppressedRuleViolations); + + for (RuleViolation violation : r.getViolations()) { + addRuleViolation(violation); + } + } + + + /** + * Returns an unmodifiable list of violations that were suppressed. + */ + public List getSuppressedViolations() { + return Collections.unmodifiableList(suppressedRuleViolations); + } + + /** + * Returns an unmodifiable list of violations that have been + * recorded until now. None of those violations were suppressed. + * + *

The violations list is sorted with {@link RuleViolation#DEFAULT_COMPARATOR}. + */ + public List getViolations() { + return Collections.unmodifiableList(violations); + } + + + /** + * Returns an unmodifiable list of processing errors that have been + * recorded until now. + */ + public List getProcessingErrors() { + return Collections.unmodifiableList(errors); + } + + + /** + * Returns an unmodifiable list of configuration errors that have + * been recorded until now. + */ + public List getConfigurationErrors() { + return Collections.unmodifiableList(configErrors); + } + + /** + * Create a report by making side effects on a {@link FileAnalysisListener}. + * This wraps a {@link ReportBuilderListener}. + */ + public static Report buildReport(Consumer lambda) { + return BaseResultProducingCloseable.using(new ReportBuilderListener(), lambda); + } + + /** + * A {@link FileAnalysisListener} that accumulates events into a + * {@link Report}. + */ + public static final class ReportBuilderListener extends BaseResultProducingCloseable implements FileAnalysisListener { + + private final Report report; + + public ReportBuilderListener() { + this(new Report()); + } + + ReportBuilderListener(Report report) { + this.report = report; + } + + @Override + protected Report getResultImpl() { + return report; + } + + @Override + public void onRuleViolation(RuleViolation violation) { + report.addRuleViolation(violation); + } + + @Override + public void onSuppressedRuleViolation(SuppressedViolation violation) { + report.addSuppressedViolation(violation); + } + + @Override + public void onError(ProcessingError error) { + report.addError(error); + } + + @Override + public String toString() { + return "ReportBuilderListener"; + } + } + + /** + * A {@link GlobalAnalysisListener} that accumulates the events of + * all files into a {@link Report}. + */ + public static final class GlobalReportBuilderListener extends BaseResultProducingCloseable implements GlobalAnalysisListener { + + private final Report report = new Report(); + + @Override + public FileAnalysisListener startFileAnalysis(TextFile file) { + // note that the report is shared, but Report is now thread-safe + return new ReportBuilderListener(this.report); + } + + @Override + public void onConfigError(ConfigurationError error) { + report.addConfigError(error); + } + + @Override + protected Report getResultImpl() { + return report; + } + } + + /** + * Creates a new report taking all the information from this report, + * but filtering the violations. + * + * @param filter when true, the violation will be kept. + * @return copy of this report + */ + @Experimental + public Report filterViolations(Predicate filter) { + Report copy = new Report(); + + for (RuleViolation violation : violations) { + if (filter.test(violation)) { + copy.addRuleViolation(violation); + } + } + + copy.suppressedRuleViolations.addAll(suppressedRuleViolations); + copy.errors.addAll(errors); + copy.configErrors.addAll(configErrors); + return copy; + } + + /** + * Creates a new report by combining this report with another report. + * This is similar to {@link #merge(Report)}, but instead a new report + * is created. The lowest start time and greatest end time are kept in the copy. + * + * @param other the other report to combine + * @return + */ + @Experimental + public Report union(Report other) { + Report copy = new Report(); + + for (RuleViolation violation : violations) { + copy.addRuleViolation(violation); + } + for (RuleViolation violation : other.violations) { + copy.addRuleViolation(violation); + } + + copy.suppressedRuleViolations.addAll(suppressedRuleViolations); + copy.suppressedRuleViolations.addAll(other.suppressedRuleViolations); + + copy.errors.addAll(errors); + copy.errors.addAll(other.errors); + copy.configErrors.addAll(configErrors); + copy.configErrors.addAll(other.configErrors); + + return copy; + } + + // ------------------- compat extensions -------------------- + @Deprecated + public Report filterViolations(net.sourceforge.pmd.util.Predicate filter) { + Predicate javaPredicate = filter::test; + return filterViolations(javaPredicate); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java new file mode 100644 index 0000000000..dcb3030052 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java @@ -0,0 +1,196 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: getFilename + +package net.sourceforge.pmd; + +import java.util.Comparator; +import java.util.Map; + +import net.sourceforge.pmd.annotation.DeprecatedUntil700; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; + +/** + * A RuleViolation is created by a Rule when it identifies a violation of the + * Rule constraints. RuleViolations are simple data holders that are collected + * into a {@link Report}. + * + *

Since PMD 6.21.0, implementations of this interface are considered internal + * API and hence deprecated. Clients should exclusively use this interface. + * + * @see Rule + */ +public interface RuleViolation { + // todo move to package reporting + + /** + * A comparator for rule violations. This compares all exposed attributes + * of a violation, filename first. The remaining parameters are compared + * in an unspecified order. + */ + Comparator DEFAULT_COMPARATOR = + Comparator.comparing(RuleViolation::getFileId) + .thenComparingInt(RuleViolation::getBeginLine) + .thenComparingInt(RuleViolation::getBeginColumn) + .thenComparing(RuleViolation::getDescription, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparingInt(RuleViolation::getEndLine) + .thenComparingInt(RuleViolation::getEndColumn) + .thenComparing(rv -> rv.getRule().getName()); + + + /** + * Key in {@link #getAdditionalInfo()} for the name of the class in + * which the violation was identified. + */ + String CLASS_NAME = "className"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the variable + * related to the violation. + */ + String VARIABLE_NAME = "variableName"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the method in + * which the violation was identified. + */ + String METHOD_NAME = "methodName"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the package in + * which the violation was identified. + */ + String PACKAGE_NAME = "packageName"; + + /** + * Get the Rule which identified this violation. + * + * @return The identifying Rule. + */ + Rule getRule(); + + /** + * Get the description of this violation. + * + * @return The description. + */ + String getDescription(); + + + /** + * Returns the location where the violation should be reported. + */ + FileLocation getLocation(); + + /** + * Return the ID of the file where the violation was found. + */ + default FileId getFileId() { + return getLocation().getFileId(); + } + + /** + * Get the begin line number in the source file in which this violation was + * identified. + * + * @return Begin line number. + */ + default int getBeginLine() { + return getLocation().getStartPos().getLine(); + } + + /** + * Get the column number of the begin line in the source file in which this + * violation was identified. + * + * @return Begin column number. + */ + default int getBeginColumn() { + return getLocation().getStartPos().getColumn(); + } + + /** + * Get the end line number in the source file in which this violation was + * identified. + * + * @return End line number. + */ + default int getEndLine() { + return getLocation().getEndPos().getLine(); + } + + /** + * Get the column number of the end line in the source file in which this + * violation was identified. + * + * @return End column number. + */ + default int getEndColumn() { + return getLocation().getEndPos().getColumn(); + } + + /** + * A map of additional key-value pairs known about this violation. + * What data is in there is language specific. Common keys supported + * by several languages are defined as constants on this interface. + * The map is unmodifiable. + */ + Map getAdditionalInfo(); + + + /** + * Get the package name of the Class in which this violation was identified. + * + * @return The package name. + * + * @deprecated Use {@link #PACKAGE_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getPackageName() { + return getAdditionalInfo().get(PACKAGE_NAME); + } + + /** + * Get the name of the Class in which this violation was identified. + * + * @return The Class name. + * @deprecated Use {@link #CLASS_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getClassName() { + return getAdditionalInfo().get(CLASS_NAME); + } + + /** + * Get the method name in which this violation was identified. + * + * @return The method name. + * @deprecated Use {@link #METHOD_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getMethodName() { + return getAdditionalInfo().get(METHOD_NAME); + } + + /** + * Get the variable name on which this violation was identified. + * + * @return The variable name. + * @deprecated Use {@link #VARIABLE_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getVariableName() { + return getAdditionalInfo().get(VARIABLE_NAME); + } + + // ------------------- compat extensions -------------------- + @Deprecated + default String getFilename() { + return getLocation().getFileId().getFileName(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java new file mode 100644 index 0000000000..c6d1d84760 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import net.sourceforge.pmd.util.filter.Filters; + +public abstract class AbstractLanguage implements Language { + private final String name; + private final String terseName; + private final Tokenizer tokenizer; + private final FilenameFilter fileFilter; + private final List extensions; + + public AbstractLanguage(String name, String terseName, Tokenizer tokenizer, String... extensions) { + this.name = name; + this.terseName = terseName; + this.tokenizer = tokenizer; + fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions)); + this.extensions = Arrays.asList(extensions); + } + + @Override + public FilenameFilter getFileFilter() { + return fileFilter; + } + + @Override + public Tokenizer getTokenizer() { + return tokenizer; + } + + @Override + public void setProperties(Properties properties) { + // needs to be implemented by subclasses. + } + + @Override + public String getName() { + return name; + } + + @Override + public String getTerseName() { + return terseName; + } + + @Override + public List getExtensions() { + return extensions; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java new file mode 100644 index 0000000000..c570dcec07 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -0,0 +1,97 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.internal.util.FileFinder; +import net.sourceforge.pmd.internal.util.IOUtil; + +/** + * Adapter for PMD 7. This exposes CPD interface of PMD 6 but runs PMD 7 under the hood. + */ +public class CPD { + private final CPDConfiguration configuration; + private final List files = new ArrayList<>(); + private Set current = new HashSet<>(); + private CPDReport report; + + public CPD(CPDConfiguration configuration) { + this.configuration = configuration; + } + + public void addAllInDirectory(File dir) throws IOException { + addDirectory(dir, false); + } + + public void addRecursively(File dir) throws IOException { + addDirectory(dir, true); + } + + public void add(List files) throws IOException { + for (File f : files) { + add(f); + } + } + + private void addDirectory(File dir, boolean recurse) throws IOException { + if (!dir.exists()) { + throw new FileNotFoundException("Couldn't find directory " + dir); + } + FileFinder finder = new FileFinder(); + // TODO - could use SourceFileSelector here + add(finder.findFilesFrom(dir, configuration.filenameFilter(), recurse)); + } + + public void add(File file) throws IOException { + if (configuration.isSkipDuplicates()) { + // TODO refactor this thing into a separate class + String signature = file.getName() + '_' + file.length(); + if (current.contains(signature)) { + System.err.println("Skipping " + file.getAbsolutePath() + + " since it appears to be a duplicate file and --skip-duplicate-files is set"); + return; + } + current.add(signature); + } + + if (!IOUtil.equalsNormalizedPaths(file.getAbsoluteFile().getCanonicalPath(), file.getAbsolutePath())) { + System.err.println("Skipping " + file + " since it appears to be a symlink"); + return; + } + + if (!file.exists()) { + System.err.println("Skipping " + file + " since it doesn't exist (broken symlink?)"); + return; + } + + files.add(file.toPath()); + } + + public void go() { + try (CpdAnalysis cpd = CpdAnalysis.create(configuration)) { + files.forEach(cpd.files()::addFile); + cpd.performAnalysis(this::collectReport); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void collectReport(CPDReport cpdReport) { + this.report = cpdReport; + } + + public Iterator getMatches() { + return report.getMatches().iterator(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java new file mode 100644 index 0000000000..a747e69b2c --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -0,0 +1,341 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: setLanguage, setSourceEncoding, filenameFilter + +package net.sourceforge.pmd.cpd; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.AbstractConfiguration; +import net.sourceforge.pmd.internal.util.FileFinder; +import net.sourceforge.pmd.internal.util.FileUtil; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.ecmascript.EcmascriptLanguageModule; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * + * @author Brian Remedios + * @author Romain Pelisse - <belaran@gmail.com> + */ +public class CPDConfiguration extends AbstractConfiguration { + + public static final String DEFAULT_LANGUAGE = "java"; + public static final String DEFAULT_RENDERER = "text"; + + private static final Map> RENDERERS = new HashMap<>(); + + + static { + RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class); + RENDERERS.put("xml", XMLRenderer.class); + RENDERERS.put("csv", CSVRenderer.class); + RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); + RENDERERS.put("vs", VSRenderer.class); + } + + + private int minimumTileSize; + + private boolean skipDuplicates; + + private String rendererName = DEFAULT_RENDERER; + + private @Nullable CPDReportRenderer cpdReportRenderer; + + private boolean ignoreLiterals; + + private boolean ignoreIdentifiers; + + private boolean ignoreAnnotations; + + private boolean ignoreUsings; + + private boolean ignoreLiteralSequences = false; + + private boolean ignoreIdentifierAndLiteralSequences = false; + + private boolean skipLexicalErrors = false; + + private boolean noSkipBlocks = false; + + private String skipBlocksPattern = CpdLanguageProperties.DEFAULT_SKIP_BLOCKS_PATTERN; + + private boolean help; + + private boolean failOnViolation = true; + + + public CPDConfiguration() { + this(LanguageRegistry.CPD); + } + + public CPDConfiguration(LanguageRegistry languageRegistry) { + super(languageRegistry, new SimpleMessageReporter(LoggerFactory.getLogger(CpdAnalysis.class))); + } + + @Override + public void setSourceEncoding(Charset sourceEncoding) { + super.setSourceEncoding(sourceEncoding); + if (cpdReportRenderer != null) { + setRendererEncoding(cpdReportRenderer, sourceEncoding); + } + } + + static CPDReportRenderer createRendererByName(String name, Charset encoding) { + if (name == null || "".equals(name)) { + name = DEFAULT_RENDERER; + } + Class rendererClass = RENDERERS.get(name.toLowerCase(Locale.ROOT)); + if (rendererClass == null) { + Class klass; + try { + klass = Class.forName(name); + if (CPDReportRenderer.class.isAssignableFrom(klass)) { + rendererClass = (Class) klass; + } else { + throw new IllegalArgumentException("Class " + name + " does not implement " + CPDReportRenderer.class); + } + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot find class " + name); + } + } + + CPDReportRenderer renderer; + try { + renderer = rendererClass.getDeclaredConstructor().newInstance(); + setRendererEncoding(renderer, encoding); + } catch (Exception e) { + System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); + renderer = new SimpleRenderer(); + } + return renderer; + } + + private static void setRendererEncoding(@NonNull Object renderer, Charset encoding) { + try { + PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass()); + Method method = encodingProperty.getWriteMethod(); + if (method == null) { + return; + } + if (method.getParameterTypes()[0] == Charset.class) { + method.invoke(renderer, encoding); + } else if (method.getParameterTypes()[0] == String.class) { + method.invoke(renderer, encoding.name()); + } + } catch (IntrospectionException | ReflectiveOperationException ignored) { + // ignored - maybe this renderer doesn't have a encoding property + } + } + + public static Set getRenderers() { + return Collections.unmodifiableSet(RENDERERS.keySet()); + } + + public int getMinimumTileSize() { + return minimumTileSize; + } + + public void setMinimumTileSize(int minimumTileSize) { + this.minimumTileSize = minimumTileSize; + } + + public boolean isSkipDuplicates() { + return skipDuplicates; + } + + public void setSkipDuplicates(boolean skipDuplicates) { + this.skipDuplicates = skipDuplicates; + } + + public String getRendererName() { + return rendererName; + } + + public void setRendererName(String rendererName) { + this.rendererName = rendererName; + if (rendererName == null) { + this.cpdReportRenderer = null; + } + this.cpdReportRenderer = createRendererByName(rendererName, getSourceEncoding()); + } + + + public CPDReportRenderer getCPDReportRenderer() { + return cpdReportRenderer; + } + + void setRenderer(CPDReportRenderer renderer) { + this.cpdReportRenderer = renderer; + } + + public boolean isIgnoreLiterals() { + return ignoreLiterals; + } + + public void setIgnoreLiterals(boolean ignoreLiterals) { + this.ignoreLiterals = ignoreLiterals; + } + + public boolean isIgnoreIdentifiers() { + return ignoreIdentifiers; + } + + public void setIgnoreIdentifiers(boolean ignoreIdentifiers) { + this.ignoreIdentifiers = ignoreIdentifiers; + } + + public boolean isIgnoreAnnotations() { + return ignoreAnnotations; + } + + public void setIgnoreAnnotations(boolean ignoreAnnotations) { + this.ignoreAnnotations = ignoreAnnotations; + } + + public boolean isIgnoreUsings() { + return ignoreUsings; + } + + public void setIgnoreUsings(boolean ignoreUsings) { + this.ignoreUsings = ignoreUsings; + } + + public boolean isIgnoreLiteralSequences() { + return ignoreLiteralSequences; + } + + public void setIgnoreLiteralSequences(boolean ignoreLiteralSequences) { + this.ignoreLiteralSequences = ignoreLiteralSequences; + } + + public boolean isIgnoreIdentifierAndLiteralSequences() { + return ignoreIdentifierAndLiteralSequences; + } + + public void setIgnoreIdentifierAndLiteralSequences(boolean ignoreIdentifierAndLiteralSequences) { + this.ignoreIdentifierAndLiteralSequences = ignoreIdentifierAndLiteralSequences; + } + + public boolean isSkipLexicalErrors() { + return skipLexicalErrors; + } + + public void setSkipLexicalErrors(boolean skipLexicalErrors) { + this.skipLexicalErrors = skipLexicalErrors; + } + + public boolean isHelp() { + return help; + } + + public void setHelp(boolean help) { + this.help = help; + } + + public boolean isNoSkipBlocks() { + return noSkipBlocks; + } + + public void setNoSkipBlocks(boolean noSkipBlocks) { + this.noSkipBlocks = noSkipBlocks; + } + + public String getSkipBlocksPattern() { + return skipBlocksPattern; + } + + public void setSkipBlocksPattern(String skipBlocksPattern) { + this.skipBlocksPattern = skipBlocksPattern; + } + + public boolean isFailOnViolation() { + return failOnViolation; + } + + public void setFailOnViolation(boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + // ------------------- compat extensions -------------------- + private FilenameFilter filenameFilter; + + public void setLanguage(Language language) { + if (language instanceof JavaLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(JavaLanguageModule.getInstance().getDefaultVersion()); + } else if (language instanceof EcmascriptLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(EcmascriptLanguageModule.getInstance().getDefaultVersion()); + } else if (language instanceof JSPLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(JspLanguageModule.getInstance().getDefaultVersion()); + } else { + throw new UnsupportedOperationException("Language " + language.getName() + " is not supported"); + } + } + + public void setSourceEncoding(String sourceEncoding) { + setSourceEncoding(Charset.forName(Objects.requireNonNull(sourceEncoding))); + } + + public FilenameFilter filenameFilter() { + if (getForceLanguageVersion() == null) { + throw new IllegalStateException("Language is null."); + } + + final FilenameFilter languageFilter = filenameFilter; + final Set exclusions = new HashSet<>(); + + if (getExcludes() != null) { + FileFinder finder = new FileFinder(); + for (Path excludedFile : getExcludes()) { + if (Files.isDirectory(excludedFile)) { + List files = finder.findFilesFrom(excludedFile.toFile(), languageFilter, true); + for (File f : files) { + exclusions.add(FileUtil.normalizeFilename(f.getAbsolutePath())); + } + } else { + exclusions.add(FileUtil.normalizeFilename(excludedFile.toAbsolutePath().toString())); + } + } + } + + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + File f = new File(dir, name); + if (exclusions.contains(FileUtil.normalizeFilename(f.getAbsolutePath()))) { + System.err.println("Excluding " + f.getAbsolutePath()); + return false; + } + return languageFilter.accept(dir, name); + } + }; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java new file mode 100644 index 0000000000..ecd2a5a2cf --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +/** + * + * @author Zev Blut zb@ubit.com + */ +public class EcmascriptLanguage extends AbstractLanguage { + public EcmascriptLanguage() { + super("JavaScript", "ecmascript", new EcmascriptTokenizer(), ".js"); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java new file mode 100644 index 0000000000..f75312c469 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java @@ -0,0 +1,8 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +public class EcmascriptTokenizer extends net.sourceforge.pmd.lang.ecmascript.cpd.EcmascriptTokenizer { +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java new file mode 100644 index 0000000000..e1501bad5d --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +public class JSPLanguage extends AbstractLanguage { + public JSPLanguage() { + super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx", ".jspf", ".tag"); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java new file mode 100644 index 0000000000..76b768afd8 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java @@ -0,0 +1,8 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +public class JSPTokenizer extends net.sourceforge.pmd.lang.jsp.cpd.JSPTokenizer { +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java new file mode 100644 index 0000000000..344a5d9bbc --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java @@ -0,0 +1,26 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 +// Changes: setProperties doesn't work, provide properties in constructor already + +package net.sourceforge.pmd.cpd; + +import java.util.Properties; + +public class JavaLanguage extends AbstractLanguage { + public JavaLanguage() { + this(System.getProperties()); + } + + public JavaLanguage(Properties properties) { + super("Java", "java", new JavaTokenizer(properties), ".java"); + } + + @Override + public final void setProperties(Properties properties) { + // note: this might be actually incompatible + throw new UnsupportedOperationException(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java new file mode 100644 index 0000000000..113663cffa --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java @@ -0,0 +1,33 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.util.Properties; + +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.lang.java.internal.JavaLanguageProperties; + +public class JavaTokenizer extends net.sourceforge.pmd.lang.java.cpd.JavaTokenizer { + public JavaTokenizer(Properties properties) { + super(convertLanguageProperties(properties)); + } + + private static final String IGNORE_LITERALS = "ignore_literals"; + private static final String IGNORE_IDENTIFIERS = "ignore_identifiers"; + private static final String IGNORE_ANNOTATIONS = "ignore_annotations"; + + private static JavaLanguageProperties convertLanguageProperties(Properties properties) { + boolean ignoreAnnotations = Boolean.parseBoolean(properties.getProperty(IGNORE_ANNOTATIONS, "false")); + boolean ignoreLiterals = Boolean.parseBoolean(properties.getProperty(IGNORE_LITERALS, "false")); + boolean ignoreIdentifiers = Boolean.parseBoolean(properties.getProperty(IGNORE_IDENTIFIERS, "false")); + + JavaLanguageProperties javaLanguageProperties = (JavaLanguageProperties) JavaLanguageModule.getInstance().newPropertyBundle(); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_IGNORE_METADATA, ignoreAnnotations); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_LITERALS, ignoreLiterals); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_IDENTIFIERS, ignoreIdentifiers); + + return javaLanguageProperties; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java new file mode 100644 index 0000000000..d2ee070bb5 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +import java.io.FilenameFilter; +import java.util.List; +import java.util.Properties; + +public interface Language { + String getName(); + + String getTerseName(); + + Tokenizer getTokenizer(); + + FilenameFilter getFileFilter(); + + void setProperties(Properties properties); + + List getExtensions(); +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java new file mode 100644 index 0000000000..2bcfdc15ea --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java @@ -0,0 +1,104 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: getFilename + +package net.sourceforge.pmd.cpd; + +import java.util.Objects; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.lang.document.TextRange2d; + +/** + * A range of tokens in a source file, identified by a start and end + * token (both included in the range). The start and end token may be + * the same token. + */ +public final class Mark implements Comparable { + + private final @NonNull TokenEntry token; + private @Nullable TokenEntry endToken; + + Mark(@NonNull TokenEntry token) { + this.token = token; + } + + @NonNull TokenEntry getToken() { + return this.token; + } + + @NonNull TokenEntry getEndToken() { + return endToken == null ? token : endToken; + } + + /** + * Return the location of this source range in the source file. + */ + public FileLocation getLocation() { + TokenEntry endToken = getEndToken(); + return FileLocation.range( + getFileId(), + TextRange2d.range2d(token.getBeginLine(), token.getBeginColumn(), + endToken.getEndLine(), endToken.getEndColumn())); + } + + FileId getFileId() { + return token.getFileId(); + } + + public int getBeginTokenIndex() { + return this.token.getIndex(); + } + + public int getEndTokenIndex() { + return getEndToken().getIndex(); + } + + void setEndToken(@NonNull TokenEntry endToken) { + assert endToken.getFileId().equals(token.getFileId()) + : "Tokens are not from the same file"; + this.endToken = endToken; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + token.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Mark other = (Mark) obj; + return Objects.equals(token, other.token) + && Objects.equals(endToken, other.endToken); + } + + @Override + public int compareTo(Mark other) { + return getToken().compareTo(other.getToken()); + } + + // ------------------- compat extensions -------------------- + public String getFilename() { + return this.token.getFileId().getOriginalPath(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java new file mode 100644 index 0000000000..4ebab60230 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -0,0 +1,193 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: implements old interface CPDRenderer, old render(Iterator matches, Writer writer) method + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.lang.document.Chars; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.util.StringUtil; + +/** + * @author Philippe T'Seyen - original implementation + * @author Romain Pelisse - javax.xml implementation + * + */ +public final class XMLRenderer implements CPDReportRenderer, CPDRenderer { + + private String encoding; + + /** + * Creates a XML Renderer with the default (platform dependent) encoding. + */ + public XMLRenderer() { + this(null); + } + + /** + * Creates a XML Renderer with a specific output encoding. + * + * @param encoding + * the encoding to use or null. If null, default (platform + * dependent) encoding is used. + */ + public XMLRenderer(String encoding) { + setEncoding(encoding); + } + + public void setEncoding(String encoding) { + if (encoding != null) { + this.encoding = encoding; + } else { + this.encoding = System.getProperty("file.encoding"); + } + } + + public String getEncoding() { + return this.encoding; + } + + private Document createDocument() { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder parser = factory.newDocumentBuilder(); + return parser.newDocument(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException(e); + } + } + + private void dumpDocToWriter(Document doc, Writer writer) { + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.VERSION, "1.0"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "codefragment"); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + } catch (TransformerException e) { + throw new IllegalStateException(e); + } + } + + + @Override + public void render(final CPDReport report, final Writer writer) throws IOException { + final Document doc = createDocument(); + final Element root = doc.createElement("pmd-cpd"); + final Map numberOfTokensPerFile = report.getNumberOfTokensPerFile(); + doc.appendChild(root); + + for (final Map.Entry pair : numberOfTokensPerFile.entrySet()) { + final Element fileElement = doc.createElement("file"); + fileElement.setAttribute("path", report.getDisplayName(pair.getKey())); + fileElement.setAttribute("totalNumberOfTokens", String.valueOf(pair.getValue())); + root.appendChild(fileElement); + } + + for (Match match : report.getMatches()) { + Element dupElt = createDuplicationElement(doc, match); + addFilesToDuplicationElement(doc, dupElt, match, report); + addCodeSnippet(doc, dupElt, match, report); + root.appendChild(dupElt); + } + dumpDocToWriter(doc, writer); + writer.flush(); + } + + private void addFilesToDuplicationElement(Document doc, Element duplication, Match match, CPDReport report) { + for (Mark mark : match) { + final Element file = doc.createElement("file"); + FileLocation loc = mark.getLocation(); + file.setAttribute("line", String.valueOf(loc.getStartLine())); + // only remove invalid characters, escaping is done by the DOM impl. + String filenameXml10 = StringUtil.removedInvalidXml10Characters(report.getDisplayName(loc.getFileId())); + file.setAttribute("path", filenameXml10); + file.setAttribute("endline", String.valueOf(loc.getEndLine())); + file.setAttribute("column", String.valueOf(loc.getStartColumn())); + file.setAttribute("endcolumn", String.valueOf(loc.getEndColumn())); + file.setAttribute("begintoken", String.valueOf(mark.getBeginTokenIndex())); + file.setAttribute("endtoken", String.valueOf(mark.getEndTokenIndex())); + duplication.appendChild(file); + } + } + + private void addCodeSnippet(Document doc, Element duplication, Match match, CPDReport report) { + Chars codeSnippet = report.getSourceCodeSlice(match.getFirstMark()); + if (codeSnippet != null) { + // the code snippet has normalized line endings + String platformSpecific = codeSnippet.toString().replace("\n", System.lineSeparator()); + Element codefragment = doc.createElement("codefragment"); + // only remove invalid characters, escaping is not necessary in CDATA. + // if the string contains the end marker of a CDATA section, then the DOM impl will + // create two cdata sections automatically. + codefragment.appendChild(doc.createCDATASection(StringUtil.removedInvalidXml10Characters(platformSpecific))); + duplication.appendChild(codefragment); + } + } + + private Element createDuplicationElement(Document doc, Match match) { + Element duplication = doc.createElement("duplication"); + duplication.setAttribute("lines", String.valueOf(match.getLineCount())); + duplication.setAttribute("tokens", String.valueOf(match.getTokenCount())); + return duplication; + } + + // ------------------- compat extensions -------------------- + @Override + public void render(Iterator matches, Writer writer) throws IOException { + List matchesList = new ArrayList<>(); + matches.forEachRemaining(matchesList::add); + + List textFiles = new ArrayList<>(); + Set paths = new HashSet<>(); + for (Match match : matchesList) { + for (Mark mark : match.getMarkSet()) { + paths.add(mark.getFilename()); + } + } + for (String path : paths) { + textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion())); + } + + try (SourceManager sourcManager = new SourceManager(textFiles)) { + CPDReport report = new CPDReport(sourcManager, matchesList, Collections.emptyMap()); + render(report, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java new file mode 100644 index 0000000000..f79d108642 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd.renderer; + +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import net.sourceforge.pmd.cpd.Match; + +/** + * @deprecated Use {@link CPDReportRenderer} + */ +@Deprecated +public interface CPDRenderer { + void render(Iterator matches, Writer writer) throws IOException; +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java new file mode 100644 index 0000000000..851c4bb492 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java @@ -0,0 +1,65 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A base class for Filters which implements behavior using a List of other + * Filters. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public abstract class AbstractCompoundFilter implements Filter { + + protected List> filters; + + public AbstractCompoundFilter() { + filters = new ArrayList<>(2); + } + + public AbstractCompoundFilter(Filter... filters) { + this.filters = Arrays.asList(filters); + } + + public List> getFilters() { + return filters; + } + + public void setFilters(List> filters) { + this.filters = filters; + } + + public void addFilter(Filter filter) { + filters.add(filter); + } + + protected abstract String getOperator(); + + @Override + public String toString() { + + if (filters.isEmpty()) { + return "()"; + } + + StringBuilder builder = new StringBuilder(); + builder.append('(').append(filters.get(0)); + + for (int i = 1; i < filters.size(); i++) { + builder.append(' ').append(getOperator()).append(' '); + builder.append(filters.get(i)); + } + builder.append(')'); + return builder.toString(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java new file mode 100644 index 0000000000..e6ca4e47d2 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java @@ -0,0 +1,48 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A base class for Filters which implements behavior using delegation to an + * underlying filter. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public abstract class AbstractDelegateFilter implements Filter { + protected Filter filter; + + public AbstractDelegateFilter() { + // default constructor + } + + public AbstractDelegateFilter(Filter filter) { + this.filter = filter; + } + + public Filter getFilter() { + return filter; + } + + public void setFilter(Filter filter) { + this.filter = filter; + } + + // Subclass should override to do something other the simply delegate. + @Override + public boolean filter(T obj) { + return filter.filter(obj); + } + + // Subclass should override to do something other the simply delegate. + @Override + public String toString() { + return filter.toString(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java new file mode 100644 index 0000000000..be8ff31fea --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical AND of a list of Filters. This implementation is short circuiting. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class AndFilter extends AbstractCompoundFilter { + + public AndFilter() { + super(); + } + + public AndFilter(Filter... filters) { + super(filters); + } + + @Override + public boolean filter(T obj) { + boolean match = true; + for (Filter filter : filters) { + if (!filter.filter(obj)) { + match = false; + break; + } + } + return match; + } + + @Override + protected String getOperator() { + return "and"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java new file mode 100644 index 0000000000..c1669e91d3 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; + +/** + * Directory filter. + * @deprecated See {@link Filter} + */ +@Deprecated +public final class DirectoryFilter implements Filter { + public static final DirectoryFilter INSTANCE = new DirectoryFilter(); + + private DirectoryFilter() { + } + + @Override + public boolean filter(File file) { + return file.isDirectory(); + } + + @Override + public String toString() { + return "is Directory"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java new file mode 100644 index 0000000000..06259afce3 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; +import java.util.Locale; + +/** + * @deprecated See {@link Filter} + */ +@Deprecated +public class FileExtensionFilter implements Filter { + protected final String[] extensions; + protected final boolean ignoreCase; + + /** + * Matches any files with the given extensions, ignoring case + */ + public FileExtensionFilter(String... extensions) { + this(true, extensions); + } + + /** + * Matches any files with the given extensions, optionally ignoring case. + */ + public FileExtensionFilter(boolean ignoreCase, String... extensions) { + this.extensions = extensions; + this.ignoreCase = ignoreCase; + if (ignoreCase) { + for (int i = 0; i < this.extensions.length; i++) { + this.extensions[i] = this.extensions[i].toUpperCase(Locale.ROOT); + } + } + } + + @Override + public boolean filter(File file) { + boolean accept = extensions == null; + if (!accept) { + for (String extension : extensions) { + String name = file.getName(); + if (ignoreCase ? name.toUpperCase(Locale.ROOT).endsWith(extension) : name.endsWith(extension)) { + accept = true; + break; + } + } + } + return accept; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java new file mode 100644 index 0000000000..e99a14bb44 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A Filter interface, used for filtering arbitrary objects. + * + * @param + * The underlying type on which the filter applies. + * + * @deprecated Will be replaced with standard java.util.function.Predicate with 7.0.0 + */ +@Deprecated +public interface Filter { + boolean filter(T obj); +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java new file mode 100644 index 0000000000..699b614308 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java @@ -0,0 +1,245 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sourceforge.pmd.annotation.InternalApi; + +/** + * Utility class for working with Filters. Contains builder style methods, apply + * methods, as well as mechanisms for adapting Filters and FilenameFilters. + * + * @deprecated Internal API, see {@link Filter} + */ +@Deprecated +@InternalApi +public final class Filters { + + private Filters() { } + + /** + * Filter a given Collection. + * + * @param + * Type of the Collection. + * @param filter + * A Filter upon the Type of objects in the Collection. + * @param collection + * The Collection to filter. + * @return A List containing only those objects for which the Filter + * returned true. + */ + public static List filter(Filter filter, Collection collection) { + List list = new ArrayList<>(); + for (T obj : collection) { + if (filter.filter(obj)) { + list.add(obj); + } + } + return list; + } + + /** + * Get a File Filter for files with the given extensions, ignoring case. + * + * @param extensions + * The extensions to filter. + * @return A File Filter. + */ + public static Filter getFileExtensionFilter(String... extensions) { + return new FileExtensionFilter(extensions); + } + + /** + * Get a File Filter for directories. + * + * @return A File Filter. + */ + public static Filter getDirectoryFilter() { + return DirectoryFilter.INSTANCE; + } + + /** + * Get a File Filter for directories or for files with the given extensions, + * ignoring case. + * + * @param extensions + * The extensions to filter. + * @return A File Filter. + */ + public static Filter getFileExtensionOrDirectoryFilter(String... extensions) { + return new OrFilter<>(getFileExtensionFilter(extensions), getDirectoryFilter()); + } + + /** + * Given a String Filter, expose as a File Filter. The File paths are + * normalized to a standard pattern using / as a path separator + * which can be used cross platform easily in a regular expression based + * String Filter. + * + * @param filter + * A String Filter. + * @return A File Filter. + */ + public static Filter toNormalizedFileFilter(final Filter filter) { + return new Filter() { + @Override + public boolean filter(File file) { + String path = file.getPath(); + path = path.replace('\\', '/'); + return filter.filter(path); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a String Filter, expose as a Filter on another type. The + * toString() method is called on the objects of the other type + * and delegated to the String Filter. + * + * @param + * The desired type. + * @param filter + * The existing String Filter. + * @return A Filter on the desired type. + */ + public static Filter fromStringFilter(final Filter filter) { + return new Filter() { + @Override + public boolean filter(T obj) { + return filter.filter(obj.toString()); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a File Filter, expose as a FilenameFilter. + * + * @param filter + * The File Filter. + * @return A FilenameFilter. + */ + public static FilenameFilter toFilenameFilter(final Filter filter) { + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return filter.filter(new File(dir, name)); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a FilenameFilter, expose as a File Filter. + * + * @param filter + * The FilenameFilter. + * @return A File Filter. + */ + public static Filter toFileFilter(final FilenameFilter filter) { + return new Filter() { + @Override + public boolean filter(File file) { + return filter.accept(file.getParentFile(), file.getName()); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Construct a String Filter using set of include and exclude regular + * expressions. If there are no include regular expressions provide, then a + * regular expression is added which matches every String by default. A + * String is included as long as it matches an include regular expression + * and does not match an exclude regular expression. + *

+ * In other words, exclude patterns override include patterns. + * + * @param includeRegexes + * The include regular expressions. May be null. + * @param excludeRegexes + * The exclude regular expressions. May be null. + * @return A String Filter. + */ + public static Filter buildRegexFilterExcludeOverInclude(List includeRegexes, + List excludeRegexes) { + OrFilter includeFilter = new OrFilter<>(); + if (includeRegexes == null || includeRegexes.isEmpty()) { + includeFilter.addFilter(new RegexStringFilter(".*")); + } else { + for (String includeRegex : includeRegexes) { + includeFilter.addFilter(new RegexStringFilter(includeRegex)); + } + } + + OrFilter excludeFilter = new OrFilter<>(); + if (excludeRegexes != null) { + for (String excludeRegex : excludeRegexes) { + excludeFilter.addFilter(new RegexStringFilter(excludeRegex)); + } + } + + return new AndFilter<>(includeFilter, new NotFilter<>(excludeFilter)); + } + + /** + * Construct a String Filter using set of include and exclude regular + * expressions. If there are no include regular expressions provide, then a + * regular expression is added which matches every String by default. A + * String is included as long as the case that there is an include which + * matches or there is not an exclude which matches. + *

+ * In other words, include patterns override exclude patterns. + * + * @param includeRegexes + * The include regular expressions. May be null. + * @param excludeRegexes + * The exclude regular expressions. May be null. + * @return A String Filter. + */ + public static Filter buildRegexFilterIncludeOverExclude(List includeRegexes, + List excludeRegexes) { + OrFilter includeFilter = new OrFilter<>(); + if (includeRegexes != null) { + for (String includeRegex : includeRegexes) { + includeFilter.addFilter(new RegexStringFilter(includeRegex)); + } + } + + OrFilter excludeFilter = new OrFilter<>(); + if (excludeRegexes != null) { + for (String excludeRegex : excludeRegexes) { + excludeFilter.addFilter(new RegexStringFilter(excludeRegex)); + } + } + + return new OrFilter<>(includeFilter, new NotFilter<>(excludeFilter)); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java new file mode 100644 index 0000000000..26cc124571 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical NEGATION of a Filter. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class NotFilter extends net.sourceforge.pmd.util.filter.AbstractDelegateFilter { + public NotFilter() { + super(); + } + + public NotFilter(Filter filter) { + super(filter); + } + + @Override + public boolean filter(T obj) { + return !filter.filter(obj); + } + + @Override + public String toString() { + return "not (" + filter + ")"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java new file mode 100644 index 0000000000..9c57d99a1f --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical OR of a list of Filters. This implementation is short circuiting. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class OrFilter extends AbstractCompoundFilter { + + public OrFilter() { + super(); + } + + public OrFilter(Filter... filters) { + super(filters); + } + + @Override + public boolean filter(T obj) { + boolean match = false; + for (Filter filter : filters) { + if (filter.filter(obj)) { + match = true; + break; + } + } + return match; + } + + @Override + protected String getOperator() { + return "or"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java new file mode 100644 index 0000000000..fd8b933cb4 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java @@ -0,0 +1,98 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * A filter which uses a regular expression to match Strings. Invalid regular + * expressions will match nothing. + *

+ * Because regular expression matching is slow, and a common usage is to match + * some sort of relative file path, the regular expression is checked to see if + * it can be evaluated using much faster calls to + * {@link String#endsWith(String)}. + * @deprecated See {@link Filter} + */ +@Deprecated +public class RegexStringFilter implements Filter { + /** + * Matches regular expressions begin with an optional {@code ^}, then + * {@code .*}, then a literal path, with an optional file extension, and + * finally an optional {@code $} at the end. The {@code .} in the extension + * may or may not be preceded by a {@code \} escape. The literal path + * portion is determine by the absence of any of the following characters: + * \ [ ( . * ? + | { $ + * + * There are two capturing groups in the expression. The first is for the + * literal path. The second is for the file extension, without the escaping. + * The concatenation of these two captures creates the {@link String} which + * can be used with {@link String#endsWith(String)}. + * + * For ease of reference, the non-Java escaped form of this pattern is: + * \^?\.\*([^\\\[\(\.\*\?\+\|\{\$]+)(?:\\?(\.\w+))?\$? + */ + private static final Pattern ENDS_WITH = Pattern + .compile("\\^?\\.\\*([^\\\\\\[\\(\\.\\*\\?\\+\\|\\{\\$]+)(?:\\\\?(\\.\\w+))?\\$?"); + + protected String regex; + protected Pattern pattern; + protected String endsWith; + + public RegexStringFilter(String regex) { + this.regex = regex; + optimize(); + } + + public String getRegex() { + return this.regex; + } + + public String getEndsWith() { + return this.endsWith; + } + + protected void optimize() { + final Matcher matcher = ENDS_WITH.matcher(this.regex); + if (matcher.matches()) { + final String literalPath = matcher.group(1); + final String fileExtension = matcher.group(2); + if (fileExtension != null) { + this.endsWith = literalPath + fileExtension; + } else { + this.endsWith = literalPath; + } + } else { + try { + this.pattern = Pattern.compile(this.regex); + } catch (PatternSyntaxException ignored) { + // If the regular expression is invalid, then pattern will be + // null. + } + } + } + + @Override + public boolean filter(String obj) { + if (this.endsWith != null) { + return obj.endsWith(this.endsWith); + } else if (this.pattern != null) { + return this.pattern.matcher(obj).matches(); + } else { + // The regular expression must have been bad, so it will match + // nothing. + return false; + } + } + + @Override + public String toString() { + return "matches " + this.regex; + } +} diff --git a/pom.xml b/pom.xml index ef6b71625d..81421c67c4 100644 --- a/pom.xml +++ b/pom.xml @@ -1185,6 +1185,18 @@ pmd-dist + + + pmd-compat6-module + + + !skipPmdCompat6 + + + + pmd-compat6 + +