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 }