/* * Cobertura - http://cobertura.sourceforge.net/ * * Copyright (C) 2003 jcoverage ltd. * Copyright (C) 2005 Mark Doliner * Copyright (C) 2005 Jeremy Thomerson * Copyright (C) 2006 Jiri Mares * Copyright (C) 2008 Julian Gamble * * Cobertura is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * Cobertura is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cobertura; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ package net.sourceforge.cobertura.reporting.xml; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; import net.sourceforge.cobertura.coveragedata.ClassData; import net.sourceforge.cobertura.coveragedata.JumpData; import net.sourceforge.cobertura.coveragedata.LineData; import net.sourceforge.cobertura.coveragedata.PackageData; import net.sourceforge.cobertura.coveragedata.ProjectData; import net.sourceforge.cobertura.coveragedata.SourceFileData; import net.sourceforge.cobertura.coveragedata.SwitchData; import net.sourceforge.cobertura.reporting.ComplexityCalculator; import net.sourceforge.cobertura.util.FileFinder; import net.sourceforge.cobertura.util.Header; import net.sourceforge.cobertura.util.IOUtil; import net.sourceforge.cobertura.util.StringUtil; import org.apache.log4j.Logger; public class XMLReport { private static final Logger logger = Logger.getLogger(XMLReport.class); protected final static String coverageDTD = "coverage-04.dtd"; private final PrintWriter pw; private final FileFinder finder; private final ComplexityCalculator complexity; private int indent = 0; public XMLReport(ProjectData projectData, File destinationDir, FileFinder finder, ComplexityCalculator complexity) throws IOException { this.complexity = complexity; this.finder = finder; File file = new File(destinationDir, "coverage.xml"); pw = IOUtil.getPrintWriter(file); try { println("<?xml version=\"1.0\"?>"); println("<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/" + coverageDTD + "\">"); println(""); double ccn = complexity.getCCNForProject(projectData); int numLinesCovered = projectData.getNumberOfCoveredLines(); int numLinesValid = projectData.getNumberOfValidLines(); int numBranchesCovered = projectData.getNumberOfCoveredBranches(); int numBranchesValid = projectData.getNumberOfValidBranches(); // TODO: Set a schema? //println("<coverage " + sourceDirectories.toString() + " xmlns=\"http://cobertura.sourceforge.net\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://cobertura.sourceforge.net/xml/coverage.xsd\">"); println( "<coverage line-rate=\"" + projectData.getLineCoverageRate() + "\" branch-rate=\"" + projectData.getBranchCoverageRate() + "\" lines-covered=\"" + numLinesCovered + "\" lines-valid=\"" + numLinesValid + "\" branches-covered=\"" + numBranchesCovered + "\" branches-valid=\"" + numBranchesValid + "\" complexity=\"" + ccn + "\" version=\"" + Header.version() + "\" timestamp=\"" + new Date().getTime() + "\">"); increaseIndentation(); dumpSources(); dumpPackages(projectData); decreaseIndentation(); println("</coverage>"); } finally { pw.close(); } } void increaseIndentation() { indent++; } void decreaseIndentation() { if (indent > 0) indent--; } void indent() { for (int i = 0; i < indent; i++) { pw.print("\t"); } } void println(String ln) { indent(); pw.println(ln); } private void dumpSources() { println("<sources>"); increaseIndentation(); for (Iterator it = finder.getSourceDirectoryList().iterator(); it.hasNext(); ) { String dir = (String) it.next(); dumpSource(dir); } decreaseIndentation(); println("</sources>"); } private void dumpSource(String sourceDirectory) { println("<source>" + sourceDirectory + "</source>"); } private void dumpPackages(ProjectData projectData) { println("<packages>"); increaseIndentation(); Iterator it = projectData.getPackages().iterator(); while (it.hasNext()) { dumpPackage((PackageData) it.next()); } decreaseIndentation(); println("</packages>"); } private void dumpPackage(PackageData packageData) { logger.debug("Dumping package " + packageData.getName()); println("<package name=\"" + packageData.getName() + "\" line-rate=\"" + packageData.getLineCoverageRate() + "\" branch-rate=\"" + packageData.getBranchCoverageRate() + "\" complexity=\"" + complexity.getCCNForPackage(packageData) + "\"" + ">"); increaseIndentation(); dumpSourceFiles(packageData); decreaseIndentation(); println("</package>"); } private void dumpSourceFiles(PackageData packageData) { println("<classes>"); increaseIndentation(); Iterator it = packageData.getSourceFiles().iterator(); while (it.hasNext()) { dumpClasses((SourceFileData) it.next()); } decreaseIndentation(); println("</classes>"); } private void dumpClasses(SourceFileData sourceFileData) { Iterator it = sourceFileData.getClasses().iterator(); while (it.hasNext()) { dumpClass((ClassData) it.next()); } } private void dumpClass(ClassData classData) { logger.debug("Dumping class " + classData.getName()); println("<class name=\"" + classData.getName() + "\" filename=\"" + classData.getSourceFileName() + "\" line-rate=\"" + classData.getLineCoverageRate() + "\" branch-rate=\"" + classData.getBranchCoverageRate() + "\" complexity=\"" + complexity.getCCNForClass(classData) + "\"" + ">"); increaseIndentation(); dumpMethods(classData); dumpLines(classData); decreaseIndentation(); println("</class>"); } private void dumpMethods(ClassData classData) { println("<methods>"); increaseIndentation(); SortedSet sortedMethods = new TreeSet(); sortedMethods.addAll(classData.getMethodNamesAndDescriptors()); Iterator iter = sortedMethods.iterator(); while (iter.hasNext()) { dumpMethod(classData, (String) iter.next()); } decreaseIndentation(); println("</methods>"); } private void dumpMethod(ClassData classData, String nameAndSig) { String name = nameAndSig.substring(0, nameAndSig.indexOf('(')); String signature = nameAndSig.substring(nameAndSig.indexOf('(')); double lineRate = classData.getLineCoverageRate(nameAndSig); double branchRate = classData.getBranchCoverageRate(nameAndSig); println("<method name=\"" + xmlEscape(name) + "\" signature=\"" + xmlEscape(signature) + "\" line-rate=\"" + lineRate + "\" branch-rate=\"" + branchRate + "\">"); increaseIndentation(); dumpLines(classData, nameAndSig); decreaseIndentation(); println("</method>"); } private static String xmlEscape(String str) { str = StringUtil.replaceAll(str, "<", "<"); str = StringUtil.replaceAll(str, ">", ">"); return str; } private void dumpLines(ClassData classData) { dumpLines(classData.getLines()); } private void dumpLines(ClassData classData, String methodNameAndSig) { dumpLines(classData.getLines(methodNameAndSig)); } private void dumpLines(Collection lines) { println("<lines>"); increaseIndentation(); SortedSet sortedLines = new TreeSet(); sortedLines.addAll(lines); Iterator iter = sortedLines.iterator(); while (iter.hasNext()) { dumpLine((LineData) iter.next()); } decreaseIndentation(); println("</lines>"); } private void dumpLine(LineData lineData) { int lineNumber = lineData.getLineNumber(); long hitCount = lineData.getHits(); boolean hasBranch = lineData.hasBranch(); String conditionCoverage = lineData.getConditionCoverage(); String lineInfo = "<line number=\"" + lineNumber + "\" hits=\"" + hitCount + "\" branch=\"" + hasBranch + "\""; if (hasBranch) { println(lineInfo + " test.condition-coverage=\"" + conditionCoverage + "\">"); dumpConditions(lineData); println("</line>"); } else { println(lineInfo + "/>"); } } private void dumpConditions(LineData lineData) { increaseIndentation(); println("<conditions>"); for (int i = 0; i < lineData.getConditionSize(); i++) { Object conditionData = lineData.getConditionData(i); String coverage = lineData.getConditionCoverage(i); dumpCondition(conditionData, coverage); } println("</conditions>"); decreaseIndentation(); } private void dumpCondition(Object conditionData, String coverage) { increaseIndentation(); StringBuffer buffer = new StringBuffer("<test.condition"); if (conditionData instanceof JumpData) { JumpData jumpData = (JumpData) conditionData; buffer.append(" number=\"").append(jumpData.getConditionNumber()).append("\""); buffer.append(" type=\"").append("jump").append("\""); buffer.append(" coverage=\"").append(coverage).append("\""); } else { SwitchData switchData = (SwitchData) conditionData; buffer.append(" number=\"").append(switchData.getSwitchNumber()).append("\""); buffer.append(" type=\"").append("switch").append("\""); buffer.append(" coverage=\"").append(coverage).append("\""); } buffer.append("/>"); println(buffer.toString()); decreaseIndentation(); } }