package hudson.plugins.analysis.core;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.plugins.analysis.Messages;
import hudson.plugins.analysis.util.FileFinder;
import hudson.plugins.analysis.util.ModuleDetector;
import hudson.plugins.analysis.util.PluginLogger;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.remoting.VirtualChannel;
/**
* Parses files that match the specified pattern and creates a corresponding
* {@link ParserResult} with a collection of annotations.
*
* @author Ulli Hafner
*/
public class FilesParser implements FileCallable<ParserResult> {
/** Generated ID. */
private static final long serialVersionUID = -6415863872891783891L;
/** Logger. */
@edu.umd.cs.findbugs.annotations.SuppressWarnings("Se")
private final transient PluginLogger logger;
/** Ant file-set pattern to scan for. */
private final String filePattern;
/** Parser to be used to process the workspace files. */
private final AnnotationParser parser;
/** Determines whether this build uses maven. */
private final boolean isMavenBuild;
/** Determines whether this build uses ant. */
private final boolean isAntBuild;
/** The predefined module name, might be empty. */
private final String moduleName;
/**
* Creates a new instance of {@link FilesParser}.
*
* @param logger
* the logger
* @param filePattern
* ant file-set pattern to scan for files to parse
* @param parser
* the parser to apply on the found files
* @param isMavenBuild
* determines whether this build uses maven
* @param isAntBuild
* determines whether this build uses maven
*/
private FilesParser(final PluginLogger logger, final String filePattern, final AnnotationParser parser, final boolean isMavenBuild, final boolean isAntBuild, final String moduleName) {
this.logger = logger;
this.filePattern = filePattern;
this.parser = parser;
this.isMavenBuild = isMavenBuild;
this.isAntBuild = isAntBuild;
this.moduleName = moduleName;
}
/**
* Creates a new instance of {@link FilesParser}.
*
* @param logger
* the logger
* @param filePattern
* ant file-set pattern to scan for files to parse
* @param parser
* the parser to apply on the found files
* @param isMavenBuild
* determines whether this build uses maven
* @param isAntBuild
* determines whether this build uses maven
*/
public FilesParser(final PluginLogger logger, final String filePattern, final AnnotationParser parser, final boolean isMavenBuild, final boolean isAntBuild) {
this(logger, filePattern, parser, isMavenBuild, isAntBuild, StringUtils.EMPTY);
}
/**
* Creates a new instance of {@link FilesParser}. Assumes that this is a
* Maven build with the specified module name.
*
* @param logger
* the logger
* @param filePattern
* ant file-set pattern to scan for files to parse
* @param parser
* the parser to apply on the found files
* @param moduleName
* the name of the module to use for all files
*/
public FilesParser(final PluginLogger logger, final String filePattern, final AnnotationParser parser, final String moduleName) {
this(logger, filePattern, parser, true, false, moduleName);
}
/**
* Logs the specified message.
*
* @param message the message
*/
protected void log(final String message) {
if (logger != null) {
logger.log(message);
}
}
/** {@inheritDoc} */
public ParserResult invoke(final File workspace, final VirtualChannel channel) throws IOException {
ParserResult result = new ParserResult(new FilePath(workspace));
try {
String[] fileNames = new FileFinder(filePattern).find(workspace);
if (fileNames.length == 0 && !isMavenBuild) {
result.addErrorMessage(Messages.FilesParser_Error_NoFiles());
}
else {
parseFiles(workspace, fileNames, result);
}
}
catch (InterruptedException exception) {
log("Parsing has been canceled.");
}
return result;
}
/**
* Parses the specified collection of files and appends the results to the
* provided container.
*
* @param workspace
* the workspace root
* @param fileNames
* the names of the file to parse
* @param result
* the result of the parsing
* @throws InterruptedException
* if the user cancels the parsing
*/
private void parseFiles(final File workspace, final String[] fileNames, final ParserResult result) throws InterruptedException {
ModuleDetector detector = new ModuleDetector();
for (String fileName : fileNames) {
File file = new File(workspace, fileName);
String module;
if (StringUtils.isBlank(moduleName)) {
module = detector.guessModuleName(file.getAbsolutePath(), isMavenBuild, isAntBuild);
}
else {
module = moduleName;
}
if (!file.canRead()) {
String message = Messages.FilesParser_Error_NoPermission(module, file);
log(message);
result.addErrorMessage(module, message);
continue;
}
if (file.length() <= 0) {
String message = Messages.FilesParser_Error_EmptyFile(module, file);
log(message);
result.addErrorMessage(module, message);
continue;
}
parseFile(file, module, result);
result.addModule(module);
}
}
/**
* Parses the specified file and stores all found annotations. If the file
* could not be parsed then an error message is appended to the result.
* @param file
* the file to parse
* @param module
* the associated module
* @param result
* the result of the parser
*
* @throws InterruptedException
* if the user cancels the parsing
*/
private void parseFile(final File file, final String module, final ParserResult result) throws InterruptedException {
try {
Collection<FileAnnotation> annotations = parser.parse(file, module);
result.addAnnotations(annotations);
log("Successfully parsed file " + file + " of module " + module + " with " + annotations.size() + " warnings.");
}
catch (InvocationTargetException exception) {
String errorMessage = Messages.FilesParser_Error_Exception(file) + "\n\n"
+ ExceptionUtils.getStackTrace((Throwable)ObjectUtils.defaultIfNull(exception.getCause(), exception));
result.addErrorMessage(module, errorMessage);
log(errorMessage);
}
}
}