Merge pull request #3260 from oowekyala:deprecate-ThreadSafeReportListener

[core] Deprecate more APIs around PMD's entry point #3260
This commit is contained in:
Andreas Dangel committed 2021-05-27 10:24:04 +02:00
commit b37508964b
14 files changed
+405 -151

No files matched your search

+20 -61
View File
@@ -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<RuleSet> 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<DataSource> 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<RuleSet> ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(",")));
List<DataSource> 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<DataSource> 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<DataSource> files = new ArrayList<>();
final List<DataSource> files = new ArrayList<>();
Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
@Override
+12
View File
@@ -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<ThreadSafeReportListener>) %}) 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)
@@ -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<DataSource> internalGetApplicableFiles(PMDConfiguration configuration,
Set<Language> languages) {
Set<Language> languages) {
LanguageFilenameFilter fileSelector = new LanguageFilenameFilter(languages);
List<DataSource> 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 <code>0</code> means successful execution,
* <code>1</code> means error, <code>4</code> means there have been
* violations found.
* <code>1</code> means error, <code>4</code> 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;
}
}
}
@@ -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}.
*
* <p>There are several aspects to the configuration of PMD.
*
* <p>The aspects related to generic PMD behavior:</p>
* <ul>
@@ -312,6 +312,7 @@ public class Report implements Iterable<RuleViolation> {
* @param listener
* the listener
*/
@Deprecated
public void addListener(ThreadSafeReportListener listener) {
listeners.add(listener);
}
@@ -629,6 +630,10 @@ public class Report implements Iterable<RuleViolation> {
return end - start;
}
/**
* @deprecated {@link ThreadSafeReportListener} is deprecated
*/
@Deprecated
public List<ThreadSafeReportListener> getListeners() {
return listeners;
}
@@ -638,7 +643,10 @@ public class Report implements Iterable<RuleViolation> {
*
* @param allListeners
* the report listeners
*
* @deprecated {@link ThreadSafeReportListener} is deprecated
*/
@Deprecated
public void addListeners(List<ThreadSafeReportListener> allListeners) {
listeners.addAll(allListeners);
}
@@ -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.
@@ -19,8 +19,8 @@ import com.beust.jcommander.ParameterException;
/**
* @author Romain Pelisse &lt;belaran@gmail.com&gt;
*
* @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
@@ -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);
@@ -152,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));
}
@@ -176,4 +186,7 @@ public final class PMDCommandLineInterface {
System.setProperty(STATUS_CODE_PROPERTY, Integer.toString(statusCode));
}
public static void printJcommanderUsageOnConsole() {
new JCommander(new PMDParameters()).usage();
}
}
@@ -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
@@ -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);
}
}
}
@@ -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
@@ -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.
* <p>
* 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()}.
*
* <p>Language instances must be registered with a {@linkplain ServiceLoader service file}
* to be picked up on by the {@link LanguageRegistry}.
*
* <p>The following are key components of a language in PMD:
* <ul>
* <li>Name - Full name of the Language</li>
* <li>Short name - The common short form of the Language</li>
* <li>Terse name - The shortest and simplest possible form of the Language
* name, generally used for Rule configuration</li>
* <li>Extensions - File extensions associated with the Language</li>
* <li>Rule Chain Visitor - The RuleChainVisitor implementation used for this
* Language</li>
* <li>Versions - The LanguageVersions associated with the Language</li>
* <li>Name - Full name of the language</li>
* <li>Short name - The common short form of the language</li>
* <li>Terse name - The shortest and simplest possible form of the language
* name, generally used for rule configuration</li>
* <li>Extensions - File extensions associated with the language</li>
* <li>Versions - The language versions associated with the language</li>
* </ul>
*
* @see LanguageVersion
@@ -31,45 +34,51 @@ public interface Language extends Comparable<Language> {
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<String> 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 <code>true</code> if this Language handles this extension,
* <code>false</code> otherwise.
* @param extensionWithoutDot A file extension (without '.' prefix)
*
* @return <code>true</code> if this language handles the extension,
* <code>false</code> 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<Language> {
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<LanguageVersion> 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, <code>null</code> 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();
@@ -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<String, Language> languages;
private final Map<String, Language> languages;
private LanguageRegistry() {
List<Language> languagesList = new ArrayList<>();
@@ -70,9 +71,13 @@ public final class LanguageRegistry {
*/
@Deprecated
public static LanguageRegistry getInstance() {
return instance;
return INSTANCE;
}
/**
* Returns a collection of all the known languages. The ordering of this
* collection is undefined.
*/
public static Collection<Language> getLanguages() {
// Filter out languages, that are not fully supported by PMD yet.
// Those languages should not have a LanguageModule then, but they have it.
@@ -95,10 +100,25 @@ 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);
}
/**
* 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) {
@@ -110,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)) {
@@ -144,10 +172,15 @@ public final class LanguageRegistry {
return null;
}
public static List<Language> findByExtension(String extension) {
/**
* Returns all languages that support the given extension.
*
* @param extensionWithoutDot A file extension (without '.' prefix)
*/
public static List<Language> findByExtension(String extensionWithoutDot) {
List<Language> languages = new ArrayList<>();
for (Language language : getInstance().languages.values()) {
if (language.hasExtension(extension)) {
if (language.hasExtension(extensionWithoutDot)) {
languages.add(language);
}
}
@@ -4,8 +4,30 @@
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)}).
*
* <p>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 <rule>} element), and not
* overridden.
*
* <p>Example usage:
* <pre>
* 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
* </pre>
*/
public class LanguageVersion implements Comparable<LanguageVersion> {
@@ -13,27 +35,44 @@ public class LanguageVersion implements Comparable<LanguageVersion> {
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;
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.
*/
@@ -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());