001/* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2003 jcoverage ltd. 005 * Copyright (C) 2005 Mark Doliner 006 * Copyright (C) 2005 Jeremy Thomerson 007 * Copyright (C) 2006 Jiri Mares 008 * Copyright (C) 2008 Julian Gamble 009 * 010 * Cobertura is free software; you can redistribute it and/or modify 011 * it under the terms of the GNU General Public License as published 012 * by the Free Software Foundation; either version 2 of the License, 013 * or (at your option) any later version. 014 * 015 * Cobertura is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of 017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 * General Public License for more details. 019 * 020 * You should have received a copy of the GNU General Public License 021 * along with Cobertura; if not, write to the Free Software 022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 023 * USA 024 */ 025 026package net.sourceforge.cobertura.reporting.xml; 027 028import java.io.File; 029import java.io.IOException; 030import java.io.PrintWriter; 031import java.util.Collection; 032import java.util.Date; 033import java.util.Iterator; 034import java.util.SortedSet; 035import java.util.TreeSet; 036 037import net.sourceforge.cobertura.coveragedata.ClassData; 038import net.sourceforge.cobertura.coveragedata.JumpData; 039import net.sourceforge.cobertura.coveragedata.LineData; 040import net.sourceforge.cobertura.coveragedata.PackageData; 041import net.sourceforge.cobertura.coveragedata.ProjectData; 042import net.sourceforge.cobertura.coveragedata.SourceFileData; 043import net.sourceforge.cobertura.coveragedata.SwitchData; 044import net.sourceforge.cobertura.reporting.ComplexityCalculator; 045import net.sourceforge.cobertura.util.FileFinder; 046import net.sourceforge.cobertura.util.Header; 047import net.sourceforge.cobertura.util.IOUtil; 048import net.sourceforge.cobertura.util.StringUtil; 049 050import org.apache.log4j.Logger; 051 052public class XMLReport 053{ 054 055 private static final Logger logger = Logger.getLogger(XMLReport.class); 056 057 protected final static String coverageDTD = "coverage-04.dtd"; 058 059 private final PrintWriter pw; 060 private final FileFinder finder; 061 private final ComplexityCalculator complexity; 062 private int indent = 0; 063 064 public XMLReport(ProjectData projectData, File destinationDir, 065 FileFinder finder, ComplexityCalculator complexity) throws IOException 066 { 067 this.complexity = complexity; 068 this.finder = finder; 069 070 File file = new File(destinationDir, "coverage.xml"); 071 pw = IOUtil.getPrintWriter(file); 072 073 try 074 { 075 println("<?xml version=\"1.0\"?>"); 076 println("<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/" 077 + coverageDTD + "\">"); 078 println(""); 079 080 double ccn = complexity.getCCNForProject(projectData); 081 int numLinesCovered = projectData.getNumberOfCoveredLines(); 082 int numLinesValid = projectData.getNumberOfValidLines(); 083 int numBranchesCovered = projectData.getNumberOfCoveredBranches(); 084 int numBranchesValid = projectData.getNumberOfValidBranches(); 085 086 // TODO: Set a schema? 087 //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\">"); 088 println( 089 "<coverage line-rate=\"" + projectData.getLineCoverageRate() 090 + "\" branch-rate=\"" + projectData.getBranchCoverageRate() 091 + "\" lines-covered=\"" + numLinesCovered 092 + "\" lines-valid=\"" + numLinesValid 093 + "\" branches-covered=\"" + numBranchesCovered 094 + "\" branches-valid=\"" + numBranchesValid 095 096 + "\" complexity=\"" + ccn 097 098 + "\" version=\"" + Header.version() 099 + "\" timestamp=\"" + new Date().getTime() 100 + "\">"); 101 102 increaseIndentation(); 103 dumpSources(); 104 dumpPackages(projectData); 105 decreaseIndentation(); 106 println("</coverage>"); 107 } 108 finally 109 { 110 pw.close(); 111 } 112 } 113 114 void increaseIndentation() 115 { 116 indent++; 117 } 118 119 void decreaseIndentation() 120 { 121 if (indent > 0) 122 indent--; 123 } 124 125 void indent() 126 { 127 for (int i = 0; i < indent; i++) 128 { 129 pw.print("\t"); 130 } 131 } 132 133 void println(String ln) 134 { 135 indent(); 136 pw.println(ln); 137 } 138 139 private void dumpSources() 140 { 141 println("<sources>"); 142 increaseIndentation(); 143 for (Iterator it = finder.getSourceDirectoryList().iterator(); it.hasNext(); ) { 144 String dir = (String) it.next(); 145 dumpSource(dir); 146 } 147 decreaseIndentation(); 148 println("</sources>"); 149 } 150 151 private void dumpSource(String sourceDirectory) 152 { 153 println("<source>" + sourceDirectory + "</source>"); 154 } 155 156 private void dumpPackages(ProjectData projectData) 157 { 158 println("<packages>"); 159 increaseIndentation(); 160 161 Iterator it = projectData.getPackages().iterator(); 162 while (it.hasNext()) 163 { 164 dumpPackage((PackageData)it.next()); 165 } 166 167 decreaseIndentation(); 168 println("</packages>"); 169 } 170 171 private void dumpPackage(PackageData packageData) 172 { 173 logger.debug("Dumping package " + packageData.getName()); 174 175 println("<package name=\"" + packageData.getName() 176 + "\" line-rate=\"" + packageData.getLineCoverageRate() 177 + "\" branch-rate=\"" + packageData.getBranchCoverageRate() 178 + "\" complexity=\"" + complexity.getCCNForPackage(packageData) + "\"" + ">"); 179 increaseIndentation(); 180 dumpSourceFiles(packageData); 181 decreaseIndentation(); 182 println("</package>"); 183 } 184 185 private void dumpSourceFiles(PackageData packageData) 186 { 187 println("<classes>"); 188 increaseIndentation(); 189 190 Iterator it = packageData.getSourceFiles().iterator(); 191 while (it.hasNext()) 192 { 193 dumpClasses((SourceFileData)it.next()); 194 } 195 196 decreaseIndentation(); 197 println("</classes>"); 198 } 199 200 private void dumpClasses(SourceFileData sourceFileData) 201 { 202 Iterator it = sourceFileData.getClasses().iterator(); 203 while (it.hasNext()) 204 { 205 dumpClass((ClassData)it.next()); 206 } 207 } 208 209 private void dumpClass(ClassData classData) 210 { 211 logger.debug("Dumping class " + classData.getName()); 212 213 println("<class name=\"" + classData.getName() + "\" filename=\"" 214 + classData.getSourceFileName() + "\" line-rate=\"" 215 + classData.getLineCoverageRate() + "\" branch-rate=\"" 216 + classData.getBranchCoverageRate() + "\" complexity=\"" 217 + complexity.getCCNForClass(classData) + "\"" + ">"); 218 increaseIndentation(); 219 220 dumpMethods(classData); 221 dumpLines(classData); 222 223 decreaseIndentation(); 224 println("</class>"); 225 } 226 227 private void dumpMethods(ClassData classData) 228 { 229 println("<methods>"); 230 increaseIndentation(); 231 232 SortedSet sortedMethods = new TreeSet(); 233 sortedMethods.addAll(classData.getMethodNamesAndDescriptors()); 234 Iterator iter = sortedMethods.iterator(); 235 while (iter.hasNext()) 236 { 237 dumpMethod(classData, (String)iter.next()); 238 } 239 240 decreaseIndentation(); 241 println("</methods>"); 242 } 243 244 private void dumpMethod(ClassData classData, String nameAndSig) 245 { 246 String name = nameAndSig.substring(0, nameAndSig.indexOf('(')); 247 String signature = nameAndSig.substring(nameAndSig.indexOf('(')); 248 double lineRate = classData.getLineCoverageRate(nameAndSig); 249 double branchRate = classData.getBranchCoverageRate(nameAndSig); 250 251 println("<method name=\"" + xmlEscape(name) + "\" signature=\"" 252 + xmlEscape(signature) + "\" line-rate=\"" + lineRate 253 + "\" branch-rate=\"" + branchRate + "\">"); 254 increaseIndentation(); 255 dumpLines(classData, nameAndSig); 256 decreaseIndentation(); 257 println("</method>"); 258 } 259 260 private static String xmlEscape(String str) 261 { 262 str = StringUtil.replaceAll(str, "<", "<"); 263 str = StringUtil.replaceAll(str, ">", ">"); 264 return str; 265 } 266 267 private void dumpLines(ClassData classData) 268 { 269 dumpLines(classData.getLines()); 270 } 271 272 private void dumpLines(ClassData classData, String methodNameAndSig) 273 { 274 dumpLines(classData.getLines(methodNameAndSig)); 275 } 276 277 private void dumpLines(Collection lines) 278 { 279 println("<lines>"); 280 increaseIndentation(); 281 282 SortedSet sortedLines = new TreeSet(); 283 sortedLines.addAll(lines); 284 Iterator iter = sortedLines.iterator(); 285 while (iter.hasNext()) 286 { 287 dumpLine((LineData)iter.next()); 288 } 289 290 decreaseIndentation(); 291 println("</lines>"); 292 } 293 294 private void dumpLine(LineData lineData) 295 { 296 int lineNumber = lineData.getLineNumber(); 297 long hitCount = lineData.getHits(); 298 boolean hasBranch = lineData.hasBranch(); 299 String conditionCoverage = lineData.getConditionCoverage(); 300 301 String lineInfo = "<line number=\"" + lineNumber + "\" hits=\"" + hitCount 302 + "\" branch=\"" + hasBranch + "\""; 303 if (hasBranch) 304 { 305 println(lineInfo + " condition-coverage=\"" + conditionCoverage + "\">"); 306 dumpConditions(lineData); 307 println("</line>"); 308 } else 309 { 310 println(lineInfo + "/>"); 311 } 312 } 313 314 private void dumpConditions(LineData lineData) 315 { 316 increaseIndentation(); 317 println("<conditions>"); 318 319 for (int i = 0; i < lineData.getConditionSize(); i++) 320 { 321 Object conditionData = lineData.getConditionData(i); 322 String coverage = lineData.getConditionCoverage(i); 323 dumpCondition(conditionData, coverage); 324 } 325 326 println("</conditions>"); 327 decreaseIndentation(); 328 } 329 330 private void dumpCondition(Object conditionData, String coverage) 331 { 332 increaseIndentation(); 333 StringBuffer buffer = new StringBuffer("<condition"); 334 if (conditionData instanceof JumpData) 335 { 336 JumpData jumpData = (JumpData) conditionData; 337 buffer.append(" number=\"").append(jumpData.getConditionNumber()).append("\""); 338 buffer.append(" type=\"").append("jump").append("\""); 339 buffer.append(" coverage=\"").append(coverage).append("\""); 340 } 341 else 342 { 343 SwitchData switchData = (SwitchData) conditionData; 344 buffer.append(" number=\"").append(switchData.getSwitchNumber()).append("\""); 345 buffer.append(" type=\"").append("switch").append("\""); 346 buffer.append(" coverage=\"").append(coverage).append("\""); 347 } 348 buffer.append("/>"); 349 println(buffer.toString()); 350 decreaseIndentation(); 351 } 352 353}