org.codenarc.ant.AntFileSetSourceAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of CodeNarc Show documentation
Show all versions of CodeNarc Show documentation
The CodeNarc project provides a static analysis tool for Groovy code.
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codenarc.ant;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.codenarc.analyzer.AbstractSourceAnalyzer;
import org.codenarc.analyzer.AnalyzerException;
import org.codenarc.results.DirectoryResults;
import org.codenarc.results.FileResults;
import org.codenarc.results.Results;
import org.codenarc.rule.Violation;
import org.codenarc.ruleset.RuleSet;
import org.codenarc.source.SourceFile;
import org.codenarc.util.PathUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* SourceAnalyzer implementation that gets source files from one or more Ant FileSets.
*
* @author Chris Mair
*/
public class AntFileSetSourceAnalyzer extends AbstractSourceAnalyzer {
private static final Logger LOG = LoggerFactory.getLogger(AntFileSetSourceAnalyzer.class);
private static final int POOL_TIMEOUT_SECONDS = 60 * 60;
/**
* Whether to throw an exception if errors occur parsing source files (true), or just log the errors (false)
*/
boolean failOnError = false;
private final Project project;
protected final List fileSets;
// Concurrent shared state
private final ConcurrentMap> resultsMap = new ConcurrentHashMap>();
private final AtomicLong sourceFileErrors = new AtomicLong(0L);
/**
* Construct a new instance on the specified Ant FileSet.
*
* @param project - the Ant Project; must not be null
* @param fileSet - the Ant FileSet; must not be null
*/
public AntFileSetSourceAnalyzer(Project project, FileSet fileSet) {
if (fileSet == null) {
throw new IllegalArgumentException("Null: fileSet");
}
if (project == null) {
throw new IllegalArgumentException("Null: project");
}
this.project = project;
this.fileSets = Arrays.asList(fileSet);
}
/**
* Construct a new instance on the specified List of Ant FileSets.
*
* @param project - the Ant Project
* @param fileSets - the List of Ant FileSet; my be empty; must not be null
*/
AntFileSetSourceAnalyzer(Project project, List fileSets) {
if (fileSets == null) {
throw new IllegalArgumentException("Null: fileSets");
}
if (project == null) {
throw new IllegalArgumentException("Null: project");
}
this.project = project;
this.fileSets = new ArrayList(fileSets);
}
/**
* Analyze all source code using the specified RuleSet and return the report results.
*
* @param ruleSet - the RuleSet to apply to each source component; must not be null.
* @return the results from applying the RuleSet to all of the source
*/
public Results analyze(RuleSet ruleSet) {
long startTime = System.currentTimeMillis();
DirectoryResults reportResults = new DirectoryResults();
int numThreads = Runtime.getRuntime().availableProcessors() - 1;
numThreads = numThreads > 0 ? numThreads : 1;
ExecutorService pool = Executors.newFixedThreadPool(numThreads);
for (FileSet fileSet : fileSets) {
processFileSet(fileSet, ruleSet, pool);
}
waitForThreadsToComplete(pool);
if (failOnError && sourceFileErrors.get() > 0L) {
throw new AnalyzerException("Errors analyzing " + sourceFileErrors.get() + " source files");
}
addDirectoryResults(reportResults);
LOG.info("Analysis time=" + (System.currentTimeMillis() - startTime) + "ms; Files with analysis errors: " + sourceFileErrors.get());
return reportResults;
}
private void waitForThreadsToComplete(ExecutorService pool) {
pool.shutdown();
try {
boolean completed = pool.awaitTermination(POOL_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!completed) {
throw new IllegalStateException("Thread Pool terminated before completion");
}
} catch (InterruptedException e) {
throw new IllegalStateException("Thread Pool interrupted before completion");
}
}
public List getSourceDirectories() {
String baseDir = project.getBaseDir().getAbsolutePath();
List result = new ArrayList();
for (FileSet fileSet : fileSets) {
String path = fileSet.getDir(project).getPath();
String trimmedPath = PathUtil.removePathPrefix(baseDir, path);
result.add(trimmedPath);
}
return result;
}
//--------------------------------------------------------------------------
// Internal Helper Methods
//--------------------------------------------------------------------------
private void processFileSet(FileSet fileSet, RuleSet ruleSet, ExecutorService pool) {
DirectoryScanner dirScanner = fileSet.getDirectoryScanner(project);
File baseDir = fileSet.getDir(project);
String[] includedFiles = dirScanner.getIncludedFiles();
if (includedFiles == null || includedFiles.length == 0) {
LOG.info("No matching files found for FileSet with basedir [" + baseDir + "]");
return;
}
for (String filePath : includedFiles) {
Runnable task = buildTask(baseDir, filePath, ruleSet);
pool.submit(task);
}
}
private Runnable buildTask(final File baseDir, final String filePath, final RuleSet ruleSet) {
return new Runnable() {
public void run() {
try {
processFile(baseDir, filePath, ruleSet);
} catch (Throwable t) {
LOG.warn("Error processing file: '" + filePath + "'; " + t);
sourceFileErrors.incrementAndGet();
}
}
};
}
private void processFile(File baseDir, String filePath, RuleSet ruleSet) {
File file = new File(baseDir, filePath);
SourceFile sourceFile = new SourceFile(file);
List allViolations = collectViolations(sourceFile, ruleSet);
if (!sourceFile.isValid()) {
sourceFileErrors.incrementAndGet();
}
FileResults fileResults = new FileResults(PathUtil.normalizePath(filePath), allViolations, sourceFile);
String parentPath = PathUtil.getParentPath(filePath);
String safeParentPath = parentPath != null ? parentPath : "";
addToResultsMap(safeParentPath, fileResults);
}
private void addToResultsMap(String parentPath, FileResults results) {
List initialEmptyResults = Collections.synchronizedList(new ArrayList());
resultsMap.putIfAbsent(parentPath, initialEmptyResults);
if (results != null) {
List dirResults = resultsMap.get(parentPath);
dirResults.add(results);
}
}
private void addToParentResults(DirectoryResults reportResults, Results results) {
String parentPath = PathUtil.getParentPath(results.getPath());
if (parentPath == null) {
reportResults.addChild(results);
return;
}
DirectoryResults parent = (DirectoryResults) reportResults.findResultsForPath(parentPath);
if (parent == null) {
parent = new DirectoryResults(parentPath);
addToParentResults(reportResults, parent);
}
parent.addChild(results);
}
private void addDirectoryResults(DirectoryResults reportResults) {
Set set = resultsMap.keySet();
ArrayList allPaths = new ArrayList(set);
Collections.sort(allPaths);
for (String path : allPaths) {
DirectoryResults dirResults = new DirectoryResults(path);
List allResults = resultsMap.get(path);
Collections.sort(allResults, new Comparator() {
public int compare(FileResults o1, FileResults o2) {
return o1.getPath().compareTo(o2.getPath());
}
});
for (FileResults child : allResults) {
dirResults.addChild(child);
}
addToParentResults(reportResults, dirResults);
}
}
}