From 0562ff7dac1034f47e2d0e0a86ae6b7ca01082f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 6 Apr 2021 15:45:34 +0200 Subject: [PATCH 1/6] Deprecate ThreadSafeReportListener --- pmd-core/src/main/java/net/sourceforge/pmd/Report.java | 8 ++++++++ .../net/sourceforge/pmd/ThreadSafeReportListener.java | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java index 8bafa2ccc5..c2ad0e2362 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java @@ -312,6 +312,7 @@ public class Report implements Iterable { * @param listener * the listener */ + @Deprecated public void addListener(ThreadSafeReportListener listener) { listeners.add(listener); } @@ -629,6 +630,10 @@ public class Report implements Iterable { return end - start; } + /** + * @deprecated {@link ThreadSafeReportListener} is deprecated + */ + @Deprecated public List getListeners() { return listeners; } @@ -638,7 +643,10 @@ public class Report implements Iterable { * * @param allListeners * the report listeners + * + * @deprecated {@link ThreadSafeReportListener} is deprecated */ + @Deprecated public void addListeners(List allListeners) { listeners.addAll(allListeners); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ThreadSafeReportListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/ThreadSafeReportListener.java index f2dd696575..39eff39f41 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/ThreadSafeReportListener.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/ThreadSafeReportListener.java @@ -12,7 +12,11 @@ import net.sourceforge.pmd.stat.Metric; * * Thread-safety is required only for concurrently notifying about different files. * Same file violations are guaranteed to be reported serially. + * + * @deprecated All entry points of PMD that allowed usage of this are now deprecated. + * This will be replaced by another TBD mechanism in PMD 7. */ +@Deprecated public interface ThreadSafeReportListener { /** * A new violation has been found. From 6216156a9c9b7fb12c7f799f60c5833159bdd900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 6 Apr 2021 15:53:44 +0200 Subject: [PATCH 2/6] Doc for LanguageVersion, deprecate constructor as internal --- .../pmd/lang/LanguageRegistry.java | 8 ++++++ .../sourceforge/pmd/lang/LanguageVersion.java | 27 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java index 21b0e8fb07..22eaf5e593 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java @@ -95,6 +95,14 @@ public final class LanguageRegistry { return languages; } + /** + * Returns a language from its {@linkplain Language#getName() full name} + * (eg {@code "Java"}). This is case sensitive. + * + * @param languageName Language name + * + * @return A language, or null if the name is unknown + */ public static Language getLanguage(String languageName) { return getInstance().languages.get(languageName); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java index 2da37bc700..08480dbd1d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java @@ -4,8 +4,27 @@ package net.sourceforge.pmd.lang; +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.annotation.InternalApi; + /** - * Created by christoferdutz on 21.09.14. + * Represents a version of a {@link Language}. Language instances provide + * a list of supported versions ({@link Language#getVersions()}). Individual + * versions can be retrieved from their version number ({@link Language#getVersion(String)}). + * + *

Versions are used to limit some rules to operate on only a version range. + * For instance, a rule that suggests eliding local variable types in Java + * (replacing them with {@code var}) makes no sense if the codebase is not + * using Java 10 or later. This is determined by {@link Rule#getMinimumLanguageVersion()} + * and {@link Rule#getMaximumLanguageVersion()}. These should be set in the + * ruleset XML (they're attributes of the {@code } element), and not + * overridden. + * + *

Example usage: + *

+ * Language javaLanguage = LanguageRegistry.{@link LanguageRegistry#getLanguage(String) getLanguage}("Java");
+ * LanguageVersion java11 = javaLanguage.{@link Language#getVersion(String) getVersion}("11");
+ * 
*/ public class LanguageVersion implements Comparable { @@ -13,6 +32,12 @@ public class LanguageVersion implements Comparable { private final String version; private final LanguageVersionHandler languageVersionHandler; + /** + * @deprecated Use {@link Language#getVersion(String)}. This is only + * supposed to be used when initializing a {@link Language} instance. + */ + @Deprecated + @InternalApi public LanguageVersion(Language language, String version, LanguageVersionHandler languageVersionHandler) { this.language = language; this.version = version; From 48ad0d35f4320c73d3961bc919f77a348e05a1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 6 Apr 2021 16:03:21 +0200 Subject: [PATCH 3/6] More doc --- .../pmd/lang/BaseLanguageModule.java | 4 +- .../net/sourceforge/pmd/lang/Language.java | 98 +++++++++++-------- .../pmd/lang/LanguageRegistry.java | 35 ++++++- .../sourceforge/pmd/lang/LanguageVersion.java | 18 +++- 4 files changed, 106 insertions(+), 49 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index 9426b86237..4fa5061b9c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -145,8 +145,8 @@ public abstract class BaseLanguageModule implements Language { } @Override - public boolean hasExtension(String extension) { - return extensions != null && extensions.contains(extension); + public boolean hasExtension(String extensionWithoutDot) { + return extensions != null && extensions.contains(extensionWithoutDot); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java index fa82517522..f8d53e6cd5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java @@ -5,22 +5,25 @@ package net.sourceforge.pmd.lang; import java.util.List; +import java.util.ServiceLoader; /** - * Interface each Language implementation has to implement. It is used by the - * LanguageRegistry to access constants and implementation classes in order to - * provide support for the language. - *

- * The following are key components of a Language in PMD: + * Represents a language module, and provides access to language-specific + * functionality. You can get a language instance from {@link LanguageRegistry}. + * Using a language involves first selecting the relevant {@link LanguageVersion} + * for the sources, and accessing implemented services through {@link LanguageVersion#getLanguageVersionHandler()}. + * + *

Language instances must be registered with a {@linkplain ServiceLoader service file} + * to be picked up on by the {@link LanguageRegistry}. + * + *

The following are key components of a language in PMD: *

    - *
  • Name - Full name of the Language
  • - *
  • Short name - The common short form of the Language
  • - *
  • Terse name - The shortest and simplest possible form of the Language - * name, generally used for Rule configuration
  • - *
  • Extensions - File extensions associated with the Language
  • - *
  • Rule Chain Visitor - The RuleChainVisitor implementation used for this - * Language
  • - *
  • Versions - The LanguageVersions associated with the Language
  • + *
  • Name - Full name of the language
  • + *
  • Short name - The common short form of the language
  • + *
  • Terse name - The shortest and simplest possible form of the language + * name, generally used for rule configuration
  • + *
  • Extensions - File extensions associated with the language
  • + *
  • Versions - The language versions associated with the language
  • *
* * @see LanguageVersion @@ -31,45 +34,51 @@ public interface Language extends Comparable { String LANGUAGE_MODULES_CLASS_NAMES_PROPERTY = "languageModulesClassNames"; /** - * Get the full name of this Language. This is generally the name of this - * Language without the use of acronyms. + * Returns the full name of this Language. This is generally the name of this + * language without the use of acronyms, but possibly some capital letters, + * eg {@code "Java"}. It's suitable for displaying in a GUI. * - * @return The full name of this Language. + * @return The full name of this language. */ String getName(); /** - * Get the short name of this Language. This is the commonly used short form - * of this Language's name, perhaps an acronym. + * Returns the short name of this language. This is the commonly + * used short form of this language's name, perhaps an acronym, + * but possibly with special characters. * - * @return The short name of this Language. + * @return The short name of this language. */ String getShortName(); /** - * Get the terse name of this Language. This is used for Rule configuration. + * Returns the terse name of this language. This is a short, alphanumeric, + * lowercase name, eg {@code "java"}. It's used to identify the language + * in the ruleset XML, and is also in the package name of the language + * module. * - * @return The terse name of this Language. + * @return The terse name of this language. */ String getTerseName(); /** - * Get the list of file extensions associated with this Language. + * Returns the list of file extensions associated with this language. + * This list is unmodifiable. Extensions do not have a '.' prefix. * - * @return List of file extensions. + * @return A list of file extensions. */ List getExtensions(); /** - * Returns whether the given Language handles the given file extension. The - * comparison is done ignoring case. + * Returns whether this language handles the given file extension. + * The comparison is done ignoring case. * - * @param extension - * A file extension. - * @return true if this Language handles this extension, - * false otherwise. + * @param extensionWithoutDot A file extension (without '.' prefix) + * + * @return true if this language handles the extension, + * false otherwise. */ - boolean hasExtension(String extension); + boolean hasExtension(String extensionWithoutDot); /** * Get the RuleChainVisitor implementation class used when visiting the AST @@ -84,30 +93,39 @@ public interface Language extends Comparable { Class getRuleChainVisitorClass(); /** - * Gets the list of supported LanguageVersion for this Language. + * Returns an ordered list of supported versions for this language. * - * @return The LanguageVersion for this Language. + * @return All supported language versions. */ List getVersions(); + /** + * Returns true if a language version with the given {@linkplain LanguageVersion#getVersion() version string} + * is registered. Then, {@link #getVersion(String) getVersion} will return a non-null value. + * + * @param version A version string + * + * @return True if the version string is known + */ boolean hasVersion(String version); /** - * Get the LanguageVersion for the version string from this Language. + * Returns the language version with the given {@linkplain LanguageVersion#getVersion() version string}. + * Returns null if no such version exists. * - * @param version - * The language version string. - * @return The corresponding LanguageVersion, null if the - * version string is not recognized. + * @param version A language version string. + * + * @return The corresponding LanguageVersion, {@code null} if the + * version string is not recognized. */ LanguageVersion getVersion(String version); /** - * Get the current PMD defined default LanguageVersion for this Language. + * Returns the default language version for this language. * This is an arbitrary choice made by the PMD product, and can change - * between PMD releases. Every Language has a default version. + * between PMD releases. Every language has a default version. * - * @return The current default LanguageVersion for this Language. + * @return The current default language version for this language. */ LanguageVersion getDefaultVersion(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java index 22eaf5e593..496b626dd8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java @@ -18,13 +18,14 @@ import java.util.ServiceLoader; import org.apache.commons.lang3.StringUtils; /** - * Created by christoferdutz on 20.09.14. + * Provides access to the registered PMD languages. These are found + * from the classpath of the {@link ClassLoader} of this class. */ public final class LanguageRegistry { - private static LanguageRegistry instance = new LanguageRegistry(); + private static final LanguageRegistry instance = new LanguageRegistry(); - private Map languages; + private final Map languages; private LanguageRegistry() { List languagesList = new ArrayList<>(); @@ -73,6 +74,10 @@ public final class LanguageRegistry { return instance; } + /** + * Returns a collection of all the known languages. The ordering of this + * collection is undefined. + */ public static Collection getLanguages() { // Filter out languages, that are not fully supported by PMD yet. // Those languages should not have a LanguageModule then, but they have it. @@ -107,6 +112,13 @@ public final class LanguageRegistry { return getInstance().languages.get(languageName); } + /** + * Returns a "default language" known to the service loader. This + * is the Java language if available, otherwise an arbitrary one. + * If no languages are loaded, returns null. + * + * @return A language, or null if the name is unknown + */ public static Language getDefaultLanguage() { Language defaultLanguage = getLanguage("Java"); if (defaultLanguage == null) { @@ -118,6 +130,14 @@ public final class LanguageRegistry { return defaultLanguage; } + /** + * Returns a language from its {@linkplain Language#getTerseName() terse name} + * (eg {@code "java"}). This is case sensitive. + * + * @param terseName Language terse name + * + * @return A language, or null if the name is unknown + */ public static Language findLanguageByTerseName(String terseName) { for (Language language : getInstance().languages.values()) { if (language.getTerseName().equals(terseName)) { @@ -152,10 +172,15 @@ public final class LanguageRegistry { return null; } - public static List findByExtension(String extension) { + /** + * Returns all languages that support the given extension. + * + * @param extensionWithoutDot A file extension (without '.' prefix) + */ + public static List findByExtension(String extensionWithoutDot) { List languages = new ArrayList<>(); for (Language language : getInstance().languages.values()) { - if (language.hasExtension(extension)) { + if (language.hasExtension(extensionWithoutDot)) { languages.add(language); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java index 08480dbd1d..dad6165693 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java @@ -24,6 +24,9 @@ import net.sourceforge.pmd.annotation.InternalApi; *
  * Language javaLanguage = LanguageRegistry.{@link LanguageRegistry#getLanguage(String) getLanguage}("Java");
  * LanguageVersion java11 = javaLanguage.{@link Language#getVersion(String) getVersion}("11");
+ * LanguageVersionHandler handler = java11.getLanguageVersionHandler();
+ * Parser parser = handler.getParser(handler.getDefaultParserOptions());
+ * // use parser
  * 
*/ public class LanguageVersion implements Comparable { @@ -44,21 +47,32 @@ public class LanguageVersion implements Comparable { this.languageVersionHandler = languageVersionHandler; } + /** + * Returns the language that owns this version. + */ public Language getLanguage() { return language; } + /** + * Returns the version string. This is usually a version number, e.g. + * {@code "1.7"} or {@code "11"}. This is used by {@link Language#getVersion(String)}. + */ public String getVersion() { return version; } + /** + * Returns the {@link LanguageVersionHandler}, which provides access + * to version-specific services, like the parser. + */ public LanguageVersionHandler getLanguageVersionHandler() { return languageVersionHandler; } /** - * Get the name of this LanguageVersion. This is Language name appended with - * the LanguageVersion version if not an empty String. + * Returns the name of this language version. This is the version string + * prefixed with the {@linkplain Language#getName() language name}. * * @return The name of this LanguageVersion. */ From 0669154d1f7c601914b2a068c765ea53b5d7955d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 6 Apr 2021 16:35:59 +0200 Subject: [PATCH 4/6] Add API to parse parameters --- .../net/sourceforge/pmd/PMDConfiguration.java | 8 +- .../pmd/cli/PMDCommandLineInterface.java | 6 ++ .../pmd/cli/PmdParametersParseResult.java | 88 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index 694519e344..7fae6012bb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -13,6 +13,7 @@ import java.util.Properties; import net.sourceforge.pmd.cache.AnalysisCache; import net.sourceforge.pmd.cache.FileAnalysisCache; import net.sourceforge.pmd.cache.NoopAnalysisCache; +import net.sourceforge.pmd.cli.PmdParametersParseResult; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; @@ -21,8 +22,11 @@ import net.sourceforge.pmd.renderers.RendererFactory; import net.sourceforge.pmd.util.ClasspathClassLoader; /** - * This class contains the details for the runtime configuration of PMD. There - * are several aspects to the configuration of PMD. + * This class contains the details for the runtime configuration of a PMD run. + * You can either create one and set individual fields, or mimic a CLI run by + * using {@link PmdParametersParseResult#extractParameters(String...) extractParameters}. + * + *

There are several aspects to the configuration of PMD. * *

The aspects related to generic PMD behavior:

*
    diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java index a8ad35ed4a..3c659b69ed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java @@ -37,6 +37,12 @@ public final class PMDCommandLineInterface { private PMDCommandLineInterface() { } + /** + * Note: this may terminate the VM. + * + * @deprecated Use {@link PmdParametersParseResult#extractParameters(String...)} + */ + @Deprecated public static PMDParameters extractParameters(PMDParameters arguments, String[] args, String progName) { JCommander jcommander = new JCommander(arguments); jcommander.setProgramName(progName); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java new file mode 100644 index 0000000000..d6f8fe3aef --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java @@ -0,0 +1,88 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli; + +import java.util.Objects; + +import net.sourceforge.pmd.PMDConfiguration; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; + +/** + * Result of parsing a bunch of CLI arguments. Parsing may fail with an + * exception, or succeed and produce a {@link PMDConfiguration}. If the + * {@code --help} argument is mentioned, no configuration is produced. + */ +public final class PmdParametersParseResult { + + private final PMDParameters result; + private final ParameterException error; + + PmdParametersParseResult(PMDParameters result) { + this.result = Objects.requireNonNull(result); + this.error = null; + } + + PmdParametersParseResult(ParameterException error) { + this.result = null; + this.error = Objects.requireNonNull(error); + } + + /** Returns true if parsing failed. */ + public boolean isError() { + return result == null; + } + + /** + * Returns whether parsing just requested the {@code --help} text. + * In this case no configuration is produced. + */ + public boolean isHelp() { + return !isError() && result.isHelp(); + } + + /** + * Returns the error if parsing failed. Parsing may fail if required + * parameters are not provided, or if some parameters don't pass validation. + * Otherwise returns null. + */ + public ParameterException getError() { + return error; + } + + /** + * Returns the resulting configuration if parsing succeeded and not {@link #isHelp(). + * Otherwise returns null. + */ + public PMDConfiguration toConfiguration() { + return result != null && !isHelp() ? result.toConfiguration() : null; + } + + /** + * Parse an array of CLI parameters and returns a result (which may be failed). + * Use this instead of {@link PMDCommandLineInterface#extractParameters(PMDParameters, String[], String)}, + * because that one may terminate the VM. + * + * @param args Array of parameters + * + * @return A parse result + * + * @throws NullPointerException If the parameter array is null + */ + public static PmdParametersParseResult extractParameters(String... args) { + Objects.requireNonNull(args, "Null parameter array"); + PMDParameters result = new PMDParameters(); + JCommander jcommander = new JCommander(result); + jcommander.setProgramName("pmd"); + + try { + jcommander.parse(args); + return new PmdParametersParseResult(result); + } catch (ParameterException e) { + return new PmdParametersParseResult(e); + } + } +} From edf5068019101e3928302103389b8a58e756c81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 6 Apr 2021 17:09:49 +0200 Subject: [PATCH 5/6] Add StatusCode enum --- .../main/java/net/sourceforge/pmd/PMD.java | 123 ++++++++++++++---- .../pmd/cli/PMDCommandLineInterface.java | 11 +- .../sourceforge/pmd/cli/PMDParameters.java | 2 +- .../pmd/lang/LanguageRegistry.java | 4 +- .../pmd/coverage/PMDCoverageTest.java | 21 ++- 5 files changed, 122 insertions(+), 39 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index 88e66e2d53..633847ede7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -31,7 +31,7 @@ import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; import net.sourceforge.pmd.cache.NoopAnalysisCache; import net.sourceforge.pmd.cli.PMDCommandLineInterface; -import net.sourceforge.pmd.cli.PMDParameters; +import net.sourceforge.pmd.cli.PmdParametersParseResult; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageFilenameFilter; import net.sourceforge.pmd.lang.LanguageVersion; @@ -213,10 +213,13 @@ public class PMD { /** * This method is the main entry point for command line usage. * - * @param configuration - * the configure to use + * @param configuration the configure to use + * * @return number of violations found. + * + * @deprecated Use {@link #runPmd(PMDConfiguration)}. */ + @Deprecated public static int doPMD(PMDConfiguration configuration) { // Load the RuleSets @@ -263,7 +266,7 @@ public class PMD { * Make sure it's our own classloader before attempting to close it.... * Maven + Jacoco provide us with a cloaseable classloader that if closed * will throw a ClassNotFoundException. - */ + */ if (configuration.getClassLoader() instanceof ClasspathClassLoader) { IOUtil.tryCloseClassLoader(configuration.getClassLoader()); } @@ -413,7 +416,7 @@ public class PMD { } private static List internalGetApplicableFiles(PMDConfiguration configuration, - Set languages) { + Set languages) { LanguageFilenameFilter fileSelector = new LanguageFilenameFilter(languages); List files = new ArrayList<>(); @@ -490,59 +493,100 @@ public class PMD { } /** - * Entry to invoke PMD as command line tool. Note that this will invoke {@link System#exit(int)}. + * Entry to invoke PMD as command line tool. Note that this will + * invoke {@link System#exit(int)}. * - * @param args - * command line arguments + * @param args command line arguments */ public static void main(String[] args) { - PMDCommandLineInterface.run(args); + StatusCode exitCode = runPmd(args); + PMDCommandLineInterface.setStatusCodeOrExit(exitCode.toInt()); } /** * Parses the command line arguments and executes PMD. Returns the * exit code without exiting the VM. * - * @param args - * command line arguments + * @param args command line arguments + * * @return the exit code, where 0 means successful execution, - * 1 means error, 4 means there have been - * violations found. + * 1 means error, 4 means there have been + * violations found. + * + * @deprecated Use {@link #runPmd(String...)}. */ + @Deprecated public static int run(String[] args) { - final PMDParameters params = PMDCommandLineInterface.extractParameters(new PMDParameters(), args, "pmd"); + return runPmd(args).toInt(); + } - if (params.isBenchmark()) { + /** + * Parses the command line arguments and executes PMD. Returns the + * status code without exiting the VM. Note that the arguments parsing + * may itself fail and produce a {@link StatusCode#ERROR}. This will + * print the error message to standard error. + * + * @param args command line arguments + * + * @return the status code. Note that {@link PMDConfiguration#isFailOnViolation()} + * (flag {@code --failOnViolation}) may turn an {@link StatusCode#OK} into a {@link + * StatusCode#VIOLATIONS_FOUND}. + */ + public static StatusCode runPmd(String... args) { + PmdParametersParseResult parseResult = PmdParametersParseResult.extractParameters(args); + if (parseResult.isHelp()) { + PMDCommandLineInterface.printJcommanderUsageOnConsole(); + System.out.println(PMDCommandLineInterface.buildUsageText()); + return StatusCode.OK; + } else if (parseResult.isError()) { + System.out.println(PMDCommandLineInterface.buildUsageText()); + System.err.println(parseResult.getError().getMessage()); + return StatusCode.ERROR; + } + return runPmd(parseResult.toConfiguration()); + } + + /** + * Execute PMD from a configuration. Returns the status code without + * exiting the VM. This is the main entry point to run a full PMD run + * with a manually created configuration. + * + * @param configuration Configuration to run + * + * @return the status code. Note that {@link PMDConfiguration#isFailOnViolation()} + * (flag {@code --failOnViolation}) may turn an {@link StatusCode#OK} into a {@link + * StatusCode#VIOLATIONS_FOUND}. + */ + public static StatusCode runPmd(PMDConfiguration configuration) { + if (configuration.isBenchmark()) { TimeTracker.startGlobalTracking(); } - int status = PMDCommandLineInterface.NO_ERRORS_STATUS; - final PMDConfiguration configuration = params.toConfiguration(); - - final Level logLevel = params.isDebug() ? Level.FINER : Level.INFO; + final Level logLevel = configuration.isDebug() ? Level.FINER : Level.INFO; final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, new ConsoleHandler()); final Level oldLogLevel = LOG.getLevel(); // Need to do this, since the static logger has already been initialized // at this point LOG.setLevel(logLevel); + StatusCode status; try { int violations = PMD.doPMD(configuration); if (violations > 0 && configuration.isFailOnViolation()) { - status = PMDCommandLineInterface.VIOLATIONS_FOUND; + status = StatusCode.VIOLATIONS_FOUND; } else { - status = PMDCommandLineInterface.NO_ERRORS_STATUS; + status = StatusCode.OK; } } catch (Exception e) { System.out.println(PMDCommandLineInterface.buildUsageText()); System.out.println(); System.err.println(e.getMessage()); - status = PMDCommandLineInterface.ERROR_STATUS; + status = StatusCode.ERROR; } finally { logHandlerManager.close(); LOG.setLevel(oldLogLevel); - if (params.isBenchmark()) { + if (configuration.isBenchmark()) { final TimingReport timingReport = TimeTracker.stopGlobalTracking(); // TODO get specified report format from config @@ -559,4 +603,37 @@ public class PMD { } return status; } + + /** + * Represents status codes that are used as exit codes during CLI runs. + * + * @see #runPmd(String[]) + */ + public enum StatusCode { + /** No errors, no violations. This is exit code {@code 0}. */ + OK(0), + /** + * Errors were detected, PMD may have not run to the end. + * This is exit code {@code 1}. + */ + ERROR(1), + /** + * No errors, but PMD found violations. This is exit code {@code 4}. + * This is only returned if {@link PMDConfiguration#isFailOnViolation()} + * is set (CLI flag {@code --failOnViolation}). + */ + VIOLATIONS_FOUND(4); + + private final int code; + + StatusCode(int code) { + this.code = code; + } + + /** Returns the exit code as used in CLI. */ + public int toInt() { + return this.code; + } + + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java index 3c659b69ed..08b7641f5e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java @@ -19,8 +19,8 @@ import com.beust.jcommander.ParameterException; /** * @author Romain Pelisse <belaran@gmail.com> - * - * @deprecated Internal API. Use {@link PMD#run(String[])} or {@link PMD#main(String[])} + * @deprecated Internal API. Use {@link PMD#runPmd(String...)} or {@link PMD#main(String[])}, + * or {@link PmdParametersParseResult} if you just want to produce a configuration. */ @Deprecated @InternalApi @@ -158,6 +158,10 @@ public final class PMDCommandLineInterface { return buf.toString(); } + /** + * @deprecated Use {@link PMD#main(String[])} + */ + @Deprecated public static void run(String[] args) { setStatusCodeOrExit(PMD.run(args)); } @@ -182,4 +186,7 @@ public final class PMDCommandLineInterface { System.setProperty(STATUS_CODE_PROPERTY, Integer.toString(statusCode)); } + public static void printJcommanderUsageOnConsole() { + new JCommander(new PMDParameters()).usage(); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java index 94cd430553..ed4ecf37a8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java @@ -23,7 +23,7 @@ import com.beust.jcommander.ParameterException; import com.beust.jcommander.validators.PositiveInteger; /** - * @deprecated Internal API. Use {@link PMD#run(String[])} or {@link PMD#main(String[])} + * @deprecated Internal API. Use {@link PMD#runPmd(String[])} or {@link PMD#main(String[])} */ @Deprecated @InternalApi diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java index 496b626dd8..358f6b62ca 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java @@ -23,7 +23,7 @@ import org.apache.commons.lang3.StringUtils; */ public final class LanguageRegistry { - private static final LanguageRegistry instance = new LanguageRegistry(); + private static final LanguageRegistry INSTANCE = new LanguageRegistry(); private final Map languages; @@ -71,7 +71,7 @@ public final class LanguageRegistry { */ @Deprecated public static LanguageRegistry getInstance() { - return instance; + return INSTANCE; } /** diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java index 71609c599d..895622d698 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Rule; import org.junit.Test; @@ -47,21 +48,19 @@ public class PMDCoverageTest { * @param commandLine */ private void runPmd(String commandLine) { - String[] args; - args = commandLine.split("\\s"); + String[] args = commandLine.split("\\s"); try { File f = folder.newFile(); - int n = args.length; - String[] a = new String[n + 2 + 2]; - System.arraycopy(args, 0, a, 0, n); - a[n] = "-reportfile"; - a[n + 1] = f.getAbsolutePath(); - a[n + 2] = "-threads"; - a[n + 3] = String.valueOf(Runtime.getRuntime().availableProcessors()); - args = a; + args = ArrayUtils.addAll( + args, + "-reportfile", + f.getAbsolutePath(), + "-threads", + String.valueOf(Runtime.getRuntime().availableProcessors()) + ); - PMD.run(args); + PMD.runPmd(args); assertEquals("Nothing should be output to stdout", 0, output.getLog().length()); From 694a325f006ade6284b208e6b9b429eacdfa11b0 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 27 May 2021 10:23:54 +0200 Subject: [PATCH 6/6] [doc] Update release notes, update java-api (#3196, #3260) --- docs/pages/pmd/userdocs/tools/java-api.md | 81 ++++++----------------- docs/pages/release_notes.md | 12 ++++ 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/docs/pages/pmd/userdocs/tools/java-api.md b/docs/pages/pmd/userdocs/tools/java-api.md index 32cdc1d88c..3cc500f45e 100644 --- a/docs/pages/pmd/userdocs/tools/java-api.md +++ b/docs/pages/pmd/userdocs/tools/java-api.md @@ -70,7 +70,7 @@ public class PmdExample { configuration.setReportFormat("xml"); configuration.setReportFile("/home/workspace/pmd-report.xml"); - PMD.doPMD(configuration); + PMD.runPMD(configuration); } } ``` @@ -78,7 +78,7 @@ public class PmdExample { ## Programmatically, variant 2 This gives you more control over which files are processed, but is also more complicated. -You can also provide your own listeners and renderers. +You can also provide your own custom renderers. 1. First we create a `PMDConfiguration`. This is currently the only way to specify a ruleset: @@ -96,11 +96,12 @@ You can also provide your own listeners and renderers. configuration.prependClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar"); ``` -3. Then we need a ruleset factory. This is created using the configuration, taking the minimum priority into +3. Then we need to load the rulesets. This is done by using the configuration, taking the minimum priority into account: ```java - RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.createFactory(configuration); + RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration); + List ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(","))); ``` 4. PMD operates on a list of `DataSource`. You can assemble a own list of `FileDataSource`, e.g. @@ -109,7 +110,8 @@ You can also provide your own listeners and renderers. List files = Arrays.asList(new FileDataSource(new File("/path/to/src/MyClass.java"))); ``` -5. For reporting, you can use a built-in renderer, e.g. `XMLRenderer`. Note, that you must manually initialize +5. For reporting, you can use a built-in renderer, e.g. `XMLRenderer` or a custom renderer implementing + `Renderer`. Note, that you must manually initialize the renderer by setting a suitable `Writer` and calling `start()`. After the PMD run, you need to call `end()` and `flush()`. Then your writer should have received all output. @@ -120,36 +122,15 @@ You can also provide your own listeners and renderers. xmlRenderer.start(); ``` -6. Create a `RuleContext`. This is the context instance, that is available then in the rule implementations. - Note: when running in multi-threaded mode (which is the default), the rule context instance is cloned for - each thread. - - ```java - RuleContext ctx = new RuleContext(); - ``` - -7. Optionally register a report listener. This allows you to react immediately on found violations. You could also - use such a listener to implement your own renderer. The listener must implement the interface - `ThreadSafeReportListener` and can be registered via `ctx.getReport().addListener(...)`. - - ```java - ctx.getReport().addListener(new ThreadSafeReportListener() { - public void ruleViolationAdded(RuleViolation ruleViolation) { - } - public void metricAdded(Metric metric) { - } - ``` - -8. Now, all the preparations are done, and PMD can be executed. This is done by calling - `PMD.processFiles(...)`. This method call takes the configuration, the ruleset factory, the files - to process, the rule context and the list of renderers. Provide an empty list, if you don't want to use +6. Now, all the preparations are done, and PMD can be executed. This is done by calling + `PMD.processFiles(...)`. This method call takes the configuration, the rulesets, the files + to process, and the list of renderers. Provide an empty list, if you don't want to use any renderer. Note: The auxclasspath needs to be closed explicitly. Otherwise the class or jar files may remain open and file resources are leaked. ```java try { - PMD.processFiles(configuration, ruleSetFactory, files, ctx, - Collections.singletonList(renderer)); + PMD.processFiles(configuration, ruleSets, files, Collections.singletonList(renderer)); } finally { ClassLoader auxiliaryClassLoader = configuration.getClassLoader(); if (auxiliaryClassLoader instanceof ClasspathClassLoader) { @@ -158,7 +139,7 @@ You can also provide your own listeners and renderers. } ``` -9. After the call, you need to finish the renderer via `end()` and `flush()`. +7. After the call, you need to finish the renderer via `end()` and `flush()`. Then you can check the rendered output. ``` java @@ -182,20 +163,17 @@ import java.nio.file.PathMatcher; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RulePriority; -import net.sourceforge.pmd.RuleSetFactory; -import net.sourceforge.pmd.RuleViolation; -import net.sourceforge.pmd.RulesetsFactoryUtils; -import net.sourceforge.pmd.ThreadSafeReportListener; +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.RuleSetLoader; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.renderers.XMLRenderer; -import net.sourceforge.pmd.stat.Metric; import net.sourceforge.pmd.util.ClasspathClassLoader; import net.sourceforge.pmd.util.datasource.DataSource; import net.sourceforge.pmd.util.datasource.FileDataSource; @@ -207,7 +185,8 @@ public class PmdExample2 { configuration.setMinimumPriority(RulePriority.MEDIUM); configuration.setRuleSets("rulesets/java/quickstart.xml"); configuration.prependClasspath("/home/workspace/target/classes"); - RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.createFactory(configuration); + RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration); + List ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(","))); List files = determineFiles("/home/workspace/src/main/java/code"); @@ -215,13 +194,8 @@ public class PmdExample2 { Renderer renderer = createRenderer(rendererOutput); renderer.start(); - RuleContext ctx = new RuleContext(); - - ctx.getReport().addListener(createReportListener()); // alternative way to collect violations - try { - PMD.processFiles(configuration, ruleSetFactory, files, ctx, - Collections.singletonList(renderer)); + PMD.processFiles(configuration, ruleSets, files, Collections.singletonList(renderer)); } finally { ClassLoader auxiliaryClassLoader = configuration.getClassLoader(); if (auxiliaryClassLoader instanceof ClasspathClassLoader) { @@ -235,21 +209,6 @@ public class PmdExample2 { System.out.println(rendererOutput.toString()); } - private static ThreadSafeReportListener createReportListener() { - return new ThreadSafeReportListener() { - @Override - public void ruleViolationAdded(RuleViolation ruleViolation) { - System.out.printf("%-20s:%d %s%n", ruleViolation.getFilename(), - ruleViolation.getBeginLine(), ruleViolation.getDescription()); - } - - @Override - public void metricAdded(Metric metric) { - // ignored - } - }; - } - private static Renderer createRenderer(Writer writer) { XMLRenderer xml = new XMLRenderer("UTF-8"); xml.setWriter(writer); @@ -258,9 +217,9 @@ public class PmdExample2 { private static List determineFiles(String basePath) throws IOException { Path dirPath = FileSystems.getDefault().getPath(basePath); - PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java"); + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java"); - List files = new ArrayList<>(); + final List files = new ArrayList<>(); Files.walkFileTree(dirPath, new SimpleFileVisitor() { @Override diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index c51e42370d..cc58a3ffa3 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -78,6 +78,7 @@ to analyze JavaScript code. Note that PMD core still only requires Java 7. * [#3243](https://github.com/pmd/pmd/pull/3243): \[apex] Correct findBoundary when traversing AST * core * [#2639](https://github.com/pmd/pmd/issues/2639): \[core] PMD CLI output file is not created if directory or directories in path don't exist + * [#3196](https://github.com/pmd/pmd/issues/3196): \[core] Deprecate ThreadSafeReportListener * doc * [#3230](https://github.com/pmd/pmd/issues/3230): \[doc] Remove "Edit me" button for language index pages * dist @@ -115,6 +116,17 @@ to analyze JavaScript code. Note that PMD core still only requires Java 7. ### API Changes +#### Deprecated API + +* {% jdoc !!core::PMD#doPMD(PMDConfiguration) %} is deprecated. + Use {% jdoc !!core::PMD#runPMD(PMDConfiguration) %} instead. +* {% jdoc !!core::PMD#run(String[]) %} is deprecated. + Use {% jdoc !!core::PMD#runPMD(String...) %} instead. +* {% jdoc core::ThreadSafeReportListener %} and the methods to use them in {% jdoc core::Report %} + ({% jdoc core::Report#addListener(ThreadSafeReportListener) %}, {% jdoc core::Report#getListeners() %}, + {% jdoc core::Report#addListeners(List) %}) are deprecated. This functionality + will be replaced by another TBD mechanism in PMD 7. + ### External Contributions * [#3272](https://github.com/pmd/pmd/pull/3272): \[apex] correction for ApexUnitTestMethodShouldHaveIsTestAnnotation false positives - [William Brockhus](https://github.com/YodaDaCoda) * [#3246](https://github.com/pmd/pmd/pull/3246): \[java] New Rule: MutableStaticState - [Vsevolod Zholobov](https://github.com/vszholobov)