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