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, "<", "&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    }