/* * Copyright 2000-2010 JetBrains s.r.o. * * 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 jetbrains.buildServer.xmlReportPlugin.findBugs; import jetbrains.buildServer.agent.BaseServerLoggerFacade; import jetbrains.buildServer.xmlReportPlugin.XmlReportParser; import jetbrains.buildServer.xmlReportPlugin.XmlReportPlugin; import org.jetbrains.annotations.NotNull; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.swing.text.html.parser.DTD; import javax.swing.text.html.parser.Parser; import javax.swing.text.html.parser.TagElement; import java.io.*; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * User: vbedrosova * Date: 02.11.2009 * Time: 14:04:31 */ class BugCollection { private static final String FINDBUGS_XML = "findbugs.xml"; private static final String MESSAGES_XML = "messages.xml"; private Map myCategories; private Map myBugPatterns; @NotNull private final BaseServerLoggerFacade myLogger; @NotNull private static DetailsParser DETAILS_PARSER; static { try { DETAILS_PARSER = new DetailsParser(DTD.getDTD("")); } catch (IOException e) { XmlReportPlugin.LOG.error("Couldn't create empty DTD", e); } } public BugCollection(@NotNull BaseServerLoggerFacade logger) { myLogger = logger; } public Map getCategories() { return myCategories; } public Map getPatterns() { return myBugPatterns; } public void loadBundledPatterns() { XmlReportPlugin.LOG.debug("Loading bundled bug patterns"); myCategories = new HashMap(); myBugPatterns = new HashMap(); try { parse(new FindBugsHandler(), new InputSource(this.getClass().getResourceAsStream("findbugs.xml"))); parse(new MessagesHandler(), new InputSource(this.getClass().getResourceAsStream("messages.xml"))); } catch (Exception e) { XmlReportPlugin.LOG.error("Couldn't load bug patterns from bundled findbugs.xml and messages.xml", e); } } public void loadPatternsFromFindBugs(@NotNull File findBugsHome) { XmlReportPlugin.LOG.debug("Loading bug patterns from FindBugs home " + findBugsHome.getAbsolutePath()); myCategories = new HashMap(); myBugPatterns = new HashMap(); final File corePlugin = new File(findBugsHome.getAbsolutePath() + File.separator + "lib", "findbugs.jar"); if (!corePlugin.exists()) { myLogger.warning("Couldn't find plugin descriptor for FindBugs \"core\" plugin " + corePlugin.getAbsolutePath() + ". Ensure specified FindBugs home path is correct."); } load(corePlugin); final File pluginFolder = new File(findBugsHome, "plugin"); final File[] plugins = pluginFolder.listFiles(); if ((plugins == null) || (plugins.length == 0)) { return; } for (final File p : plugins) { if (!p.getAbsolutePath().endsWith(".jar")) { continue; } load(p); } } private void load(@NotNull File file) { XmlReportPlugin.LOG.debug("Loading bug patterns from plugin jar " + file.getAbsolutePath()); JarFile jar = null; try { jar = new JarFile(file); final JarEntry findugs = jar.getJarEntry(FINDBUGS_XML); final JarEntry messages = jar.getJarEntry(MESSAGES_XML); if (findugs == null) { XmlReportPlugin.LOG.warn("Couldn't find findbugs.xml in plugin jar " + file.getAbsolutePath()); return; } if (messages == null) { XmlReportPlugin.LOG.warn("Couldn't find messages.xml in plugin jar " + file.getAbsolutePath()); return; } parse(new FindBugsHandler(), new InputSource(jar.getInputStream(findugs))); parse(new MessagesHandler(), new InputSource(jar.getInputStream(messages))); } catch (Exception e) { XmlReportPlugin.LOG.error("Couldn't load bug patterns from findbugs.xml and messages.xml from plugin jar " + file.getAbsolutePath(), e); } finally { try { if (jar != null) { jar.close(); } } catch (IOException e) { XmlReportPlugin.LOG.error("Couldn't close plugin jar " + file.getAbsolutePath(), e); } } } private final class FindBugsHandler extends DefaultHandler { public static final String BUG_PATTERN = "BugPattern"; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (BUG_PATTERN.equals(localName)) { myBugPatterns.put(attributes.getValue("type"), new Pattern(attributes.getValue("category"))); } } } private final class MessagesHandler extends DefaultHandler { public static final String CATEGORY = "BugCategory"; public static final String DESCRIPTION = "Description"; public static final String SHORT_DESCRIPTION = "ShortDescription"; public static final String DETAILS = "Details"; public static final String BUG_PATTERN = "BugPattern"; private Category myCurrentCategory; private Pattern myCurrentPattern; private final StringBuffer myCData = new StringBuffer(); public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (CATEGORY.equals(localName)) { myCurrentCategory = new Category(); myCategories.put(attributes.getValue("category"), myCurrentCategory); } else if (BUG_PATTERN.equals(localName)) { myCurrentPattern = myBugPatterns.get(attributes.getValue("type")); } } public void endElement(String uri, String localName, String qName) throws SAXException { if (CATEGORY.equals(localName)) { myCurrentCategory = null; } else if (BUG_PATTERN.equals(localName)) { myCurrentPattern = null; } else if (DESCRIPTION.equals(localName)) { myCurrentCategory.setName(XmlReportParser.formatText(myCData)); } else if (SHORT_DESCRIPTION.equals(localName)) { if (myCurrentPattern != null) { myCurrentPattern.setName(XmlReportParser.formatText(myCData)); } } else if (DETAILS.equals(localName)) { if (myCurrentCategory != null) { final String text = XmlReportParser.formatText(myCData); myCurrentCategory.setDescription(text.substring(0, 1).toUpperCase() + text.substring(1)); } else if (myCurrentPattern != null) { myCurrentPattern.setDescription(formatText(myCData)); } } myCData.delete(0, myCData.length()); } public void characters(char ch[], int start, int length) throws SAXException { myCData.append(ch, start, length); } } private static String formatText(@NotNull StringBuffer sb) { // return XmlReportParser.formatText(sb).replaceAll("\"]*(\"[^\"]*\")?)*>|&\\p{Alnum}*;", ""); try { DETAILS_PARSER.parse(new BufferedReader(new StringReader(sb.toString()))); } catch (IOException e) { XmlReportPlugin.LOG.error("Couldn't format html description to text", e); } return DETAILS_PARSER.getText().replace(" ", ""); } private void parse(@NotNull DefaultHandler handler, @NotNull InputSource source) throws Exception { XmlReportParser.createXmlReader(handler, handler, false).parse(source); } public static final class Category { @NotNull private String myName; @NotNull private String myDescription; public Category() { this("Unknown category"); } private Category(String name) { this(name, "No description"); } private Category(String name, String description) { myName = name; myDescription = description; } @NotNull public String getName() { return myName; } public void setName(@NotNull String name) { myName = name; } @NotNull public String getDescription() { return myDescription; } public void setDescription(@NotNull String description) { myDescription = description; } } public static final class Pattern { private String myName; private final String myCategory; private String myDescription; public Pattern() { this("Unknown category"); } public Pattern(String category) { this("Unknown pattern", category, "No description"); } private Pattern(String name, String category, String description) { myName = name; myCategory = category; myDescription = description; } public String getName() { return myName; } public void setName(String name) { myName = name; } public String getDescription() { return myDescription; } public void setDescription(String description) { myDescription = description; } public String getCategory() { return myCategory; } } private static final class DetailsParser extends Parser { private final StringBuffer myStringBuffer = new StringBuffer(); public DetailsParser(DTD dtd) { super(dtd); } public void parse(Reader in) throws java.io.IOException { myStringBuffer.delete(0, myStringBuffer.length()); super.parse(in); } protected void handleText(char text[]) { myStringBuffer.append(text); } protected void handleEndTag(TagElement tag) { myStringBuffer.append(" "); } public String getText() { return myStringBuffer.toString(); } } }