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, "<", "&lt;");
263                str = StringUtil.replaceAll(str, ">", "&gt;");
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}