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    }