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
026 package net.sourceforge.cobertura.reporting.xml;
027
028 import java.io.File;
029 import java.io.IOException;
030 import java.io.PrintWriter;
031 import java.util.Collection;
032 import java.util.Date;
033 import java.util.Iterator;
034 import java.util.SortedSet;
035 import java.util.TreeSet;
036
037 import net.sourceforge.cobertura.coveragedata.ClassData;
038 import net.sourceforge.cobertura.coveragedata.JumpData;
039 import net.sourceforge.cobertura.coveragedata.LineData;
040 import net.sourceforge.cobertura.coveragedata.PackageData;
041 import net.sourceforge.cobertura.coveragedata.ProjectData;
042 import net.sourceforge.cobertura.coveragedata.SourceFileData;
043 import net.sourceforge.cobertura.coveragedata.SwitchData;
044 import net.sourceforge.cobertura.reporting.ComplexityCalculator;
045 import net.sourceforge.cobertura.util.FileFinder;
046 import net.sourceforge.cobertura.util.Header;
047 import net.sourceforge.cobertura.util.IOUtil;
048 import net.sourceforge.cobertura.util.StringUtil;
049
050 import org.apache.log4j.Logger;
051
052 public 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 }