package hudson.plugins.warnings.parser; import hudson.model.Hudson; import hudson.plugins.analysis.util.EncodingValidator; import hudson.plugins.analysis.util.model.FileAnnotation; import hudson.plugins.warnings.GroovyParser; import hudson.plugins.warnings.WarningsDescriptor; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.tools.ant.DirectoryScanner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * Registry for the active parsers in this plug-in. * * @author Ulli Hafner */ // CHECKSTYLE:COUPLING-OFF public class ParserRegistry { /** The actual parsers to use when scanning a file. */ private final List<WarningsParser> parsers; /** Compound include/exclude filter for files that should get into report. */ private final FileFilter fileFilter; /** The default charset to be used when reading and parsing files. */ private final Charset defaultCharset; /** * Returns all available parser names. * * @return all available parser names */ public static List<String> getAvailableParsers() { ArrayList<String> sortedParsers = new ArrayList<String>(getAllParserNames()); Collections.sort(sortedParsers); return Collections.unmodifiableList(sortedParsers); } private static Set<String> getAllParserNames() { Set<String> parsers = new HashSet<String>(); for (WarningsParser parser : getAllParsers()) { parsers.add(parser.getName()); } return parsers; } /** * Returns a list of parsers that match the specified names. Note that the * mapping of names to parsers is one to many. * * @param parserNames * the parser names * @return a list of parsers, might be modified by the receiver */ public static List<WarningsParser> getParsers(final Set<String> parserNames) { List<WarningsParser> actualParsers = new ArrayList<WarningsParser>(); for (String name : parserNames) { for (WarningsParser warningsParser : getAllParsers()) { if (warningsParser.getName().equals(name)) { actualParsers.add(warningsParser); } } } return actualParsers; } /** * Returns all parser names that identify at least one existing parser. The * returned list is sorted alphabetically. * * @param parserNames * the names to filter * @return the filtered set, containing only valid names */ public static List<String> filterExistingParserNames(final Set<String> parserNames) { List<String> validNames = Lists.newArrayList(); Set<String> allParsers = getAllParserNames(); for (String name : parserNames) { if (allParsers.contains(name)) { validNames.add(name); } } Collections.sort(validNames); return validNames; } /** * Returns all available parsers. * * @return all available parsers */ private static List<WarningsParser> getAllParsers() { List<WarningsParser> parsers = new ArrayList<WarningsParser>(); parsers.add(new JavacParser()); parsers.add(new AntJavacParser()); parsers.add(new JavaDocParser()); parsers.add(new AntEclipseParser()); parsers.add(new MsBuildParser()); parsers.add(new GccParser()); parsers.add(new Gcc4CompilerParser()); parsers.add(new Gcc4LinkerParser()); parsers.add(new InvalidsParser()); parsers.add(new SunCParser()); parsers.add(new GnatParser()); parsers.add(new ErlcParser()); parsers.add(new IntelCParser()); parsers.add(new IarParser()); MsBuildParser pclintParser = new MsBuildParser(); pclintParser.setName("PC-Lint"); parsers.add(pclintParser); parsers.add(new BuckminsterParser()); parsers.add(new TiCcsParser()); parsers.add(new AcuCobolParser()); parsers.add(new FlexSDKParser()); parsers.add(new PhpParser()); parsers.add(new CoolfluxChessccParser()); parsers.add(new P4Parser()); parsers.add(new RobocopyParser()); parsers.add(new DoxygenParser()); parsers.add(new TnsdlParser()); Iterable<GroovyParser> parserDescriptions = getDynamicParserDescriptions(); parsers.addAll(getDynamicParsers(parserDescriptions)); return ImmutableList.copyOf(parsers); } private static Iterable<GroovyParser> getDynamicParserDescriptions() { Hudson instance = Hudson.getInstance(); if (instance != null) { WarningsDescriptor descriptor = instance.getDescriptorByType(WarningsDescriptor.class); if (descriptor != null) { return descriptor.getParsers(); } } return Collections.emptyList(); } private static List<WarningsParser> getDynamicParsers(final Iterable<GroovyParser> parserDescriptions) { List<WarningsParser> parsers = new ArrayList<WarningsParser>(); for (GroovyParser description : parserDescriptions) { if (description.isValid()) { parsers.add(new DynamicParser(description.getName(), description .getRegexp(), description.getScript())); } } return parsers; } /** * Creates a new instance of <code>ParserRegistry</code>. * * @param parsers * the parsers to use when scanning a file * @param defaultEncoding * the default encoding to be used when reading and parsing files */ public ParserRegistry(final List<WarningsParser> parsers, final String defaultEncoding) { this(parsers, defaultEncoding, StringUtils.EMPTY, StringUtils.EMPTY); } /** * Creates a new instance of <code>ParserRegistry</code>. * * @param parsers * the parsers to use when scanning a file * @param includePattern * Ant file-set pattern of files to include in report, * <code>null</code> or an empty string do not filter the output * @param excludePattern * Ant file-set pattern of files to exclude from report, * <code>null</code> or an empty string do not filter the output * @param defaultEncoding * the default encoding to be used when reading and parsing files */ public ParserRegistry(final List<WarningsParser> parsers, final String defaultEncoding, final String includePattern, final String excludePattern) { defaultCharset = EncodingValidator.defaultCharset(defaultEncoding); this.parsers = new ArrayList<WarningsParser>(parsers); if (this.parsers.isEmpty()) { this.parsers.addAll(getAllParsers()); } if (StringUtils.isEmpty(includePattern) && StringUtils.isEmpty(excludePattern)) { fileFilter = null; } else { fileFilter = new FileFilter(includePattern, excludePattern); } } /** * Iterates over the available parsers and parses the specified file with each parser. * Returns all found warnings. * * @param file the input stream * @return all found warnings * * @throws IOException Signals that an I/O exception has occurred. */ public Collection<FileAnnotation> parse(final File file) throws IOException { List<FileAnnotation> allAnnotations = new ArrayList<FileAnnotation>(); for (WarningsParser parser : parsers) { Reader input = null; try { input = createReader(file); allAnnotations.addAll(parser.parse(input)); } finally { IOUtils.closeQuietly(input); } } return applyExcludeFilter(allAnnotations); } /** * Iterates over the available parsers and parses the specified file with each parser. * Returns all found warnings. * * @param file the input stream * @return all found warnings * * @throws IOException Signals that an I/O exception has occurred. */ public Collection<FileAnnotation> parse(final InputStream file) throws IOException { try { List<FileAnnotation> allAnnotations = new ArrayList<FileAnnotation>(); for (WarningsParser parser : parsers) { allAnnotations.addAll(parser.parse(createReader(file))); } return applyExcludeFilter(allAnnotations); } finally { IOUtils.closeQuietly(file); } } /** * Applies the exclude filter to the found annotations. * * @param allAnnotations * all annotations * @return the filtered annotations if there is a filter defined */ private Collection<FileAnnotation> applyExcludeFilter(final List<FileAnnotation> allAnnotations) { if (fileFilter == null) { return allAnnotations; } else { return filterAnnotations(allAnnotations); } } /** * Filters the annotations based on the {@link #fileFilter}. * * @param annotations * the annotations to filter * @return the annotations that are not excluded in the filter */ private Collection<FileAnnotation> filterAnnotations(final List<FileAnnotation> annotations) { List<FileAnnotation> filteredAnnotations = new ArrayList<FileAnnotation>(); for (FileAnnotation annotation : annotations) { if (fileFilter.matches(annotation.getFileName())) { filteredAnnotations.add(annotation); } } return filteredAnnotations; } /** * Creates a reader from the specified file. Uses the defined character set to * read the content of the input stream. * * @param file the file * @return the reader * @throws FileNotFoundException if the file does not exist */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL") protected Reader createReader(final File file) throws FileNotFoundException { return createReader(new FileInputStream(file)); } /** * Creates a reader from the specified input stream. Uses the defined character set to * read the content of the input stream. * * @param inputStream the input stream * @return the reader */ protected Reader createReader(final InputStream inputStream) { return new InputStreamReader(inputStream, defaultCharset); } /** * Filters file names based on Ant file-set patterns. */ private static final class FileFilter extends DirectoryScanner { private static final String SEPARATOR = ",\\s*"; /** * Creates a new instance of {@link FileFilter}. * * @param includePattern * Ant file-set pattern of files to include in report * @param excludePattern * Ant file-set pattern of files to exclude from report */ public FileFilter(final String includePattern, final String excludePattern) { super(); if (StringUtils.isEmpty(includePattern)) { setIncludes(new String[] {"**/*"}); } else { setIncludes(includePattern.split(SEPARATOR)); } if (StringUtils.isEmpty(excludePattern)) { setExcludes(new String[] {}); } else { setExcludes(excludePattern.split(SEPARATOR)); } } /** * Returns whether the name * matches the one of the inclusion patterns * and does not match one of the exclusion patterns. * * @param name * the file name to test * @return <code>true</code> if the name * matches one of the inclusion patterns * and does not match any of the exclusion patterns. */ public boolean matches(final String name) { String canonicalName; if (File.separatorChar == '\\') { canonicalName = StringUtils.replaceChars(name, '/', '\\'); } else { canonicalName = name; } return isIncluded(canonicalName) && !isExcluded(canonicalName); } } }