0001: /*
0002: * FindBugs - Find bugs in Java programs
0003: * Copyright (C) 2003-2005 University of Maryland
0004: *
0005: * This library is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU Lesser General Public
0007: * License as published by the Free Software Foundation; either
0008: * version 2.1 of the License, or (at your option) any later version.
0009: *
0010: * This library is distributed in the hope that it will be useful,
0011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0013: * Lesser General Public License for more details.
0014: *
0015: * You should have received a copy of the GNU Lesser General Public
0016: * License along with this library; if not, write to the Free Software
0017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018: */
0019:
0020: package edu.umd.cs.findbugs;
0021:
0022: import java.io.BufferedInputStream;
0023: import java.io.BufferedOutputStream;
0024: import java.io.BufferedReader;
0025: import java.io.ByteArrayInputStream;
0026: import java.io.File;
0027: import java.io.FileInputStream;
0028: import java.io.FileOutputStream;
0029: import java.io.IOException;
0030: import java.io.InputStream;
0031: import java.io.OutputStream;
0032: import java.io.Reader;
0033: import java.io.StringWriter;
0034: import java.math.BigInteger;
0035: import java.security.MessageDigest;
0036: import java.util.Collection;
0037: import java.util.Comparator;
0038: import java.util.HashMap;
0039: import java.util.HashSet;
0040: import java.util.Iterator;
0041: import java.util.LinkedHashSet;
0042: import java.util.LinkedList;
0043: import java.util.List;
0044: import java.util.Map;
0045: import java.util.Set;
0046: import java.util.SortedSet;
0047: import java.util.TreeMap;
0048: import java.util.TreeSet;
0049: import java.util.zip.GZIPInputStream;
0050:
0051: import javax.xml.transform.TransformerException;
0052:
0053: import org.dom4j.Document;
0054: import org.dom4j.DocumentException;
0055: import org.dom4j.DocumentFactory;
0056: import org.xml.sax.InputSource;
0057: import org.xml.sax.SAXException;
0058: import org.xml.sax.SAXParseException;
0059: import org.xml.sax.XMLReader;
0060: import org.xml.sax.helpers.XMLReaderFactory;
0061:
0062: import edu.umd.cs.findbugs.annotations.CheckForNull;
0063: import edu.umd.cs.findbugs.annotations.NonNull;
0064: import edu.umd.cs.findbugs.ba.AnalysisContext;
0065: import edu.umd.cs.findbugs.ba.MissingClassException;
0066: import edu.umd.cs.findbugs.model.ClassFeatureSet;
0067: import edu.umd.cs.findbugs.util.Util;
0068: import edu.umd.cs.findbugs.xml.Dom4JXMLOutput;
0069: import edu.umd.cs.findbugs.xml.OutputStreamXMLOutput;
0070: import edu.umd.cs.findbugs.xml.XMLAttributeList;
0071: import edu.umd.cs.findbugs.xml.XMLOutput;
0072: import edu.umd.cs.findbugs.xml.XMLOutputUtil;
0073:
0074: /**
0075: * An implementation of {@link BugCollection} that keeps the BugInstances
0076: * sorted by class (using the native comparison ordering of BugInstance's
0077: * compareTo() method as a tie-breaker).
0078: *
0079: * @see BugInstance
0080: * @author David Hovemeyer
0081: */
0082: public class SortedBugCollection implements BugCollection {
0083: long analysisTimestamp = System.currentTimeMillis();
0084: String analysisVersion = Version.RELEASE;
0085: private boolean withMessages = false;
0086:
0087: private static final boolean REPORT_SUMMARY_HTML = SystemProperties
0088: .getBoolean("findbugs.report.SummaryHTML");
0089:
0090: public long getAnalysisTimestamp() {
0091: return analysisTimestamp;
0092: }
0093:
0094: public void setAnalysisTimestamp(long timestamp) {
0095: analysisTimestamp = timestamp;
0096: }
0097:
0098: /**
0099: * Add a Collection of BugInstances to this BugCollection object.
0100: * This just calls add(BugInstance) for each instance in the input collection.
0101: *
0102: * @param collection the Collection of BugInstances to add
0103: */
0104: public void addAll(Collection<BugInstance> collection) {
0105: for (BugInstance aCollection : collection) {
0106: add(aCollection);
0107: }
0108: }
0109:
0110: /**
0111: * Add a Collection of BugInstances to this BugCollection object.
0112: *
0113: * @param collection the Collection of BugInstances to add
0114: * @param updateActiveTime true if active time of added BugInstances should
0115: * be updated to match collection: false if not
0116: */
0117: public void addAll(Collection<BugInstance> collection,
0118: boolean updateActiveTime) {
0119: for (BugInstance warning : collection) {
0120: add(warning, updateActiveTime);
0121: }
0122: }
0123:
0124: /**
0125: * Add a BugInstance to this BugCollection.
0126: * This just calls add(bugInstance, true).
0127: *
0128: * @param bugInstance the BugInstance
0129: * @return true if the BugInstance was added, or false if a matching
0130: * BugInstance was already in the BugCollection
0131: */
0132: public boolean add(BugInstance bugInstance) {
0133: return add(bugInstance, true);
0134: }
0135:
0136: /**
0137: * Add an analysis error.
0138: *
0139: * @param message the error message
0140: */
0141: public void addError(String message) {
0142: addError(message, null);
0143: }
0144:
0145: /**
0146: * Get the current AppVersion.
0147: */
0148: public AppVersion getCurrentAppVersion() {
0149: return new AppVersion(getSequenceNumber()).setReleaseName(
0150: getReleaseName()).setTimestamp(getTimestamp())
0151: .setNumClasses(getProjectStats().getNumClasses())
0152: .setCodeSize(getProjectStats().getCodeSize());
0153: }
0154:
0155: /**
0156: * Read XML data from given file into this object,
0157: * populating given Project as a side effect.
0158: *
0159: * @param fileName name of the file to read
0160: * @param project the Project
0161: */
0162: public void readXML(String fileName, Project project)
0163: throws IOException, DocumentException {
0164: readXML(new File(fileName), project);
0165: }
0166:
0167: /**
0168: * Read XML data from given file into this object,
0169: * populating given Project as a side effect.
0170: *
0171: * @param file the file
0172: * @param project the Project
0173: */
0174: public void readXML(File file, Project project) throws IOException,
0175: DocumentException {
0176: project.setCurrentWorkingDirectory(file.getParentFile());
0177: InputStream in = new BufferedInputStream(new FileInputStream(
0178: file));
0179: if (file.getName().endsWith(".gz"))
0180: in = new GZIPInputStream(in);
0181: readXML(in, project, file);
0182: }
0183:
0184: /**
0185: * Read XML data from given input stream into this
0186: * object, populating the Project as a side effect.
0187: * An attempt will be made to close the input stream
0188: * (even if an exception is thrown).
0189: *
0190: * @param in the InputStream
0191: * @param project the Project
0192: */
0193: public void readXML(InputStream in, Project project, File base)
0194: throws IOException, DocumentException {
0195: if (in == null)
0196: throw new IllegalArgumentException();
0197:
0198: try {
0199: if (project == null)
0200: throw new IllegalArgumentException();
0201: doReadXML(in, project, base);
0202: } finally {
0203: in.close();
0204: }
0205: }
0206:
0207: public void readXML(InputStream in, Project project)
0208: throws IOException, DocumentException {
0209: doReadXML(in, project, null);
0210:
0211: }
0212:
0213: private void doReadXML(InputStream in, Project project, File base)
0214: throws IOException, DocumentException {
0215:
0216: checkInputStream(in);
0217:
0218: try {
0219: SAXBugCollectionHandler handler = new SAXBugCollectionHandler(
0220: this , project, base);
0221:
0222: XMLReader xr = null;
0223: if (true)
0224: try {
0225: xr = XMLReaderFactory.createXMLReader();
0226: } catch (SAXException e) {
0227: AnalysisContext.logError(
0228: "Couldn't create XMLReaderFactory", e);
0229: }
0230:
0231: if (xr == null) {
0232: xr = new org.dom4j.io.aelfred.SAXDriver();
0233: }
0234: xr.setContentHandler(handler);
0235: xr.setErrorHandler(handler);
0236:
0237: Reader reader = Util.getReader(in);
0238:
0239: xr.parse(new InputSource(reader));
0240: } catch (SAXParseException e) {
0241: throw new DocumentException("Parse error at line "
0242: + e.getLineNumber() + " : " + e.getColumnNumber(),
0243: e);
0244: } catch (SAXException e) {
0245: // FIXME: throw SAXException from method?
0246: throw new DocumentException("Sax error ", e);
0247: } finally {
0248: in.close();
0249: }
0250:
0251: // Presumably, project is now up-to-date
0252: project.setModified(false);
0253: }
0254:
0255: /**
0256: * Write this BugCollection to a file as XML.
0257: *
0258: * @param fileName the file to write to
0259: * @param project the Project from which the BugCollection was generated
0260: */
0261: public void writeXML(String fileName, Project project)
0262: throws IOException {
0263: BufferedOutputStream out = new BufferedOutputStream(
0264: new FileOutputStream(fileName));
0265: writeXML(out, project);
0266: }
0267:
0268: /**
0269: * Write this BugCollection to a file as XML.
0270: *
0271: * @param file the file to write to
0272: * @param project the Project from which the BugCollection was generated
0273: */
0274: public void writeXML(File file, Project project) throws IOException {
0275: BufferedOutputStream out = new BufferedOutputStream(
0276: new FileOutputStream(file));
0277: writeXML(out, project);
0278: }
0279:
0280: /**
0281: * Convert the BugCollection into a dom4j Document object.
0282: *
0283: * @param project the Project from which the BugCollection was generated
0284: * @return the Document representing the BugCollection as a dom4j tree
0285: */
0286: public Document toDocument(Project project) {
0287: if (project == null)
0288: throw new NullPointerException("No project");
0289: DocumentFactory docFactory = new DocumentFactory();
0290: Document document = docFactory.createDocument();
0291: Dom4JXMLOutput treeBuilder = new Dom4JXMLOutput(document);
0292:
0293: try {
0294: writeXML(treeBuilder, project);
0295: } catch (IOException e) {
0296: // Can't happen
0297: }
0298:
0299: return document;
0300: }
0301:
0302: /**
0303: * Write the BugCollection to given output stream as XML.
0304: * The output stream will be closed, even if an exception is thrown.
0305: *
0306: * @param out the OutputStream to write to
0307: * @param project the Project from which the BugCollection was generated
0308: */
0309: public void writeXML(OutputStream out, Project project)
0310: throws IOException {
0311: XMLOutput xmlOutput;
0312: if (project == null)
0313: throw new NullPointerException("No project");
0314: if (withMessages)
0315: xmlOutput = new OutputStreamXMLOutput(out,
0316: "http://findbugs.sourceforge.net/xsl/default.xsl");
0317: else
0318: xmlOutput = new OutputStreamXMLOutput(out);
0319:
0320: writeXML(xmlOutput, project);
0321: }
0322:
0323: public void writePrologue(XMLOutput xmlOutput, Project project)
0324: throws IOException {
0325: xmlOutput.beginDocument();
0326: xmlOutput.openTag(ROOT_ELEMENT_NAME, new XMLAttributeList()
0327: .addAttribute("version", analysisVersion)
0328: .addAttribute("sequence",
0329: String.valueOf(getSequenceNumber()))
0330: .addAttribute("timestamp",
0331: String.valueOf(getTimestamp())).addAttribute(
0332: "analysisTimestamp",
0333: String.valueOf(getAnalysisTimestamp()))
0334:
0335: .addAttribute("release", getReleaseName()));
0336: project.writeXML(xmlOutput);
0337: }
0338:
0339: private String getQuickInstanceHash(BugInstance bugInstance) {
0340: String hash = bugInstance.getInstanceHash();
0341: if (hash != null)
0342: return hash;
0343: MessageDigest digest = null;
0344: try {
0345: digest = MessageDigest.getInstance("MD5");
0346: } catch (Exception e2) {
0347: // OK, we won't digest
0348: assert true;
0349: }
0350: hash = bugInstance.getInstanceKey();
0351: if (digest != null) {
0352: byte[] data = digest.digest(hash.getBytes());
0353: String tmp = new BigInteger(1, data).toString(16);
0354: if (false)
0355: System.out.println(hash + " -> " + tmp);
0356: hash = tmp;
0357: }
0358: bugInstance.setInstanceHash(hash);
0359: return hash;
0360: }
0361:
0362: public void computeBugHashes() {
0363: if (preciseHashOccurrenceNumbersAvailable)
0364: return;
0365: invalidateHashes();
0366: MessageDigest digest = null;
0367: try {
0368: digest = MessageDigest.getInstance("MD5");
0369: } catch (Exception e2) {
0370: // OK, we won't digest
0371: }
0372:
0373: HashMap<String, Integer> seen = new HashMap<String, Integer>();
0374:
0375: for (BugInstance bugInstance : getCollection()) {
0376: String hash = bugInstance.getInstanceHash();
0377: if (hash == null) {
0378: hash = bugInstance.getInstanceKey();
0379:
0380: if (digest != null) {
0381: byte[] data = digest.digest(hash.getBytes());
0382: String tmp = new BigInteger(1, data).toString(16);
0383: if (false)
0384: System.out.println(hash + " -> " + tmp);
0385: hash = tmp;
0386: }
0387: bugInstance.setInstanceHash(hash);
0388: }
0389: Integer count = seen.get(hash);
0390: if (count == null) {
0391: bugInstance.setInstanceOccurrenceNum(0);
0392: seen.put(hash, 0);
0393: } else {
0394: bugInstance.setInstanceOccurrenceNum(count + 1);
0395: seen.put(hash, count + 1);
0396: }
0397: }
0398: for (BugInstance bugInstance : getCollection())
0399: bugInstance.setInstanceOccurrenceMax(seen.get(bugInstance
0400: .getInstanceHash()));
0401: preciseHashOccurrenceNumbersAvailable = true;
0402: }
0403:
0404: /**
0405: * Write the BugCollection to an XMLOutput object.
0406: * The finish() method of the XMLOutput object is guaranteed
0407: * to be called.
0408: *
0409: * <p>
0410: * To write the SummaryHTML element, set property
0411: * findbugs.report.SummaryHTML to "true".
0412: * </p>
0413: *
0414: * @param xmlOutput the XMLOutput object
0415: * @param project the Project from which the BugCollection was generated
0416: */
0417: public void writeXML(XMLOutput xmlOutput, @NonNull
0418: Project project) throws IOException {
0419: if (project == null)
0420: throw new NullPointerException("No project");
0421: try {
0422: writePrologue(xmlOutput, project);
0423: if (withMessages)
0424: computeBugHashes();
0425:
0426: // Write BugInstances
0427: for (BugInstance bugInstance : getCollection())
0428: bugInstance.writeXML(xmlOutput, withMessages);
0429:
0430: writeEpilogue(xmlOutput);
0431: } finally {
0432: xmlOutput.finish();
0433: }
0434: }
0435:
0436: public void writeEpilogue(XMLOutput xmlOutput) throws IOException {
0437: if (withMessages) {
0438: writeBugCategories(xmlOutput);
0439: writeBugPatterns(xmlOutput);
0440: writeBugCodes(xmlOutput);
0441: }
0442: // Errors, missing classes
0443: emitErrors(xmlOutput);
0444:
0445: // Statistics
0446: getProjectStats().writeXML(xmlOutput);
0447:
0448: // // Class and method hashes
0449: // xmlOutput.openTag(CLASS_HASHES_ELEMENT_NAME);
0450: // for (Iterator<ClassHash> i = classHashIterator(); i.hasNext();) {
0451: // ClassHash classHash = i.next();
0452: // classHash.writeXML(xmlOutput);
0453: // }
0454: // xmlOutput.closeTag(CLASS_HASHES_ELEMENT_NAME);
0455:
0456: // Class features
0457: xmlOutput.openTag("ClassFeatures");
0458: for (Iterator<ClassFeatureSet> i = classFeatureSetIterator(); i
0459: .hasNext();) {
0460: ClassFeatureSet classFeatureSet = i.next();
0461: classFeatureSet.writeXML(xmlOutput);
0462: }
0463: xmlOutput.closeTag("ClassFeatures");
0464:
0465: // AppVersions
0466: xmlOutput.openTag(HISTORY_ELEMENT_NAME);
0467: for (Iterator<AppVersion> i = appVersionIterator(); i.hasNext();) {
0468: AppVersion appVersion = i.next();
0469: appVersion.writeXML(xmlOutput);
0470: }
0471: xmlOutput.closeTag(HISTORY_ELEMENT_NAME);
0472:
0473: // Summary HTML
0474: if (REPORT_SUMMARY_HTML) {
0475: String html = getSummaryHTML();
0476: if (html != null && !html.equals("")) {
0477: xmlOutput.openTag(SUMMARY_HTML_ELEMENT_NAME);
0478: xmlOutput.writeCDATA(html);
0479: xmlOutput.closeTag(SUMMARY_HTML_ELEMENT_NAME);
0480: }
0481: }
0482:
0483: xmlOutput.closeTag(ROOT_ELEMENT_NAME);
0484: }
0485:
0486: private void writeBugPatterns(XMLOutput xmlOutput)
0487: throws IOException {
0488: // Find bug types reported
0489: Set<String> bugTypeSet = new HashSet<String>();
0490: for (Iterator<BugInstance> i = iterator(); i.hasNext();) {
0491: BugInstance bugInstance = i.next();
0492: BugPattern bugPattern = bugInstance.getBugPattern();
0493: if (bugPattern != null) {
0494: bugTypeSet.add(bugPattern.getType());
0495: }
0496: }
0497: // Emit element describing each reported bug pattern
0498: for (String bugType : bugTypeSet) {
0499: BugPattern bugPattern = I18N.instance().lookupBugPattern(
0500: bugType);
0501: if (bugPattern == null)
0502: continue;
0503:
0504: XMLAttributeList attributeList = new XMLAttributeList();
0505: attributeList.addAttribute("type", bugType);
0506: attributeList
0507: .addAttribute("abbrev", bugPattern.getAbbrev());
0508: attributeList.addAttribute("category", bugPattern
0509: .getCategory());
0510: if (bugPattern.getCWEid() != 0) {
0511: attributeList.addAttribute("cweid", Integer
0512: .toString(bugPattern.getCWEid()));
0513: }
0514: xmlOutput.openTag("BugPattern", attributeList);
0515:
0516: xmlOutput.openTag("ShortDescription");
0517: xmlOutput.writeText(bugPattern.getShortDescription());
0518: xmlOutput.closeTag("ShortDescription");
0519:
0520: xmlOutput.openTag("Details");
0521: xmlOutput.writeCDATA(bugPattern.getDetailText());
0522: xmlOutput.closeTag("Details");
0523:
0524: xmlOutput.closeTag("BugPattern");
0525: }
0526: }
0527:
0528: private void writeBugCodes(XMLOutput xmlOutput) throws IOException {
0529: // Find bug codes reported
0530: Set<String> bugCodeSet = new HashSet<String>();
0531: for (Iterator<BugInstance> i = iterator(); i.hasNext();) {
0532: BugInstance bugInstance = i.next();
0533: String bugCode = bugInstance.getAbbrev();
0534: if (bugCode != null) {
0535: bugCodeSet.add(bugCode);
0536: }
0537: }
0538: // Emit element describing each reported bug code
0539: for (String bugCodeAbbrev : bugCodeSet) {
0540: BugCode bugCode = I18N.instance().getBugCode(bugCodeAbbrev);
0541: String bugCodeDescription = bugCode.getDescription();
0542: if (bugCodeDescription == null)
0543: continue;
0544:
0545: XMLAttributeList attributeList = new XMLAttributeList();
0546: attributeList.addAttribute("abbrev", bugCodeAbbrev);
0547: if (bugCode.getCWEid() != 0) {
0548: attributeList.addAttribute("cweid", Integer
0549: .toString(bugCode.getCWEid()));
0550: }
0551: xmlOutput.openTag("BugCode", attributeList);
0552:
0553: xmlOutput.openTag("Description");
0554: xmlOutput.writeText(bugCodeDescription);
0555: xmlOutput.closeTag("Description");
0556:
0557: xmlOutput.closeTag("BugCode");
0558: }
0559: }
0560:
0561: private void writeBugCategories(XMLOutput xmlOutput)
0562: throws IOException {
0563: // Find bug categories reported
0564: Set<String> bugCatSet = new HashSet<String>();
0565: for (Iterator<BugInstance> i = iterator(); i.hasNext();) {
0566: BugInstance bugInstance = i.next();
0567: BugPattern bugPattern = bugInstance.getBugPattern();
0568: if (bugPattern != null) {
0569: bugCatSet.add(bugPattern.getCategory());
0570: }
0571: }
0572: // Emit element describing each reported bug code
0573: for (String bugCat : bugCatSet) {
0574: String bugCatDescription = I18N.instance()
0575: .getBugCategoryDescription(bugCat);
0576: if (bugCatDescription == null)
0577: continue;
0578:
0579: XMLAttributeList attributeList = new XMLAttributeList();
0580: attributeList.addAttribute("category", bugCat);
0581:
0582: xmlOutput.openTag("BugCategory", attributeList);
0583:
0584: xmlOutput.openTag("Description");
0585: xmlOutput.writeText(bugCatDescription);
0586: xmlOutput.closeTag("Description");
0587:
0588: xmlOutput.closeTag("BugCategory");
0589: }
0590: }
0591:
0592: private void emitErrors(XMLOutput xmlOutput) throws IOException {
0593: //System.err.println("Writing errors to XML output");
0594:
0595: xmlOutput.openTag(ERRORS_ELEMENT_NAME);
0596:
0597: // Emit Error elements describing analysis errors
0598: for (Iterator<AnalysisError> i = errorIterator(); i.hasNext();) {
0599: AnalysisError error = i.next();
0600: xmlOutput.openTag(ERROR_ELEMENT_NAME);
0601:
0602: xmlOutput.openTag(ERROR_MESSAGE_ELEMENT_NAME);
0603: xmlOutput.writeText(error.getMessage());
0604: xmlOutput.closeTag(ERROR_MESSAGE_ELEMENT_NAME);
0605:
0606: if (error.getExceptionMessage() != null) {
0607: xmlOutput.openTag(ERROR_EXCEPTION_ELEMENT_NAME);
0608: xmlOutput.writeText(error.getExceptionMessage());
0609: xmlOutput.closeTag(ERROR_EXCEPTION_ELEMENT_NAME);
0610:
0611: String stackTrace[] = error.getStackTrace();
0612: if (stackTrace != null) {
0613: for (String aStackTrace : stackTrace) {
0614: xmlOutput
0615: .openTag(ERROR_STACK_TRACE_ELEMENT_NAME);
0616: xmlOutput.writeText(aStackTrace);
0617: xmlOutput
0618: .closeTag(ERROR_STACK_TRACE_ELEMENT_NAME);
0619: }
0620: }
0621:
0622: if (false && error.getNestedExceptionMessage() != null) {
0623: xmlOutput.openTag(ERROR_EXCEPTION_ELEMENT_NAME);
0624: xmlOutput.writeText(error
0625: .getNestedExceptionMessage());
0626: xmlOutput.closeTag(ERROR_EXCEPTION_ELEMENT_NAME);
0627:
0628: stackTrace = error.getNestedStackTrace();
0629: if (stackTrace != null) {
0630: for (String aStackTrace : stackTrace) {
0631: xmlOutput
0632: .openTag(ERROR_STACK_TRACE_ELEMENT_NAME);
0633: xmlOutput.writeText(aStackTrace);
0634: xmlOutput
0635: .closeTag(ERROR_STACK_TRACE_ELEMENT_NAME);
0636: }
0637: }
0638: }
0639: }
0640: xmlOutput.closeTag(ERROR_ELEMENT_NAME);
0641: }
0642:
0643: // Emit missing classes
0644: XMLOutputUtil.writeElementList(xmlOutput,
0645: MISSING_CLASS_ELEMENT_NAME, missingClassIterator());
0646:
0647: xmlOutput.closeTag(ERRORS_ELEMENT_NAME);
0648: }
0649:
0650: private void checkInputStream(InputStream in) throws IOException {
0651: if (in.markSupported()) {
0652: byte[] buf = new byte[200];
0653: in.mark(buf.length);
0654:
0655: int numRead = 0;
0656: while (numRead < buf.length) {
0657: int n = in.read(buf, numRead, buf.length - numRead);
0658: if (n < 0)
0659: throw new IOException(
0660: "XML does not contain saved bug data");
0661: numRead += n;
0662: }
0663:
0664: in.reset();
0665:
0666: BufferedReader reader = new BufferedReader(Util
0667: .getReader(new ByteArrayInputStream(buf)));
0668: try {
0669: String line;
0670: while ((line = reader.readLine()) != null) {
0671: if (line.startsWith("<BugCollection"))
0672: return;
0673: }
0674: } finally {
0675: reader.close();
0676: }
0677:
0678: throw new IOException("XML does not contain saved bug data");
0679: }
0680: }
0681:
0682: /**
0683: * Clone all of the BugInstance objects in the source Collection
0684: * and add them to the destination Collection.
0685: *
0686: * @param dest the destination Collection
0687: * @param source the source Collection
0688: */
0689: public static void cloneAll(Collection<BugInstance> dest,
0690: Collection<BugInstance> source) {
0691: for (BugInstance obj : source) {
0692: dest.add((BugInstance) obj.clone());
0693: }
0694: }
0695:
0696: public static class BugInstanceComparator implements
0697: Comparator<BugInstance> {
0698: private BugInstanceComparator() {
0699: }
0700:
0701: public int compare(BugInstance lhs, BugInstance rhs) {
0702: ClassAnnotation lca = lhs.getPrimaryClass();
0703: ClassAnnotation rca = rhs.getPrimaryClass();
0704: if (lca == null || rca == null)
0705: throw new IllegalStateException(
0706: "null class annotation: " + lca + "," + rca);
0707: int cmp = lca.getClassName().compareTo(rca.getClassName());
0708: if (cmp != 0)
0709: return cmp;
0710: return lhs.compareTo(rhs);
0711: }
0712:
0713: public static final BugInstanceComparator instance = new BugInstanceComparator();
0714: }
0715:
0716: public static class MultiversionBugInstanceComparator extends
0717: BugInstanceComparator {
0718: @Override
0719: public int compare(BugInstance lhs, BugInstance rhs) {
0720: int result = super .compare(lhs, rhs);
0721: if (result != 0)
0722: return result;
0723: long diff = lhs.getFirstVersion() - rhs.getFirstVersion();
0724: if (diff == 0)
0725: diff = lhs.getLastVersion() - rhs.getLastVersion();
0726: if (diff < 0)
0727: return -1;
0728: if (diff > 0)
0729: return 1;
0730: return 0;
0731: }
0732:
0733: public static final MultiversionBugInstanceComparator instance = new MultiversionBugInstanceComparator();
0734: }
0735:
0736: private Comparator<BugInstance> comparator;
0737: private TreeSet<BugInstance> bugSet;
0738: private LinkedHashSet<AnalysisError> errorList;
0739: private TreeSet<String> missingClassSet;
0740: @CheckForNull
0741: private String summaryHTML;
0742: private ProjectStats projectStats;
0743: // private Map<String, ClassHash> classHashMap;
0744: private Map<String, ClassFeatureSet> classFeatureSetMap;
0745: private List<AppVersion> appVersionList;
0746:
0747: private boolean preciseHashOccurrenceNumbersAvailable = false;
0748: /**
0749: * Sequence number of the most-recently analyzed version
0750: * of the code.
0751: */
0752: private long sequence;
0753: /**
0754: * Release name of the analyzed application.
0755: */
0756: private String releaseName;
0757: /**
0758: * Current analysis timestamp.
0759: */
0760: private long timestamp;
0761:
0762: /**
0763: * Constructor.
0764: * Creates an empty object.
0765: */
0766: public SortedBugCollection() {
0767: this (new ProjectStats());
0768: }
0769:
0770: /**
0771: * Constructor.
0772: * Creates an empty object.
0773: */
0774: public SortedBugCollection(Comparator<BugInstance> comparator) {
0775: this (new ProjectStats(), comparator);
0776: }
0777:
0778: /**
0779: * Constructor.
0780: * Creates an empty object given an existing ProjectStats.
0781: *
0782: * @param projectStats the ProjectStats
0783: */
0784: public SortedBugCollection(ProjectStats projectStats) {
0785: this (projectStats, MultiversionBugInstanceComparator.instance);
0786: }
0787:
0788: /**
0789: * Constructor.
0790: * Creates an empty object given an existing ProjectStats.
0791: *
0792: * @param projectStats the ProjectStats
0793: * @param comparator to use for sorting bug instances
0794: */
0795: public SortedBugCollection(ProjectStats projectStats,
0796: Comparator<BugInstance> comparator) {
0797: this .projectStats = projectStats;
0798: this .comparator = comparator;
0799: bugSet = new TreeSet<BugInstance>(comparator);
0800: errorList = new LinkedHashSet<AnalysisError>() {
0801: @Override
0802: public boolean add(AnalysisError a) {
0803: if (this .size() > 1000)
0804: return false;
0805: return super .add(a);
0806: }
0807: };
0808: missingClassSet = new TreeSet<String>();
0809: summaryHTML = null;
0810: classFeatureSetMap = new TreeMap<String, ClassFeatureSet>();
0811: sequence = 0L;
0812: appVersionList = new LinkedList<AppVersion>();
0813: releaseName = "";
0814: timestamp = -1L;
0815: }
0816:
0817: public boolean add(BugInstance bugInstance, boolean updateActiveTime) {
0818: preciseHashOccurrenceNumbersAvailable = false;
0819: if (updateActiveTime) {
0820: bugInstance.setFirstVersion(sequence);
0821: }
0822:
0823: return bugSet.add(bugInstance);
0824: }
0825:
0826: private void invalidateHashes() {
0827: preciseHashOccurrenceNumbersAvailable = false;
0828: }
0829:
0830: public boolean remove(BugInstance bugInstance) {
0831: invalidateHashes();
0832: return bugSet.remove(bugInstance);
0833: }
0834:
0835: public Iterator<BugInstance> iterator() {
0836: return bugSet.iterator();
0837: }
0838:
0839: public Collection<BugInstance> getCollection() {
0840: return bugSet;
0841: }
0842:
0843: public void addError(String message, Throwable exception) {
0844: if (exception instanceof MissingClassException) {
0845: MissingClassException e = (MissingClassException) exception;
0846: addMissingClass(AbstractBugReporter.getMissingClassName(e
0847: .getClassNotFoundException()));
0848: return;
0849: }
0850: if (exception instanceof ClassNotFoundException) {
0851: ClassNotFoundException e = (ClassNotFoundException) exception;
0852: addMissingClass(AbstractBugReporter.getMissingClassName(e));
0853: return;
0854: }
0855: if (exception instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
0856: edu.umd.cs.findbugs.classfile.MissingClassException e = (edu.umd.cs.findbugs.classfile.MissingClassException) exception;
0857: addMissingClass(AbstractBugReporter.getMissingClassName(e
0858: .toClassNotFoundException()));
0859: return;
0860: }
0861: errorList.add(new AnalysisError(message, exception));
0862: }
0863:
0864: public void addError(AnalysisError error) {
0865: errorList.add(error);
0866: }
0867:
0868: public void addMissingClass(String className) {
0869: if (className.startsWith("[")) {
0870: assert false : "Bad class name " + className;
0871: return;
0872: }
0873: missingClassSet.add(className);
0874: }
0875:
0876: public Iterator<AnalysisError> errorIterator() {
0877: return errorList.iterator();
0878: }
0879:
0880: public Iterator<String> missingClassIterator() {
0881: return missingClassSet.iterator();
0882: }
0883:
0884: public boolean contains(BugInstance bugInstance) {
0885: return bugSet.contains(bugInstance);
0886: }
0887:
0888: public BugInstance getMatching(BugInstance bugInstance) {
0889: SortedSet<BugInstance> tailSet = bugSet.tailSet(bugInstance);
0890: if (tailSet.isEmpty())
0891: return null;
0892: BugInstance first = tailSet.first();
0893: return bugInstance.equals(first) ? first : null;
0894: }
0895:
0896: public String getSummaryHTML() throws IOException {
0897: if (summaryHTML == null) {
0898: try {
0899: StringWriter writer = new StringWriter();
0900: ProjectStats stats = getProjectStats();
0901: stats.transformSummaryToHTML(writer);
0902: summaryHTML = writer.toString();
0903: } catch (final TransformerException e) {
0904: IOException ioe = new IOException(
0905: "Couldn't generate summary HTML");
0906: ioe.initCause(e);
0907: throw ioe;
0908: }
0909: }
0910:
0911: return summaryHTML;
0912: }
0913:
0914: public ProjectStats getProjectStats() {
0915: return projectStats;
0916: }
0917:
0918: /* (non-Javadoc)
0919: * @see edu.umd.cs.findbugs.BugCollection#lookupFromUniqueId(java.lang.String)
0920: */
0921: @Deprecated
0922: public BugInstance lookupFromUniqueId(String uniqueId) {
0923: for (BugInstance bug : bugSet)
0924: if (bug.getInstanceHash().equals(uniqueId))
0925: return bug;
0926: return null;
0927: }
0928:
0929: public long getSequenceNumber() {
0930: return sequence;
0931: }
0932:
0933: public void setSequenceNumber(long sequence) {
0934: this .sequence = sequence;
0935: }
0936:
0937: public SortedBugCollection duplicate() {
0938: SortedBugCollection dup = new SortedBugCollection(
0939: (ProjectStats) projectStats.clone(), comparator);
0940:
0941: SortedBugCollection.cloneAll(dup.bugSet, this .bugSet);
0942: dup.errorList.addAll(this .errorList);
0943: dup.missingClassSet.addAll(this .missingClassSet);
0944: dup.summaryHTML = this .summaryHTML;
0945: // dup.classHashMap.putAll(this.classHashMap);
0946: dup.classFeatureSetMap.putAll(this .classFeatureSetMap);
0947: dup.sequence = this .sequence;
0948: dup.timestamp = this .timestamp;
0949: dup.releaseName = this .releaseName;
0950: for (AppVersion appVersion : appVersionList) {
0951: dup.appVersionList.add((AppVersion) appVersion.clone());
0952: }
0953:
0954: return dup;
0955: }
0956:
0957: /* (non-Javadoc)
0958: * @see edu.umd.cs.findbugs.BugCollection#clearBugInstances()
0959: */
0960:
0961: public void clearBugInstances() {
0962: bugSet.clear();
0963: invalidateHashes();
0964:
0965: }
0966:
0967: /* (non-Javadoc)
0968: * @see edu.umd.cs.findbugs.BugCollection#getReleaseName()
0969: */
0970:
0971: public String getReleaseName() {
0972: if (releaseName == null)
0973: return "";
0974: return releaseName;
0975: }
0976:
0977: /* (non-Javadoc)
0978: * @see edu.umd.cs.findbugs.BugCollection#setReleaseName(java.lang.String)
0979: */
0980:
0981: public void setReleaseName(String releaseName) {
0982: this .releaseName = releaseName;
0983: }
0984:
0985: /* (non-Javadoc)
0986: * @see edu.umd.cs.findbugs.BugCollection#appVersionIterator()
0987: */
0988:
0989: public Iterator<AppVersion> appVersionIterator() {
0990: return appVersionList.iterator();
0991: }
0992:
0993: /* (non-Javadoc)
0994: * @see edu.umd.cs.findbugs.BugCollection#addAppVersion(edu.umd.cs.findbugs.AppVersion)
0995: */
0996:
0997: public void addAppVersion(AppVersion appVersion) {
0998: appVersionList.add(appVersion);
0999: }
1000:
1001: /* (non-Javadoc)
1002: * @see edu.umd.cs.findbugs.BugCollection#clearAppVersions()
1003: */
1004:
1005: public void clearAppVersions() {
1006: appVersionList.clear();
1007: }
1008:
1009: /* (non-Javadoc)
1010: * @see edu.umd.cs.findbugs.BugCollection#createEmptyCollectionWithMetadata()
1011: */
1012:
1013: public SortedBugCollection createEmptyCollectionWithMetadata() {
1014: SortedBugCollection dup = new SortedBugCollection(
1015: (ProjectStats) projectStats.clone(), comparator);
1016: dup.errorList.addAll(this .errorList);
1017: dup.missingClassSet.addAll(this .missingClassSet);
1018: dup.summaryHTML = this .summaryHTML;
1019: dup.classFeatureSetMap.putAll(this .classFeatureSetMap);
1020: dup.sequence = this .sequence;
1021: dup.timestamp = this .timestamp;
1022: dup.releaseName = this .releaseName;
1023: for (AppVersion appVersion : appVersionList) {
1024: dup.appVersionList.add((AppVersion) appVersion.clone());
1025: }
1026:
1027: return dup;
1028: }
1029:
1030: /* (non-Javadoc)
1031: * @see edu.umd.cs.findbugs.BugCollection#setTimestamp(long)
1032: */
1033:
1034: public void setTimestamp(long timestamp) {
1035: this .timestamp = timestamp;
1036: }
1037:
1038: /* (non-Javadoc)
1039: * @see edu.umd.cs.findbugs.BugCollection#getTimestamp()
1040: */
1041:
1042: public long getTimestamp() {
1043: return timestamp;
1044: }
1045:
1046: /* (non-Javadoc)
1047: * @see edu.umd.cs.findbugs.BugCollection#getClassFeatureSet(java.lang.String)
1048: */
1049:
1050: public ClassFeatureSet getClassFeatureSet(String className) {
1051: return classFeatureSetMap.get(className);
1052: }
1053:
1054: /* (non-Javadoc)
1055: * @see edu.umd.cs.findbugs.BugCollection#setClassFeatureSet(edu.umd.cs.findbugs.model.ClassFeatureSet)
1056: */
1057:
1058: public void setClassFeatureSet(ClassFeatureSet classFeatureSet) {
1059: classFeatureSetMap.put(classFeatureSet.getClassName(),
1060: classFeatureSet);
1061: }
1062:
1063: /* (non-Javadoc)
1064: * @see edu.umd.cs.findbugs.BugCollection#classFeatureSetIterator()
1065: */
1066:
1067: public Iterator<ClassFeatureSet> classFeatureSetIterator() {
1068: return classFeatureSetMap.values().iterator();
1069: }
1070:
1071: /* (non-Javadoc)
1072: * @see edu.umd.cs.findbugs.BugCollection#clearClassFeatures()
1073: */
1074: public void clearClassFeatures() {
1075: classFeatureSetMap.clear();
1076: }
1077:
1078: /**
1079: * @param withMessages The withMessages to set.
1080: */
1081: public void setWithMessages(boolean withMessages) {
1082: this .withMessages = withMessages;
1083: }
1084:
1085: /**
1086: * @return Returns the withMessages.
1087: */
1088: public boolean getWithMessages() {
1089: return withMessages;
1090: }
1091:
1092: /* (non-Javadoc)
1093: * @see edu.umd.cs.findbugs.BugCollection#getAppVersionFromSequenceNumber(int)
1094: */
1095: public AppVersion getAppVersionFromSequenceNumber(long target) {
1096: for (AppVersion av : appVersionList)
1097: if (av.getSequenceNumber() == target)
1098: return av;
1099: if (target == this .getSequenceNumber())
1100: return this .getCurrentAppVersion();
1101: return null;
1102: }
1103:
1104: /* (non-Javadoc)
1105: * @see edu.umd.cs.findbugs.BugCollection#findBug(java.lang.String, java.lang.String, int)
1106: */
1107: public BugInstance findBug(String instanceHash, String bugType,
1108: int lineNumber) {
1109: for (BugInstance bug : bugSet)
1110: if (bug.getInstanceHash().equals(instanceHash)
1111: && bug.getBugPattern().getType().equals(bugType)
1112: && bug.getPrimarySourceLineAnnotation()
1113: .getStartLine() == lineNumber)
1114: return bug;
1115: return null;
1116: }
1117:
1118: /**
1119: * @param version
1120: */
1121: public void setAnalysisVersion(String version) {
1122: this .analysisVersion = version;
1123:
1124: }
1125: }
1126:
1127: // vim:ts=4
|