Merge pull request #3785 from oowekyala:pmd6-file-collector

[core] Add file collector and new programmatic API for PMD #3785
This commit is contained in:
Andreas Dangel
2022-03-03 17:10:52 +01:00
34 changed files with 2078 additions and 404 deletions

View File

@ -70,7 +70,7 @@ public class PmdExample {
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
PMD.runPMD(configuration);
PMD.runPmd(configuration);
}
}
```
@ -80,7 +80,7 @@ public class PmdExample {
This gives you more control over which files are processed, but is also more complicated.
You can also provide your own custom renderers.
1. First we create a `PMDConfiguration`. This is currently the only way to specify a ruleset:
1. First we create a `PMDConfiguration` and configure it, first the rules:
```java
PMDConfiguration configuration = new PMDConfiguration();
@ -88,63 +88,69 @@ You can also provide your own custom renderers.
configuration.setRuleSets("rulesets/java/quickstart.xml");
```
2. In order to support type resolution, PMD needs to have access to the compiled classes and dependencies
as well. This is called "auxclasspath" and is also configured here.
2. Then we configure, which paths to analyze:
```java
configuration.setInputPaths("/home/workspace/src/main/java/code");
```
3. The we configure the default language version for Java. And in order to support type resolution,
PMD needs to have access to the compiled classes and dependencies as well. This is called
"auxclasspath" and is also configured here.
Note: you can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows.
```java
configuration.prependClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar");
configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
configuration.prependAuxClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar");
```
3. Then we need to load the rulesets. This is done by using the configuration, taking the minimum priority into
account:
4. Then we configure the reporting. Configuring the report file is optional. If not specified, the report
will be written to `stdout`.
```java
RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration);
List<RuleSet> ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(",")));
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
```
4. PMD operates on a list of `DataSource`. You can assemble a own list of `FileDataSource`, e.g.
5. Now an optional step: If you want to use additional renderers as in the example, set them up before
calling PMD. 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`:
```java
List<DataSource> files = Arrays.asList(new FileDataSource(new File("/path/to/src/MyClass.java")));
```
Writer rendererOutput = new StringWriter();
Renderer renderer = createRenderer(rendererOutput);
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.
```java
StringWriter rendererOutput = new StringWriter();
Renderer xmlRenderer = new XMLRenderer("UTF-8");
xmlRenderer.setWriter(rendererOutput);
xmlRenderer.start();
```
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, ruleSets, files, Collections.singletonList(renderer));
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
((ClasspathClassLoader) auxiliaryClassLoader).close();
}
// ...
private static Renderer createRenderer(Writer writer) {
XMLRenderer xml = new XMLRenderer("UTF-8");
xml.setWriter(writer);
return xml;
}
```
7. After the call, you need to finish the renderer via `end()` and `flush()`.
Then you can check the rendered output.
6. Finally we can start the PMD analysis. There is the possibility to fine-tune the configuration
by adding additional files to analyze or adding additional rulesets or renderers:
```java
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
```
The renderer will be automatically flushed and closed at the end of the analysis.
7. Then you can check the rendered output.
``` java
renderer.end();
renderer.flush();
System.out.println("Rendered Report:");
System.out.println(rendererOutput.toString());
```
@ -155,28 +161,15 @@ Here is a complete example:
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
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 java.nio.file.Paths;
import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.PmdAnalysis;
import net.sourceforge.pmd.RulePriority;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetLoader;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.renderers.XMLRenderer;
import net.sourceforge.pmd.util.ClasspathClassLoader;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;
public class PmdExample2 {
@ -184,27 +177,30 @@ public class PmdExample2 {
PMDConfiguration configuration = new PMDConfiguration();
configuration.setMinimumPriority(RulePriority.MEDIUM);
configuration.setRuleSets("rulesets/java/quickstart.xml");
configuration.prependClasspath("/home/workspace/target/classes");
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");
configuration.setInputPaths("/home/workspace/src/main/java/code");
configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
configuration.prependAuxClasspath("/home/workspace/target/classes");
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
Writer rendererOutput = new StringWriter();
Renderer renderer = createRenderer(rendererOutput);
renderer.start();
try {
PMD.processFiles(configuration, ruleSets, files, Collections.singletonList(renderer));
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
((ClasspathClassLoader) auxiliaryClassLoader).close();
}
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
renderer.end();
renderer.flush();
System.out.println("Rendered Report:");
System.out.println(rendererOutput.toString());
}
@ -214,28 +210,6 @@ public class PmdExample2 {
xml.setWriter(writer);
return xml;
}
private static List<DataSource> determineFiles(String basePath) throws IOException {
Path dirPath = FileSystems.getDefault().getPath(basePath);
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
final List<DataSource> files = new ArrayList<>();
Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (matcher.matches(path.getFileName())) {
System.out.printf("Using %s%n", path);
files.add(new FileDataSource(path.toFile()));
} else {
System.out.printf("Ignoring %s%n", path);
}
return super.visitFile(path, attrs);
}
});
System.out.printf("Analyzing %d files in %s%n", files.size(), basePath);
return files;
}
}
```

View File

@ -14,6 +14,40 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### New programmatic API
This release introduces a new programmatic API to replace the inflexible {% jdoc core::PMD %} class.
Programmatic execution of PMD should now be done with a {% jdoc core::PMDConfiguration %}
and a {% jdoc core::PmdAnalysis %}, for instance:
```java
PMDConfiguration config = new PMDConfiguration();
config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
config.setInputPaths("src/main/java");
config.prependAuxClasspath("target/classes");
config.setMinimumPriority(RulePriority.HIGH);
config.setRuleSets("rulesets/java/quickstart.xml");
config.setReportFormat("xml");
config.setReportFile("target/pmd-report.xml");
try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
```
The `PMD` class still supports methods related to CLI execution: `runPmd` and `main`.
All other members are now deprecated for removal.
The CLI itself remains compatible, if you run PMD via command-line, no action is required on your part.
### Fixed Issues
* apex-performance
@ -21,6 +55,27 @@ This is a {{ site.pmd.release_type }} release.
### API Changes
#### Deprecated API
* Several members of {% jdoc core::PMD %} have been newly deprecated, including:
- `PMD#EOL`: use `System#lineSeparator()`
- `PMD#SUPPRESS_MARKER`: use {% jdoc core::PMDConfiguration#DEFAULT_SUPPRESS_MARKER %}
- `PMD#processFiles`: use the [new programmatic API](#new-programmatic-api)
- `PMD#getApplicableFiles`: is internal
* {% jdoc !!core::PMDConfiguration#prependClasspath(java.lang.String) %} is deprecated
in favour of {% jdoc core::PMDConfiguration#prependAuxClasspath(java.lang.String) %}.
#### Experimental APIs
* Together with the [new programmatic API](#new-programmatic-api) the interface
{% jdoc core::lang.document.TextFile %} has been added as *experimental*. It intends
to replace {% jdoc core::util.datasource.DataSource %} and {% jdoc core::cpd.SourceCode %} in the long term.
This interface will change in PMD 7 to support read/write operations
and other things. You don't need to use it in PMD 6, as {% jdoc core::lang.document.FileCollector %}
decouples you from this. A file collector is available through {% jdoc !!core::PmdAnalysis#files() %}.
### External Contributions
* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe() - [@filiprafalowicz](https://github.com/filiprafalowicz)

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import net.sourceforge.pmd.cache.AnalysisCache;
@ -39,7 +40,7 @@ import net.sourceforge.pmd.util.ClasspathClassLoader;
* {@link #getClassLoader()}</li>
* <li>A means to configure a ClassLoader using a prepended classpath String,
* instead of directly setting it programmatically.
* {@link #prependClasspath(String)}</li>
* {@link #prependAuxClasspath(String)}</li>
* <li>A LanguageVersionDiscoverer instance, which defaults to using the default
* LanguageVersion of each Language. Means are provided to change the
* LanguageVersion for each Language.
@ -83,8 +84,12 @@ import net.sourceforge.pmd.util.ClasspathClassLoader;
* </ul>
*/
public class PMDConfiguration extends AbstractConfiguration {
/** The default suppress marker string. */
public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD";
// General behavior options
private String suppressMarker = PMD.SUPPRESS_MARKER;
private String suppressMarker = DEFAULT_SUPPRESS_MARKER;
private int threads = Runtime.getRuntime().availableProcessors();
private ClassLoader classLoader = getClass().getClassLoader();
private LanguageVersionDiscoverer languageVersionDiscoverer = new LanguageVersionDiscoverer();
@ -191,13 +196,46 @@ public class PMDConfiguration extends AbstractConfiguration {
* 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 {
if (classLoader == null) {
classLoader = PMDConfiguration.class.getClassLoader();
try {
prependAuxClasspath(classpath);
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
if (classpath != null) {
classLoader = new ClasspathClassLoader(classpath, 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.
*
* <p>If the classpath String looks like a URL to a file (i.e. starts with
* <code>file://</code>) the file will be read with each line representing
* an entry on the classpath.</p>
*
* @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);
}
}
@ -238,6 +276,7 @@ public class PMDConfiguration extends AbstractConfiguration {
*/
public void setForceLanguageVersion(LanguageVersion forceLanguageVersion) {
this.forceLanguageVersion = forceLanguageVersion;
languageVersionDiscoverer.setForcedVersion(forceLanguageVersion);
}
/**
@ -247,6 +286,7 @@ public class PMDConfiguration extends AbstractConfiguration {
* the LanguageVersion
*/
public void setDefaultLanguageVersion(LanguageVersion languageVersion) {
Objects.requireNonNull(languageVersion);
setDefaultLanguageVersions(Arrays.asList(languageVersion));
}
@ -259,6 +299,7 @@ public class PMDConfiguration extends AbstractConfiguration {
*/
public void setDefaultLanguageVersions(List<LanguageVersion> languageVersions) {
for (LanguageVersion languageVersion : languageVersions) {
Objects.requireNonNull(languageVersion);
languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion);
}
}

View File

@ -0,0 +1,272 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.benchmark.TimeTracker;
import net.sourceforge.pmd.benchmark.TimedOperation;
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
import net.sourceforge.pmd.internal.util.FileCollectionUtil;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
import net.sourceforge.pmd.lang.document.FileCollector;
import net.sourceforge.pmd.processor.AbstractPMDProcessor;
import net.sourceforge.pmd.processor.MonoThreadProcessor;
import net.sourceforge.pmd.processor.MultiThreadProcessor;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.util.ClasspathClassLoader;
import net.sourceforge.pmd.util.IOUtil;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.log.PmdLogger;
import net.sourceforge.pmd.util.log.PmdLogger.Level;
import net.sourceforge.pmd.util.log.SimplePmdLogger;
/**
* Main programmatic API of PMD. Create and configure a {@link PMDConfiguration},
* then use {@link #create(PMDConfiguration)} to obtain an instance.
* You can perform additional configuration on the instance, eg adding
* files to process, or additional rulesets and renderers. Then, call
* {@link #performAnalysis()}. Example:
* <pre>{@code
* PMDConfiguration config = new PMDConfiguration();
* config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
* config.setInputPaths("src/main/java");
* config.prependClasspath("target/classes");
* config.setMinimumPriority(RulePriority.HIGH);
* config.setRuleSets("rulesets/java/quickstart.xml");
* config.setReportFormat("xml");
* config.setReportFile("target/pmd-report.xml");
*
* try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
* // optional: add more rulesets
* pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
* // optional: add more files
* pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
* // optional: add more renderers
* pmd.addRenderer(renderer);
*
* pmd.performAnalysis();
* }
* }</pre>
*
*/
public final class PmdAnalysis implements AutoCloseable {
private final FileCollector collector;
private final List<Renderer> renderers = new ArrayList<>();
private final List<RuleSet> ruleSets = new ArrayList<>();
private final PMDConfiguration configuration;
private final SimplePmdLogger logger = new SimplePmdLogger(Logger.getLogger("net.sourceforge.pmd"));
/**
* Constructs a new instance. The files paths (input files, filelist,
* exclude list, etc) given in the configuration are collected into
* the file collector ({@link #files()}), but more can be added
* programmatically using the file collector.
*/
private PmdAnalysis(PMDConfiguration config) {
this.configuration = config;
this.collector = FileCollector.newCollector(
config.getLanguageVersionDiscoverer(),
logger
);
final Level logLevel = configuration.isDebug() ? Level.TRACE : Level.INFO;
this.logger.setLevel(logLevel);
}
/**
* Constructs a new instance from a configuration.
*
* <ul>
* <li> The files paths (input files, filelist,
* exclude list, etc) are explored and the files to analyse are
* collected into the file collector ({@link #files()}).
* More can be added programmatically using the file collector.
* <li>The rulesets given in the configuration are loaded ({@link PMDConfiguration#getRuleSets()})
* <li>A renderer corresponding to the parameters of the configuration
* is created and added (but not started).
* </ul>
*/
public static PmdAnalysis create(PMDConfiguration config) {
PmdAnalysis builder = new PmdAnalysis(config);
// note: do not filter files by language
// they could be ignored later. The problem is if you call
// addRuleSet later, then you could be enabling new languages
// So the files should not be pruned in advance
FileCollectionUtil.collectFiles(config, builder.files());
Renderer renderer = config.createRenderer();
renderer.setReportFile(config.getReportFile());
builder.addRenderer(renderer);
final RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(config);
final RuleSets ruleSets = RulesetsFactoryUtils.getRuleSetsWithBenchmark(config.getRuleSets(), ruleSetLoader.toFactory());
if (ruleSets != null) {
for (RuleSet ruleSet : ruleSets.getAllRuleSets()) {
builder.addRuleSet(ruleSet);
}
}
return builder;
}
@InternalApi
static PmdAnalysis createWithoutCollectingFiles(PMDConfiguration config) {
return new PmdAnalysis(config);
}
/**
* Returns the file collector for the analysed sources.
*/
public FileCollector files() {
return collector; // todo user can close collector programmatically
}
/**
* Add a new renderer. The given renderer must not already be started,
* it will be started by {@link #performAnalysis()}.
*
* @throws NullPointerException If the parameter is null
*/
public void addRenderer(Renderer renderer) {
this.renderers.add(Objects.requireNonNull(renderer));
}
/**
* Add a new ruleset.
*
* @throws NullPointerException If the parameter is null
*/
public void addRuleSet(RuleSet ruleSet) {
this.ruleSets.add(Objects.requireNonNull(ruleSet));
}
public List<RuleSet> getRulesets() {
return Collections.unmodifiableList(ruleSets);
}
/**
* Run PMD with the current state of this instance. This will start
* and finish the registered renderers. All files collected in the
* {@linkplain #files() file collector} are processed. This does not
* return a report, for compatibility with PMD 7.
*/
public void performAnalysis() {
performAnalysisAndCollectReport();
}
/**
* Run PMD with the current state of this instance. This will start
* and finish the registered renderers. All files collected in the
* {@linkplain #files() file collector} are processed. Returns the
* output report.
*/
// TODO PMD 7 @DeprecatedUntil700
public Report performAnalysisAndCollectReport() {
try (FileCollector files = collector) {
files.filterLanguages(getApplicableLanguages());
List<DataSource> dataSources = FileCollectionUtil.collectorToDataSource(files);
startRenderers();
Report report = performAnalysisImpl(dataSources);
finishRenderers();
return report;
}
}
Report performAnalysisImpl(List<DataSource> sortedFiles) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) {
PMD.encourageToUseIncrementalAnalysis(configuration);
Report report = new Report();
report.addListener(configuration.getAnalysisCache());
RuleContext ctx = new RuleContext();
ctx.setReport(report);
newFileProcessor(configuration).processFiles(new RuleSets(ruleSets), sortedFiles, ctx, renderers);
configuration.getAnalysisCache().persist();
return report;
}
}
private void startRenderers() {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
for (Renderer renderer : renderers) {
try {
renderer.start();
} catch (IOException e) {
logger.errorEx("Error while starting renderer " + renderer.getName(), e);
}
}
}
}
private void finishRenderers() {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
for (Renderer renderer : renderers) {
try {
renderer.end();
renderer.flush();
} catch (IOException e) {
logger.errorEx("Error while finishing renderer " + renderer.getName(), e);
}
}
}
}
private Set<Language> getApplicableLanguages() {
final Set<Language> languages = new HashSet<>();
final LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
for (RuleSet ruleSet : ruleSets) {
for (final Rule rule : ruleSet.getRules()) {
final Language ruleLanguage = rule.getLanguage();
if (!languages.contains(ruleLanguage)) {
final LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage);
if (RuleSet.applies(rule, version)) {
languages.add(ruleLanguage);
logger.trace("Using {0} version ''{1}''", version.getLanguage().getName(), version.getTerseName());
}
}
}
}
return languages;
}
private static AbstractPMDProcessor newFileProcessor(final PMDConfiguration configuration) {
return configuration.getThreads() > 0 ? new MultiThreadProcessor(configuration)
: new MonoThreadProcessor(configuration);
}
public PmdLogger getLog() {
return logger;
}
@Override
public void close() {
collector.close();
/*
* 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());
}
}
}

View File

@ -265,9 +265,9 @@ public class PMDTaskImpl {
try {
if (auxClasspath != null) {
project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
configuration.prependClasspath(auxClasspath.toString());
configuration.prependAuxClasspath(auxClasspath.toString());
}
} catch (IOException ioe) {
} catch (IllegalArgumentException ioe) {
throw new BuildException(ioe.getMessage(), ioe);
}
}

View File

@ -4,7 +4,6 @@
package net.sourceforge.pmd.cli;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
@ -233,8 +232,8 @@ public class PMDParameters {
}
try {
configuration.prependClasspath(this.getAuxclasspath());
} catch (IOException e) {
configuration.prependAuxClasspath(this.getAuxclasspath());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid auxiliary classpath: " + e.getMessage(), e);
}
return configuration;

View File

@ -0,0 +1,174 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.internal.util;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.document.FileCollector;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.util.FileUtil;
import net.sourceforge.pmd.util.database.DBMSMetadata;
import net.sourceforge.pmd.util.database.DBURI;
import net.sourceforge.pmd.util.database.SourceObject;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.log.PmdLogger;
import net.sourceforge.pmd.util.log.PmdLogger.Level;
import net.sourceforge.pmd.util.log.PmdLoggerScope;
/**
* @author Clément Fournier
*/
public final class FileCollectionUtil {
private FileCollectionUtil() {
}
public static List<DataSource> collectorToDataSource(FileCollector collector) {
List<DataSource> result = new ArrayList<>();
for (TextFile file : collector.getCollectedFiles()) {
result.add(file.toDataSourceCompat());
}
return result;
}
public static FileCollector collectFiles(PMDConfiguration configuration, Set<Language> languages, PmdLogger logger) {
FileCollector collector = collectFiles(configuration, logger);
collector.filterLanguages(languages);
return collector;
}
private static FileCollector collectFiles(PMDConfiguration configuration, PmdLogger logger) {
FileCollector collector = FileCollector.newCollector(
configuration.getLanguageVersionDiscoverer(),
logger
);
collectFiles(configuration, collector);
return collector;
}
public static void collectFiles(PMDConfiguration configuration, FileCollector collector) {
if (configuration.getSourceEncoding() != null) {
collector.setCharset(configuration.getSourceEncoding());
}
if (configuration.getInputPaths() != null) {
collectFiles(collector, configuration.getInputPaths());
}
if (configuration.getInputUri() != null) {
collectDB(collector, configuration.getInputUri());
}
if (configuration.getInputFilePath() != null) {
collectFileList(collector, configuration.getInputFilePath());
}
if (configuration.getIgnoreFilePath() != null) {
// disable trace logs for this secondary collector (would report 'adding xxx')
PmdLoggerScope mutedLog = new PmdLoggerScope("exclude list", collector.getLog());
mutedLog.setLevel(Level.ERROR);
try (FileCollector excludeCollector = FileCollector.newCollector(configuration.getLanguageVersionDiscoverer(), mutedLog)) {
collectFileList(excludeCollector, configuration.getIgnoreFilePath());
collector.exclude(excludeCollector);
}
}
}
public static void collectFiles(FileCollector collector, String fileLocations) {
for (String rootLocation : fileLocations.split(",")) {
try {
collector.relativizeWith(rootLocation);
addRoot(collector, rootLocation);
} catch (IOException e) {
collector.getLog().errorEx("Error collecting " + rootLocation, e);
}
}
}
public static void collectFileList(FileCollector collector, String fileListLocation) {
Path path = Paths.get(fileListLocation);
if (!Files.exists(path)) {
collector.getLog().error("No such file {0}", fileListLocation);
return;
}
String filePaths;
try {
filePaths = FileUtil.readFilelist(path.toFile());
} catch (IOException e) {
collector.getLog().errorEx("Error reading {0}", new Object[] { fileListLocation }, e);
return;
}
collectFiles(collector, filePaths);
}
private static void addRoot(FileCollector collector, String rootLocation) throws IOException {
Path path = Paths.get(rootLocation);
if (!Files.exists(path)) {
collector.getLog().error("No such file {0}", path);
return;
}
if (Files.isDirectory(path)) {
collector.addDirectory(path);
} else if (rootLocation.endsWith(".zip") || rootLocation.endsWith(".jar")) {
@SuppressWarnings("PMD.CloseResource")
FileSystem fs = collector.addZipFile(path);
if (fs == null) {
return;
}
for (Path zipRoot : fs.getRootDirectories()) {
collector.addFileOrDirectory(zipRoot);
}
} else if (Files.isRegularFile(path)) {
collector.addFile(path);
} else {
collector.getLog().trace("Ignoring {0}: not a regular file or directory", path);
}
}
public static void collectDB(FileCollector collector, String uriString) {
try {
collector.getLog().trace("Connecting to {0}", uriString);
DBURI dbUri = new DBURI(uriString);
DBMSMetadata dbmsMetadata = new DBMSMetadata(dbUri);
collector.getLog().trace("DBMSMetadata retrieved");
List<SourceObject> sourceObjectList = dbmsMetadata.getSourceObjectList();
collector.getLog().trace("Located {0} database source objects", sourceObjectList.size());
for (SourceObject sourceObject : sourceObjectList) {
String falseFilePath = sourceObject.getPseudoFileName();
collector.getLog().trace("Adding database source object {0}", falseFilePath);
try (Reader sourceCode = dbmsMetadata.getSourceCode(sourceObject)) {
String source = IOUtils.toString(sourceCode);
collector.addSourceFile(source, falseFilePath);
} catch (SQLException ex) {
collector.getLog().warningEx("Cannot get SourceCode for {0} - skipping ...",
new Object[] { falseFilePath},
ex);
}
}
} catch (ClassNotFoundException e) {
collector.getLog().errorEx("Cannot get files from DB - probably missing database JDBC driver", e);
} catch (Exception e) {
collector.getLog().errorEx("Cannot get files from DB - ''{0}''", new Object[] { uriString }, e);
}
}
}

View File

@ -175,7 +175,7 @@ public abstract class BaseLanguageModule implements Language {
@Override
public String toString() {
return "LanguageModule:" + name + '(' + this.getClass().getSimpleName() + ')';
return getTerseName();
}
@Override

View File

@ -35,7 +35,7 @@ public class LanguageVersion implements Comparable<LanguageVersion> {
private final Language language;
private final String version;
private final LanguageVersionHandler languageVersionHandler;
private final LanguageVersionHandler languageVersionHandler; // note: this is null if this is a cpd-only language...
/**
* @deprecated Use {@link Language#getVersion(String)}. This is only

View File

@ -8,6 +8,11 @@ import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* This class can discover the LanguageVersion of a source file. Further, every
@ -17,6 +22,23 @@ import java.util.Map;
public class LanguageVersionDiscoverer {
private Map<Language, LanguageVersion> languageToLanguageVersion = new HashMap<>();
private LanguageVersion forcedVersion;
public LanguageVersionDiscoverer() {
this(null);
}
/**
* Build a new instance.
*
* @param forcedVersion If non-null, all files should be assigned this version.
* The methods of this class still work as usual and do not
* care about the forced language version.
*/
public LanguageVersionDiscoverer(LanguageVersion forcedVersion) {
this.forcedVersion = forcedVersion;
}
/**
* Set the given LanguageVersion as the current default for it's Language.
*
@ -25,6 +47,7 @@ public class LanguageVersionDiscoverer {
* @return The previous default version for the language.
*/
public LanguageVersion setDefaultLanguageVersion(LanguageVersion languageVersion) {
AssertionUtil.requireParamNotNull("languageVersion", languageVersion);
LanguageVersion currentLanguageVersion = languageToLanguageVersion.put(languageVersion.getLanguage(),
languageVersion);
if (currentLanguageVersion == null) {
@ -41,6 +64,7 @@ public class LanguageVersionDiscoverer {
* @return The current default version for the language.
*/
public LanguageVersion getDefaultLanguageVersion(Language language) {
Objects.requireNonNull(language);
LanguageVersion languageVersion = languageToLanguageVersion.get(language);
if (languageVersion == null) {
languageVersion = language.getDefaultVersion();
@ -81,6 +105,14 @@ public class LanguageVersionDiscoverer {
return languageVersion;
}
public LanguageVersion getForcedVersion() {
return forcedVersion;
}
public void setForcedVersion(LanguageVersion forceLanguageVersion) {
this.forcedVersion = forceLanguageVersion;
}
/**
* Get the Languages of a given source file.
*
@ -106,11 +138,8 @@ public class LanguageVersionDiscoverer {
// Get the extensions from a file
private String getExtension(String fileName) {
String extension = null;
int extensionIndex = 1 + fileName.lastIndexOf('.');
if (extensionIndex > 0) {
extension = fileName.substring(extensionIndex);
}
return extension;
return StringUtils.substringAfterLast(fileName, ".");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;
/**
* A {@link TextFile} backed by a file in some {@link FileSystem}.
*/
@Experimental
class NioTextFile implements TextFile {
private final Path path;
private final Charset charset;
private final LanguageVersion languageVersion;
private final String displayName;
private final String pathId;
NioTextFile(Path path, Charset charset, LanguageVersion languageVersion, String displayName) {
AssertionUtil.requireParamNotNull("path", path);
AssertionUtil.requireParamNotNull("charset", charset);
AssertionUtil.requireParamNotNull("language version", languageVersion);
this.displayName = displayName;
this.path = path;
this.charset = charset;
this.languageVersion = languageVersion;
this.pathId = path.toAbsolutePath().toString();
}
@Override
public LanguageVersion getLanguageVersion() {
return languageVersion;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public String getPathId() {
return pathId;
}
@Override
public String readContents() throws IOException {
if (!Files.isRegularFile(path)) {
throw new IOException("Not a regular file: " + path);
}
try (BufferedReader br = Files.newBufferedReader(path, charset)) {
return IOUtils.toString(br);
}
}
@Override
public DataSource toDataSourceCompat() {
return new FileDataSource(path.toFile());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NioTextFile that = (NioTextFile) o;
return Objects.equals(path, that.path);
}
@Override
public int hashCode() {
return Objects.hash(pathId);
}
@Override
public String toString() {
return getPathId();
}
}

View File

@ -0,0 +1,94 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.StringReader;
import java.util.Objects;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.ReaderDataSource;
/**
* Read-only view on a string.
*
* @author Clément Fournier
*/
@Experimental
class StringTextFile implements TextFile {
private final String content;
private final String pathId;
private final String displayName;
private final LanguageVersion languageVersion;
StringTextFile(String content,
String pathId,
String displayName,
LanguageVersion languageVersion) {
AssertionUtil.requireParamNotNull("source text", content);
AssertionUtil.requireParamNotNull("file name", displayName);
AssertionUtil.requireParamNotNull("file ID", pathId);
AssertionUtil.requireParamNotNull("language version", languageVersion);
this.languageVersion = languageVersion;
this.content = content;
this.pathId = pathId;
this.displayName = displayName;
}
@Override
public LanguageVersion getLanguageVersion() {
return languageVersion;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public String getPathId() {
return pathId;
}
@Override
public String readContents() {
return content;
}
@Override
public DataSource toDataSourceCompat() {
return new ReaderDataSource(
new StringReader(content),
pathId
);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StringTextFile that = (StringTextFile) o;
return Objects.equals(pathId, that.pathId);
}
@Override
public int hashCode() {
return Objects.hash(pathId);
}
@Override
public String toString() {
return getPathId();
}
}

View File

@ -0,0 +1,98 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.File;
import java.io.IOException;
import net.sourceforge.pmd.PmdAnalysis;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.cpd.SourceCode;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
/**
* Represents some location containing character data. Despite the name,
* it's not necessarily backed by a file in the file-system: it may be
* eg an in-memory buffer, or a zip entry, ie it's an abstraction. Text
* files are the input which PMD and CPD process.
*
* <p>Text files must provide read access, and may provide write access.
* This interface only provides block IO operations, while {@link TextDocument} adds logic
* about incremental edition (eg replacing a single region of text).
*
* <p>This interface is meant to replace {@link DataSource} and {@link SourceCode.CodeLoader}.
* "DataSource" is not an appropriate name for a file which can be written
* to, also, the "data" it provides is text, not bytes.
*
* <h2>Experimental</h2>
* This interface will change in PMD 7 to support read/write operations
* and other things. You don't need to use it in PMD 6, as {@link FileCollector}
* decouples you from this. A file collector is available through {@link PmdAnalysis#files()}.
*/
@Experimental
public interface TextFile {
/**
* The name used for a file that has no name. This is mostly only
* relevant for unit tests.
*/
String UNKNOWN_FILENAME = "(unknown file)";
/**
* Returns the language version which should be used to process this
* file. This is a property of the file, which allows sources for
* several different language versions to be processed in the same
* PMD run. It also makes it so, that the file extension is not interpreted
* to find out the language version after the initial file collection
* phase.
*
* @return A language version
*/
LanguageVersion getLanguageVersion();
/**
* Returns an identifier for the path of this file. This should not
* be interpreted as a {@link File}, it may not be a file on this
* filesystem. The only requirement for this method, is that two
* distinct text files should have distinct path IDs, and that from
* one analysis to the next, the path ID of logically identical files
* be the same.
*
* <p>Basically this may be implemented as a URL, or a file path. It
* is used to index violation caches.
*/
String getPathId();
/**
* Returns a display name for the file. This name is used for
* reporting and should not be interpreted. It may be relative
* to a directory, may use platform-specific path separators,
* may not be normalized. Use {@link #getPathId()} when you
* want an identifier.
*/
String getDisplayName();
/**
* Reads the contents of the underlying character source.
*
* @return The most up-to-date content
*
* @throws IOException If this instance is closed
* @throws IOException If reading causes an IOException
*/
String readContents() throws IOException;
/**
* Compatibility with {@link DataSource} (pmd internals still use DataSource in PMD 6).
*/
@Deprecated
DataSource toDataSourceCompat();
}

View File

@ -0,0 +1,63 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document.internal;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
/**
* Discovers the languages applicable to a file.
*/
public class LanguageDiscoverer {
private final Language forcedLanguage;
/**
* Build a new instance.
*
* @param forcedLanguage If non-null, all files will be assigned this language.
*/
public LanguageDiscoverer(Language forcedLanguage) {
this.forcedLanguage = forcedLanguage;
}
/**
* Get the Languages of a given source file.
*
* @param sourceFile The file.
*
* @return The Languages for the source file, may be empty.
*/
public List<Language> getLanguagesForFile(Path sourceFile) {
return getLanguagesForFile(sourceFile.getFileName().toString());
}
/**
* Get the Languages of a given source file.
*
* @param fileName The file name.
*
* @return The Languages for the source file, may be empty.
*/
public List<Language> getLanguagesForFile(String fileName) {
if (forcedLanguage != null) {
return Collections.singletonList(forcedLanguage);
}
String extension = getExtension(fileName);
return LanguageRegistry.findByExtension(extension);
}
// Get the extensions from a file
private String getExtension(String fileName) {
return StringUtils.substringAfterLast(fileName, ".");
}
}

View File

@ -89,7 +89,7 @@ public class RuleBuilder {
Language lang = LanguageRegistry.findLanguageByTerseName(languageName);
if (lang == null) {
throw new IllegalArgumentException(
"Unknown Language '" + languageName + "' for rule" + name + ", supported Languages are "
"Unknown Language '" + languageName + "' for rule " + name + ", supported Languages are "
+ LanguageRegistry.commaSeparatedTerseNamesForLanguage(LanguageRegistry.findWithRuleSupport()));
}
language = lang;

View File

@ -20,6 +20,7 @@ import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* Create a ClassLoader which loads classes using a CLASSPATH like String. If
@ -57,17 +58,19 @@ public class ClasspathClassLoader extends URLClassLoader {
return urlList.toArray(new URL[0]);
}
private static URL[] initURLs(String classpath) throws IOException {
if (classpath == null) {
throw new IllegalArgumentException("classpath argument cannot be null");
}
private static URL[] initURLs(String classpath) {
AssertionUtil.requireParamNotNull("classpath", classpath);
final List<URL> urls = new ArrayList<>();
if (classpath.startsWith("file:")) {
// Treat as file URL
addFileURLs(urls, new URL(classpath));
} else {
// Treat as classpath
addClasspathURLs(urls, classpath);
try {
if (classpath.startsWith("file:")) {
// Treat as file URL
addFileURLs(urls, new URL(classpath));
} else {
// Treat as classpath
addClasspathURLs(urls, classpath);
}
} catch (IOException e) {
throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e);
}
return urls.toArray(new URL[0]);
}

View File

@ -100,7 +100,7 @@ public final class FileUtil {
}
private static List<DataSource> collect(List<DataSource> dataSources, String fileLocation,
FilenameFilter filenameFilter) {
FilenameFilter filenameFilter) {
File file = new File(fileLocation);
if (!file.exists()) {
throw new RuntimeException("File " + file.getName() + " doesn't exist");

View File

@ -18,6 +18,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -105,4 +107,50 @@ public final class IOUtil {
}
}
/**
* Close all closeable resources in order. If any exception occurs,
* it is saved and returned. If more than one exception occurs, the
* following are accumulated as suppressed exceptions in the first.
*
* @param closeables Resources to close
*
* @return An exception, or null if no 'close' routine threw
*/
@SuppressWarnings("PMD.CloseResource") // false-positive
public static IOException closeAll(Collection<? extends AutoCloseable> closeables) {
IOException composed = null;
for (AutoCloseable it : closeables) {
try {
it.close();
} catch (Exception e) {
if (composed == null) {
composed = new IOException("Cannot close resource " + it, e);
} else {
composed.addSuppressed(e);
}
}
}
return composed;
}
/**
* Ensure that the closeables are closed. In the end, throws the
* pending exception if not null, or the exception retuned by {@link #closeAll(Collection)}
* if not null. If both are non-null, adds one of them to the suppress
* list of the other, and throws that one.
*/
public static void ensureClosed(List<? extends AutoCloseable> toClose,
Exception pendingException) throws Exception {
Exception closeException = closeAll(toClose);
if (closeException != null) {
if (pendingException != null) {
closeException.addSuppressed(pendingException);
throw closeException;
}
// else no exception at all
} else if (pendingException != null) {
throw pendingException;
}
}
}

View File

@ -0,0 +1,70 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* Logger façade. Can probably be converted to just SLF4J logger in PMD 7.
*
* @author Clément Fournier
*/
@InternalApi
public interface PmdLogger {
boolean isLoggable(Level level);
void log(Level level, String message, Object... formatArgs);
void logEx(Level level, String message, Object[] formatArgs, Throwable error);
void info(String message, Object... formatArgs);
void trace(String message, Object... formatArgs);
void debug(String message, Object... formatArgs);
void warning(String message, Object... formatArgs);
void warningEx(String message, Throwable error);
void warningEx(String message, Object[] formatArgs, Throwable error);
void error(String message, Object... formatArgs);
void errorEx(String message, Throwable error);
void errorEx(String message, Object[] formatArgs, Throwable error);
int numErrors();
// levels, in sync with SLF4J levels
enum Level {
TRACE,
DEBUG,
INFO,
WARN,
ERROR;
java.util.logging.Level toJutilLevel() {
switch (this) {
case DEBUG:
return java.util.logging.Level.FINE;
case ERROR:
return java.util.logging.Level.SEVERE;
case INFO:
return java.util.logging.Level.INFO;
case TRACE:
return java.util.logging.Level.FINER;
case WARN:
return java.util.logging.Level.WARNING;
default:
throw AssertionUtil.shouldNotReachHere("exhaustive");
}
}
}
}

View File

@ -0,0 +1,115 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import java.text.MessageFormat;
import java.util.logging.Logger;
import org.apache.commons.lang3.exception.ExceptionUtils;
/**
* A logger based on a {@link Logger}.
*
* @author Clément Fournier
*/
abstract class PmdLoggerBase implements PmdLogger {
private int numErrors;
private Level minLevel = Level.TRACE;
/**
* null level means off.
*/
public final void setLevel(Level minLevel) {
this.minLevel = minLevel;
}
@Override
public final boolean isLoggable(Level level) {
return minLevel != null
&& minLevel.compareTo(level) <= 0
&& isLoggableImpl(level);
}
protected boolean isLoggableImpl(Level level) {
return true;
}
@Override
public void logEx(Level level, String message, Object[] formatArgs, Throwable error) {
if (isLoggable(level)) {
message = MessageFormat.format(message, formatArgs);
log(level, message + ": " + error.getMessage());
if (isLoggable(Level.DEBUG)) {
log(Level.DEBUG, ExceptionUtils.getStackTrace(error));
}
}
}
@Override
public final void log(Level level, String message, Object... formatArgs) {
if (level == Level.ERROR) {
this.numErrors++;
}
if (isLoggable(level)) {
logImpl(level, message, formatArgs);
}
}
/**
* Perform logging assuming {@link #isLoggable(Level)} is true.
*/
protected abstract void logImpl(Level level, String message, Object[] formatArgs);
@Override
public void trace(String message, Object... formatArgs) {
log(Level.TRACE, message, formatArgs);
}
@Override
public void debug(String message, Object... formatArgs) {
log(Level.DEBUG, message, formatArgs);
}
@Override
public void info(String message, Object... formatArgs) {
log(Level.INFO, message, formatArgs);
}
@Override
public void warning(String message, Object... formatArgs) {
log(Level.WARN, message, formatArgs);
}
@Override
public final void warningEx(String message, Throwable error) {
warningEx(message, new Object[0], error);
}
@Override
public void warningEx(String message, Object[] formatArgs, Throwable error) {
logEx(Level.WARN, message, formatArgs, error);
}
@Override
public void error(String message, Object... formatArgs) {
log(Level.ERROR, message, formatArgs);
}
@Override
public final void errorEx(String message, Throwable error) {
errorEx(message, new Object[0], error);
}
@Override
public void errorEx(String message, Object[] formatArgs, Throwable error) {
logEx(Level.ERROR, message, formatArgs, error);
}
@Override
public int numErrors() {
return numErrors;
}
}

View File

@ -0,0 +1,30 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import net.sourceforge.pmd.annotation.InternalApi;
/**
* A logger that prefixes a scope name to log messages. Also keeps a
* separate error count.
*
* @author Clément Fournier
*/
@InternalApi
public final class PmdLoggerScope extends PmdLoggerBase {
private final PmdLogger backend;
private final String scopePrefix;
public PmdLoggerScope(String scopeName, PmdLogger backend) {
this.backend = backend;
this.scopePrefix = "[" + scopeName + "] ";
}
@Override
protected void logImpl(Level level, String message, Object[] formatArgs) {
backend.log(level, scopePrefix + message, formatArgs);
}
}

View File

@ -0,0 +1,35 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import java.text.MessageFormat;
import java.util.logging.Logger;
import net.sourceforge.pmd.annotation.InternalApi;
/**
* A {@link Logger} (java.util) based logger impl.
*
* @author Clément Fournier
*/
@InternalApi
public class SimplePmdLogger extends PmdLoggerBase implements PmdLogger {
private final Logger backend;
public SimplePmdLogger(Logger backend) {
this.backend = backend;
}
@Override
protected boolean isLoggableImpl(Level level) {
return backend.isLoggable(level.toJutilLevel());
}
@Override
protected void logImpl(Level level, String message, Object[] formatArgs) {
backend.log(level.toJutilLevel(), MessageFormat.format(message, formatArgs));
}
}

View File

@ -51,10 +51,10 @@ public class ConfigurationTest {
}
@Test
public void testClassLoader() throws IOException {
public void testClassLoader() {
PMDConfiguration configuration = new PMDConfiguration();
assertEquals("Default ClassLoader", PMDConfiguration.class.getClassLoader(), configuration.getClassLoader());
configuration.prependClasspath("some.jar");
configuration.prependAuxClasspath("some.jar");
assertEquals("Prepended ClassLoader class", ClasspathClassLoader.class,
configuration.getClassLoader().getClass());
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
@ -68,31 +68,31 @@ public class ConfigurationTest {
}
@Test
public void auxClasspathWithRelativeFileEmpty() throws IOException {
public void auxClasspathWithRelativeFileEmpty() {
String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
Assert.assertEquals(0, urls.length);
}
@Test
public void auxClasspathWithRelativeFileEmpty2() throws IOException {
public void auxClasspathWithRelativeFileEmpty2() {
String relativeFilePath = "./src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
Assert.assertEquals(0, urls.length);
}
@Test
public void auxClasspathWithRelativeFile() throws IOException, URISyntaxException {
public void auxClasspathWithRelativeFile() throws URISyntaxException {
final String FILE_SCHEME = "file";
String currentWorkingDirectory = new File("").getAbsoluteFile().toURI().getPath();
String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
URI[] uris = new URI[urls.length];
for (int i = 0; i < urls.length; i++) {
@ -239,7 +239,7 @@ public class ConfigurationTest {
}
@Test
public void testAnalysisCacheLocation() throws IOException {
public void testAnalysisCacheLocation() {
final PMDConfiguration configuration = new PMDConfiguration();
configuration.setAnalysisCacheLocation(null);

View File

@ -20,6 +20,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Rule;
@ -121,6 +122,19 @@ public class CoreCliTest {
assertTrue("Report file should have been created", Files.exists(reportFile));
}
@Test
public void testFileCollectionWithUnknownFiles() throws IOException {
Path reportFile = tempRoot().resolve("out/reportFile.txt");
Files.createFile(srcDir.resolve("foo.not_analysable"));
assertFalse("Report file should not exist", Files.exists(reportFile));
runPmdSuccessfully("--no-cache", "--dir", srcDir, "--rulesets", DUMMY_RULESET, "--report-file", reportFile, "--debug");
assertTrue("Report file should have been created", Files.exists(reportFile));
String reportText = IOUtils.toString(Files.newBufferedReader(reportFile, StandardCharsets.UTF_8));
assertThat(reportText, not(containsStringIgnoringCase("error")));
}
@Test
public void testNonExistentReportFileDeprecatedOptions() {
Path reportFile = tempRoot().resolve("out/reportFile.txt");
@ -235,8 +249,8 @@ public class CoreCliTest {
}
private static void runPmd(int expectedExitCode, Object[] args) {
int actualExitCode = PMD.run(argsToString(args));
assertEquals("Exit code", expectedExitCode, actualExitCode);
StatusCode actualExitCode = PMD.runPmd(argsToString(args));
assertEquals("Exit code", expectedExitCode, actualExitCode.toInt());
}

View File

@ -4,6 +4,9 @@
package net.sourceforge.pmd.cli;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@ -30,8 +33,8 @@ public class PMDFilelistTest {
List<DataSource> applicableFiles = PMD.getApplicableFiles(configuration, languages);
Assert.assertEquals(2, applicableFiles.size());
Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile.dummy"));
Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("anotherfile.dummy"));
assertThat(applicableFiles.get(0).getNiceFileName(false, ""), endsWith("anotherfile.dummy"));
assertThat(applicableFiles.get(1).getNiceFileName(false, ""), endsWith("somefile.dummy"));
}
@Test
@ -44,9 +47,9 @@ public class PMDFilelistTest {
List<DataSource> applicableFiles = PMD.getApplicableFiles(configuration, languages);
Assert.assertEquals(3, applicableFiles.size());
Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile.dummy"));
Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("anotherfile.dummy"));
Assert.assertTrue(applicableFiles.get(2).getNiceFileName(false, "").endsWith("somefile.dummy"));
assertThat(applicableFiles.get(0).getNiceFileName(false, ""), endsWith("anotherfile.dummy"));
assertThat(applicableFiles.get(1).getNiceFileName(false, ""), endsWith("somefile.dummy"));
assertThat(applicableFiles.get(2).getNiceFileName(false, ""), endsWith("somefile.dummy"));
}
@Test

View File

@ -0,0 +1,143 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
/**
* @author Clément Fournier
*/
public class FileCollectorTest {
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void testAddFile() throws IOException {
Path root = tempFolder.getRoot().toPath();
Path foo = newFile(root, "foo.dummy");
Path bar = newFile(root, "bar.unknown");
FileCollector collector = newCollector();
assertTrue("should be dummy language", collector.addFile(foo));
assertFalse("should be unknown language", collector.addFile(bar));
assertCollected(collector, listOf("foo.dummy"));
}
@Test
public void testAddFileForceLanguage() throws IOException {
Path root = tempFolder.getRoot().toPath();
Path bar = newFile(root, "bar.unknown");
Language dummy = LanguageRegistry.findLanguageByTerseName("dummy");
FileCollector collector = newCollector(dummy.getDefaultVersion());
assertTrue("should be unknown language", collector.addFile(bar, dummy));
assertCollected(collector, listOf("bar.unknown"));
assertNoErrors(collector);
}
@Test
public void testAddFileNotExists() {
Path root = tempFolder.getRoot().toPath();
FileCollector collector = newCollector();
assertFalse(collector.addFile(root.resolve("does_not_exist.dummy")));
assertEquals(1, collector.getLog().numErrors());
}
@Test
public void testAddFileNotAFile() throws IOException {
Path root = tempFolder.getRoot().toPath();
Path dir = root.resolve("src");
Files.createDirectories(dir);
FileCollector collector = newCollector();
assertFalse(collector.addFile(dir));
assertEquals(1, collector.getLog().numErrors());
}
@Test
public void testAddDirectory() throws IOException {
Path root = tempFolder.getRoot().toPath();
newFile(root, "src/foo.dummy");
newFile(root, "src/bar.unknown");
newFile(root, "src/x/bar.dummy");
FileCollector collector = newCollector();
collector.addDirectory(root.resolve("src"));
assertCollected(collector, listOf("src/foo.dummy", "src/x/bar.dummy"));
}
@Test
public void testRelativize() throws IOException {
String displayName = FileCollector.getDisplayName(Paths.get("a", "b", "c"), listOf(Paths.get("a").toString()));
assertEquals(displayName, Paths.get("b", "c").toString());
}
private Path newFile(Path root, String path) throws IOException {
Path resolved = root.resolve(path);
Files.createDirectories(resolved.getParent());
Files.createFile(resolved);
return resolved;
}
private void assertCollected(FileCollector collector, List<String> relPaths) {
Map<String, String> actual = new LinkedHashMap<>();
for (TextFile file : collector.getCollectedFiles()) {
actual.put(file.getDisplayName(), file.getLanguageVersion().getTerseName());
}
for (int i = 0; i < relPaths.size(); i++) {
// normalize, we want display names to be platform-specific
relPaths.set(i, relPaths.get(i).replace('/', File.separatorChar));
}
assertEquals(relPaths, new ArrayList<>(actual.keySet()));
}
private void assertNoErrors(FileCollector collector) {
assertEquals("No errors expected", 0, collector.getLog().numErrors());
}
private FileCollector newCollector() {
return newCollector(null);
}
private FileCollector newCollector(LanguageVersion forcedVersion) {
LanguageVersionDiscoverer discoverer = new LanguageVersionDiscoverer(forcedVersion);
FileCollector collector = FileCollector.newCollector(discoverer, new PmdTestLogger());
collector.relativizeWith(tempFolder.getRoot().getAbsolutePath());
return collector;
}
}

View File

@ -0,0 +1,22 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.util.logging.Logger;
import net.sourceforge.pmd.util.log.SimplePmdLogger;
/**
* @author Clément Fournier
*/
public class PmdTestLogger extends SimplePmdLogger {
private static final Logger LOG = Logger.getLogger("testlogger");
public PmdTestLogger() {
super(LOG);
setLevel(null);
}
}

View File

@ -4,16 +4,11 @@
package net.sourceforge.pmd.cli;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import java.io.File;
import java.util.regex.Pattern;
import org.junit.Assert;
import org.junit.Test;
import net.sourceforge.pmd.util.FileUtil;
/**
* @author Romain Pelisse &lt;belaran@gmail.com&gt;
*
@ -21,62 +16,57 @@ import net.sourceforge.pmd.util.FileUtil;
public class CLITest extends BaseCLITest {
@Test
public void minimalArgs() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/bestpractices.xml,category/java/design.xml", };
runTest(args, "minimalArgs");
runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/bestpractices.xml,category/java/design.xml");
}
@Test
public void minimumPriority() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-min", "1", };
runTest(args, "minimumPriority");
runTest(args);
}
@Test
public void usingDebug() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-debug", };
runTest(args, "minimalArgsWithDebug");
runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-debug");
}
@Test
public void usingDebugLongOption() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "--debug", };
runTest(args, "minimalArgsWithDebug");
runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "--debug");
}
@Test
public void changeJavaVersion() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-version", "1.5", "-language",
"java", "-debug", };
String resultFilename = runTest(args, "chgJavaVersion");
assertTrue("Invalid Java version",
FileUtil.findPatternInFile(new File(resultFilename), "Using Java version: Java 1.5"));
"java", "--debug", };
String log = runTest(args);
assertThat(log, containsPattern("Adding file .*\\.java \\(lang: java 1\\.5\\)"));
}
@Test
public void exitStatusNoViolations() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", };
runTest(args, "exitStatusNoViolations");
runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml");
}
@Test
public void exitStatusWithViolations() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", };
String resultFilename = runTest(args, "exitStatusWithViolations", 4);
assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if"));
String log = runTest(4, args);
assertThat(log, containsString("Avoid empty if"));
}
@Test
public void exitStatusWithViolationsAndWithoutFailOnViolations() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", "-failOnViolation", "false", };
String resultFilename = runTest(args, "exitStatusWithViolationsAndWithoutFailOnViolations", 0);
assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if"));
String log = runTest(0, args);
assertThat(log, containsString("Avoid empty if"));
}
@Test
public void exitStatusWithViolationsAndWithoutFailOnViolationsLongOption() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", "--fail-on-violation", "false", };
String resultFilename = runTest(args, "exitStatusWithViolationsAndWithoutFailOnViolations", 0);
assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if"));
String log = runTest(0, args);
assertThat(log, containsString("Avoid empty if"));
}
/**
@ -85,12 +75,9 @@ public class CLITest extends BaseCLITest {
@Test
public void testWrongRuleset() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml", };
String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt";
createTestOutputFile(filename);
runPMDWith(args);
Assert.assertEquals(1, getStatusCode());
assertTrue(FileUtil.findPatternInFile(new File(filename),
"Can't find resource 'category/java/designn.xml' for rule 'null'." + " Make sure the resource is a valid file"));
String log = runTest(1, args);
assertThat(log, containsString("Can't find resource 'category/java/designn.xml' for rule 'null'."
+ " Make sure the resource is a valid file"));
}
/**
@ -99,12 +86,9 @@ public class CLITest extends BaseCLITest {
@Test
public void testWrongRulesetWithRulename() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml/UseCollectionIsEmpty", };
String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt";
createTestOutputFile(filename);
runPMDWith(args);
Assert.assertEquals(1, getStatusCode());
assertTrue(FileUtil.findPatternInFile(new File(filename),
"Can't find resource 'category/java/designn.xml' for rule " + "'UseCollectionIsEmpty'."));
String log = runTest(1, args);
assertThat(log, containsString("Can't find resource 'category/java/designn.xml' for rule "
+ "'UseCollectionIsEmpty'."));
}
/**
@ -113,11 +97,8 @@ public class CLITest extends BaseCLITest {
@Test
public void testWrongRulename() {
String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml/ThisRuleDoesNotExist", };
String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt";
createTestOutputFile(filename);
runPMDWith(args);
Assert.assertEquals(1, getStatusCode());
assertTrue(FileUtil.findPatternInFile(new File(filename), Pattern
.quote("No rules found. Maybe you misspelled a rule name?" + " (category/java/design.xml/ThisRuleDoesNotExist)")));
String log = runTest(1, args);
assertThat(log, containsString("No rules found. Maybe you misspelled a rule name?"
+ " (category/java/design.xml/ThisRuleDoesNotExist)"));
}
}

Some files were not shown because too many files have changed in this diff Show More