View Javadoc

1   /*
2    * Copyright  2001-2004 The Apache Software Foundation
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  package org.woopi.ant.taskdefs.junit;
18  
19  import java.io.BufferedOutputStream;
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.io.FileWriter;
27  import java.io.BufferedWriter;
28  import java.util.Enumeration;
29  import java.util.Vector;
30  import java.util.Hashtable;
31  import java.util.HashSet;
32  import java.util.Map;
33  import java.util.TreeMap;
34  import java.util.StringTokenizer;
35  import java.text.DecimalFormat;
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import org.apache.tools.ant.BuildException;
39  import org.apache.tools.ant.DirectoryScanner;
40  import org.apache.tools.ant.Project;
41  import org.apache.tools.ant.Task;
42  import org.apache.tools.ant.types.FileSet;
43  import org.apache.tools.ant.util.DOMElementWriter;
44  import org.apache.tools.ant.util.StringUtils;
45  import org.w3c.dom.Document;
46  import org.w3c.dom.Element;
47  import org.w3c.dom.Node;
48  import org.w3c.dom.NodeList;
49  import org.w3c.dom.NamedNodeMap;
50  import org.xml.sax.SAXException;
51  
52  import org.apache.velocity.VelocityContext;
53  import org.apache.velocity.Template;
54  import org.apache.velocity.app.Velocity;
55  import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
56  
57  
58  /***
59   * Aggregates all <junit> XML formatter testsuite data under
60   * a specific directory and transforms the results via XSLT.
61   * It is not particulary clean but
62   * should be helpful while I am thinking about another technique.
63   *
64   * <p> The main problem is due to the fact that a JVM can be forked for a testcase
65   * thus making it impossible to aggregate all testcases since the listener is
66   * (obviously) in the forked JVM. A solution could be to write a
67   * TestListener that will receive events from the TestRunner via sockets. This
68   * is IMHO the simplest way to do it to avoid this file hacking thing.
69   *
70   *
71   * @ant.task name="junitreport" category="testing"
72   */
73  public class JunitVelocityReportTask extends Task implements XMLConstants {
74  
75      /*** the list of all filesets, that should contains the xml to aggregate */
76      protected Vector filesets = new Vector();
77  
78      /*** the name of the result file */
79      protected String toFile;
80  
81      /*** the directory to write the file to */
82      protected File toDir;
83  
84      protected Vector transformers = new Vector();
85  
86      /*** The default directory: <tt>&#046;</tt>. It is resolved from the project directory */
87      public static final String DEFAULT_DIR = ".";
88  
89      /*** the default file name: <tt>TESTS-TestSuites.xml</tt> */
90      public static final String DEFAULT_FILENAME = "TESTS-TestSuites.xml";
91  
92      public DecimalFormat dnf = new DecimalFormat("##0.##");
93  
94      public JunitVelocityReportTask() {
95        super();
96        try {
97          Velocity.setProperty("resource.loader","class");
98          Velocity.setProperty("class.resource.loader.class",
99                          "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
100         Velocity.init(); 
101         Velocity.setProperty("resource.loader","class");
102         Velocity.setProperty("class.resource.loader.class",
103                         "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
104       } catch (Exception e) {e.printStackTrace();}
105     }
106 
107 
108     /***
109      * Generate a report based on the document created by the merge.
110      */
111     public AggregateTransformer createReport() {
112         AggregateTransformer transformer = new AggregateTransformer(this);
113         transformers.addElement(transformer);
114         return transformer;
115     }
116 
117     /***
118      * Set the name of the aggregegated results file. It must be relative
119      * from the <tt>todir</tt> attribute. If not set it will use {@link #DEFAULT_FILENAME}
120      * @param  value   the name of the file.
121      * @see #setTodir(File)
122      */
123     public void setTofile(String value) {
124         toFile = value;
125     }
126 
127     /***
128      * Set the destination directory where the results should be written. If not
129      * set if will use {@link #DEFAULT_DIR}. When given a relative directory
130      * it will resolve it from the project directory.
131      * @param value    the directory where to write the results, absolute or
132      * relative.
133      */
134     public void setTodir(File value) {
135         toDir = value;
136     }
137 
138     /***
139      * Add a new fileset containing the XML results to aggregate
140      * @param    fs      the new fileset of xml results.
141      */
142     public void addFileSet(FileSet fs) {
143         filesets.addElement(fs);
144     }
145 
146     /***
147      * Aggregate all testsuites into a single document and write it to the
148      * specified directory and file.
149      * @throws  BuildException  thrown if there is a serious error while writing
150      *          the document.
151      */
152     public void execute() throws BuildException {
153         Element rootElement = createDocument();
154         File destFile = getDestinationFile();
155         // write the document
156         try {
157             writeDOMTree(rootElement.getOwnerDocument(), destFile);
158         } catch (IOException e) {
159             throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e);
160         }
161         
162         generateVelocityReport(rootElement);
163         // apply transformation
164         /*
165         Enumeration e = transformers.elements();
166         while (e.hasMoreElements()) {
167             AggregateTransformer transformer =
168                 (AggregateTransformer) e.nextElement();
169             transformer.setXmlDocument(rootElement.getOwnerDocument());
170             transformer.transform();
171         }
172         */
173     }
174 
175     /***
176      * the rootElement should contain the node testsuits
177     */
178     public void generateVelocityReport(Element rootElement) {
179       /*
180       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
181       for (int i=0;i<testsuiteNodes.getLength();i++) {
182         Element testsuiteNode = (Element)testsuiteNodes.item(i);
183         
184       }
185       */
186       generateStylesheet(rootElement);
187       generateIndex(rootElement);
188       generateOverviewFrame(rootElement);
189       generateOverviewSummary(rootElement);
190       generateAllClassesFrame(rootElement);
191       generateTestClassInfos(rootElement);
192       generatePackageSummaryFiles(rootElement);
193       generatePackageFrames(rootElement);
194     }
195 
196     /***
197      * just applicable to testsuites Node
198     */
199     public int getIntegerSumCount(Element rootElement,String attributeName) {
200       int result = 0;
201       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
202       for (int i=0;i<testsuiteNodes.getLength();i++) {
203         Element testsuiteNode = (Element)testsuiteNodes.item(i);
204         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
205         Node testAttribute = attributeMap.getNamedItem(attributeName);
206         try {result += Integer.parseInt(testAttribute.getNodeValue()); } catch (Exception e) {}
207       }
208       return result;
209     }
210 
211     /***
212      * just applicable to testsuites Node
213     */
214     public double getDoubleSumCount(Element rootElement,String attributeName) {
215       double result = 0.0;
216       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
217       for (int i=0;i<testsuiteNodes.getLength();i++) {
218         Element testsuiteNode = (Element)testsuiteNodes.item(i);
219         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
220         Node testAttribute = attributeMap.getNamedItem(attributeName);
221         try {result += Double.parseDouble(testAttribute.getNodeValue()); } catch (Exception e) {}
222       }
223       return result;
224     }
225     
226     public String getSummaryStylesheetClass(int errorCount, int failureCount) {
227       String result = "Pass";
228       if (errorCount > 0) result = "Error";
229       else if (failureCount > 0) result = "Failure";
230       return result;
231     }
232 
233     /***
234      * just applicable to testsuites Node
235     */
236     public Map[] getPackagesMap(Element rootElement) {
237       Map[] result = null;
238       HashSet resultSet = new HashSet();
239       String noPackage = "&lt;none&gt;";
240       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
241       for (int i=0;i<testsuiteNodes.getLength();i++) {
242         Element testsuiteNode = (Element)testsuiteNodes.item(i);
243         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
244         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
245         Hashtable temp = new Hashtable();
246         String packageName = packageAttribute.getNodeValue();
247         if (packageName == null || packageName.trim().equals("")) {
248           packageName = noPackage;
249         }
250         temp.put("package",packageName);
251         temp.put("packagePath",packageName.replace(".","/"));
252         resultSet.add(temp);
253       }
254       result = new Hashtable[resultSet.size()];
255       result = (Map[])resultSet.toArray(result);
256       return result;
257     }
258 
259     /***
260      * just applicable to testsuites Node
261     */
262     public Map[] getClassesMap(Element rootElement) {
263       Map[] result = null;
264       HashSet resultSet = new HashSet();
265       String noPackage = "&lt;none&gt;";
266       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
267       for (int i=0;i<testsuiteNodes.getLength();i++) {
268         Element testsuiteNode = (Element)testsuiteNodes.item(i);
269         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
270         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
271         Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
272         Hashtable temp = new Hashtable();
273         String packageName = packageAttribute.getNodeValue();
274         String className = nameAttribute.getNodeValue();
275         if (packageName == null || packageName.trim().equals("")) {
276           packageName = noPackage;
277         }
278         temp.put("className",className);
279         temp.put("packagePath",packageName.replace(".","/"));
280         resultSet.add(temp);
281       }
282       result = new Hashtable[resultSet.size()];
283       result = (Map[])resultSet.toArray(result);
284       return result;
285     }    
286     
287     /***
288      * just applicable to testsuites Node
289     */
290     public Map[] getClassesMap(Element rootElement, String packageNameP) {
291       Map[] result = null;
292       TreeMap resultMap = new TreeMap();
293       String noPackage = "&lt;none&gt;";
294       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
295       for (int i=0;i<testsuiteNodes.getLength();i++) {
296         Element testsuiteNode = (Element)testsuiteNodes.item(i);
297         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
298         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
299         String packageName = packageAttribute.getNodeValue();
300         if (packageName.equals(packageNameP)) {
301           Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
302           Hashtable temp = new Hashtable();
303           String className = nameAttribute.getNodeValue();
304           if (packageName == null || packageName.trim().equals("")) {
305             packageName = noPackage;
306           }
307           temp.put("className",className);
308           temp.put("packagePath",packageName.replace(".","/"));
309           resultMap.put(className,temp);
310         }
311       }
312       result = new Hashtable[resultMap.size()];
313       result = (Map[])resultMap.values().toArray(result);
314       return result;
315     }    
316 
317 
318     /***
319      * just applicable to testsuites Node
320     */
321     public Map[] getPackageStatisticsMap(Element rootElement) {
322       Map[] result = null;
323       TreeMap resultMap = new TreeMap();
324       String noPackage = "&lt;none&gt;";
325       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
326       for (int i=0;i<testsuiteNodes.getLength();i++) {
327         Element testsuiteNode = (Element)testsuiteNodes.item(i);
328         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
329         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
330         Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
331         Node testsAttribute = attributeMap.getNamedItem("tests");
332         Node errorAttribute = attributeMap.getNamedItem(ATTR_ERRORS);
333         Node failureAttribute = attributeMap.getNamedItem(ATTR_FAILURES);
334         Node timeAttribute = attributeMap.getNamedItem(ATTR_TIME);
335         int tests = 0;
336         int errors = 0;
337         int failures = 0;
338         double time = 0.0;
339         try {tests += Integer.parseInt(testsAttribute.getNodeValue()); } catch (Exception e) {}
340         try {errors += Integer.parseInt(errorAttribute.getNodeValue()); } catch (Exception e) {}
341         try {failures += Integer.parseInt(failureAttribute.getNodeValue()); } catch (Exception e) {}
342         try {time += Double.parseDouble(timeAttribute.getNodeValue()); } catch (Exception e) {}
343         String packageName = packageAttribute.getNodeValue();
344         String className = nameAttribute.getNodeValue();
345         if (packageName == null || packageName.trim().equals("")) {
346           packageName = noPackage;
347         }
348         String stylesheet = "Pass";
349         if (errors > 0) stylesheet = "Error";
350         else if (failures > 0) stylesheet = "Failure";
351         Map temp = new Hashtable();
352         if (resultMap.containsKey(packageName)) {
353           temp = (Map)resultMap.get(packageName);
354           temp.put("tests",(Integer)temp.get("tests")+tests);
355           temp.put("errors",(Integer)temp.get("errors")+errors);
356           temp.put("failures",(Integer)temp.get("failures")+failures);
357           temp.put("stylesheet", stylesheet);
358           temp.put("time",(Double)temp.get("time")+time);            
359         } else {
360           temp.put("tests",tests);
361           temp.put("errors",errors);
362           temp.put("failures",failures);
363           temp.put("time",time);            
364           temp.put("packageName",packageName);
365           temp.put("stylesheet", stylesheet);
366           temp.put("packagePath",packageName.replace(".","/"));
367           resultMap.put(packageName,temp);
368         }
369       }
370       result = new Hashtable[resultMap.size()];
371       result = (Map[])resultMap.values().toArray(result);
372       for (int i=0;i<result.length;i++) {
373         result[i].put("time",getTimeFormat((Double)result[i].get("time")));
374       }
375       return result;
376     }    
377     
378     /***
379      * just applicable to testsuites Node
380     */
381     public Map[] getSpecialPackageStatisticsMap(Element rootElement, String packageNameP) {
382       Map[] result = null;
383       TreeMap resultMap = new TreeMap();
384       String noPackage = "&lt;none&gt;";
385       NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
386       for (int i=0;i<testsuiteNodes.getLength();i++) {
387         Element testsuiteNode = (Element)testsuiteNodes.item(i);
388         NamedNodeMap attributeMap =testsuiteNode.getAttributes(); 
389         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
390         Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
391         Node testsAttribute = attributeMap.getNamedItem("tests");
392         Node errorAttribute = attributeMap.getNamedItem(ATTR_ERRORS);
393         Node failureAttribute = attributeMap.getNamedItem(ATTR_FAILURES);
394         Node timeAttribute = attributeMap.getNamedItem(ATTR_TIME);
395         String packageName = packageAttribute.getNodeValue();
396         String className = nameAttribute.getNodeValue();
397         if (packageName.equals(packageNameP)) {
398           int tests = 0;
399           int errors = 0;
400           int failures = 0;
401           double time = 0.0;
402           try {tests += Integer.parseInt(testsAttribute.getNodeValue()); } catch (Exception e) {}
403           try {errors += Integer.parseInt(errorAttribute.getNodeValue()); } catch (Exception e) {}
404           try {failures += Integer.parseInt(failureAttribute.getNodeValue()); } catch (Exception e) {}
405           try {time += Double.parseDouble(timeAttribute.getNodeValue()); } catch (Exception e) {}
406           if (packageName == null || packageName.trim().equals("")) {
407             packageName = noPackage;
408           }
409           String stylesheet = "Pass";
410           if (errors > 0) stylesheet = "Error";
411           else if (failures > 0) stylesheet = "Failure";
412           Map temp = new Hashtable();
413           if (resultMap.containsKey(className)) {
414             temp = (Map)resultMap.get(className);
415             temp.put("tests",(Integer)temp.get("tests")+tests);
416             temp.put("errors",(Integer)temp.get("errors")+errors);
417             temp.put("failures",(Integer)temp.get("failures")+failures);
418             temp.put("time",(Double)temp.get("time")+time);            
419           } else {
420             temp.put("className",className);
421             temp.put("tests"    ,tests);
422             temp.put("errors"   ,errors);
423             temp.put("failures" ,failures);
424             temp.put("time"     ,time);            
425             temp.put("packageName",packageName);
426             temp.put("stylesheet", stylesheet);
427             temp.put("packagePath",packageName.replace(".","/"));
428             resultMap.put(className,temp);
429           }
430         }
431       }
432       result = new Hashtable[resultMap.size()];
433       result = (Map[])resultMap.values().toArray(result);
434       for (int i=0;i<result.length;i++) {
435         result[i].put("time",getTimeFormat((Double)result[i].get("time")));
436       }
437       return result;
438     }    
439 
440     public void generateOverviewSummary(Element rootElement) {
441       try {
442         int testCount = getIntegerSumCount(rootElement,"tests");
443         int errorCount = getIntegerSumCount(rootElement,"errors");
444         int failureCount = getIntegerSumCount(rootElement,"failures");
445         double timeCount = getDoubleSumCount(rootElement,"time");
446         int successRate = (testCount - failureCount - errorCount) * 100 / testCount;
447         String summaryStylesheetClass = getSummaryStylesheetClass(errorCount, failureCount);
448         VelocityContext context = new VelocityContext();
449         context.put("testCount", ""+testCount);
450         context.put("errorCount", ""+errorCount);
451         context.put("failureCount", ""+failureCount);
452         context.put("timeCount", getTimeFormat(timeCount));
453         context.put("successRate", successRate);
454         context.put("summaryStylesheetClass",summaryStylesheetClass);
455         Map[] packages = getPackageStatisticsMap(rootElement);
456         context.put("packages",packages);
457         Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/overview-summary.vm");
458         File baseDir = new File(toDir+"/html");
459         if (!baseDir.exists()) baseDir.mkdirs();
460         FileWriter fw = new FileWriter(baseDir+"/overview-summary.html");
461         BufferedWriter writer = new BufferedWriter(fw);
462         template.merge(context , writer);  
463         writer.close();
464       } catch (Exception e) {
465         e.printStackTrace();
466       }
467     }
468 
469     public void generateStylesheet(Element rootElement) {
470       try {
471         VelocityContext context = new VelocityContext();
472         Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/stylesheet.vm");
473         File baseDir = new File(toDir+"/html");
474         if (!baseDir.exists()) baseDir.mkdirs();
475         FileWriter fw = new FileWriter(baseDir+"/stylesheet.css");
476         BufferedWriter writer = new BufferedWriter(fw);
477         template.merge(context , writer);  
478         writer.close();
479       } catch (Exception e) {
480         e.printStackTrace();
481       }
482     }
483     
484     public void generateIndex(Element rootElement) {
485       try {
486         VelocityContext context = new VelocityContext();
487         Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/index.vm");
488         File baseDir = new File(toDir+"/html");
489         if (!baseDir.exists()) baseDir.mkdirs();
490         FileWriter fw = new FileWriter(baseDir+"/index.html");
491         BufferedWriter writer = new BufferedWriter(fw);
492         template.merge(context , writer);  
493         writer.close();
494       } catch (Exception e) {
495         e.printStackTrace();
496       }
497     }
498 
499     public void generateOverviewFrame(Element rootElement) {
500       try {
501         VelocityContext context = new VelocityContext();
502         Integer maxPackages = new Integer(2);
503         Map[] packages = getPackagesMap(rootElement);
504         context.put("packages",packages);
505         Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/overview-frame.vm");
506         File baseDir = new File(toDir+"/html");
507         if (!baseDir.exists()) baseDir.mkdirs();
508         FileWriter fw = new FileWriter(baseDir+"/overview-frame.html");
509         BufferedWriter writer = new BufferedWriter(fw);
510         template.merge(context , writer);  
511         writer.close();
512       } catch (Exception e) {
513         e.printStackTrace();
514       }
515     }
516 
517 
518     public void generateAllClassesFrame(Element rootElement) {
519       try {
520         VelocityContext context = new VelocityContext();
521         Integer maxPackages = new Integer(2);
522         Map[] classes = getClassesMap(rootElement);
523         context.put("classes",classes);
524         Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/allclasses-frame.vm");
525         File baseDir = new File(toDir+"/html");
526         if (!baseDir.exists()) baseDir.mkdirs();
527         FileWriter fw = new FileWriter(baseDir+"/allclasses-frame.html");
528         BufferedWriter writer = new BufferedWriter(fw);
529         template.merge(context , writer);  
530         writer.close();
531       } catch (Exception e) {
532         e.printStackTrace();
533       }
534     }
535 
536     public void generatePackageFrames(Element rootElement) {
537       try {
538         String noPackage = "&lt;none&gt;";
539         Map[] pkgStats = getPackageStatisticsMap(rootElement);
540         for (int i=0;i<pkgStats.length;i++) {
541           Map statMap = pkgStats[i];
542           String packageName = (String)statMap.get("packageName");
543           Map[] classes = getClassesMap(rootElement, packageName);
544           StringTokenizer st = new StringTokenizer(packageName, ".");
545           int packageNameParts = st.countTokens();
546           if(packageName.equals(noPackage))
547               packageNameParts = 0;
548           String stylesheetUrl = "stylesheet.css";
549           for(int k = 0; k < packageNameParts; k++)
550               stylesheetUrl = (new StringBuilder()).append("../")
551                                 .append(stylesheetUrl).toString();
552 
553           VelocityContext context = new VelocityContext();
554           Integer maxPackages = new Integer(2);
555           context.put("packageName",packageName);
556           context.put("stylesheetUrl",stylesheetUrl);
557           context.put("classes",classes);
558 
559           Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/package-frame.vm");
560           String packagePath = packageName.replace(".", "/");
561 
562           String baseDirString = (new StringBuilder()).append(toDir)
563                                    .append("/html").toString();
564           if(!packageName.equals(noPackage))
565               baseDirString = (new StringBuilder()).append(baseDirString)
566                                   .append("/").append(packagePath).toString();
567           File baseDir = new File(baseDirString);
568           if(!baseDir.exists())
569               baseDir.mkdirs();
570           FileWriter fw = new FileWriter(baseDir+"/package-frame.html");
571           BufferedWriter writer = new BufferedWriter(fw);
572           template.merge(context , writer);  
573           writer.close();
574         }
575       } catch (Exception e) {
576         e.printStackTrace();
577       }
578     }
579 
580     public Map[] getTestClassProperties(Element testSuiteElement) {
581       Map[] result = null;
582       TreeMap resultMap = new TreeMap();
583       String noPackage = "&lt;none&gt;";
584       NodeList propsNodes = testSuiteElement.getElementsByTagName(PROPERTIES);
585       if (propsNodes.getLength() >= 1) {
586         NodeList propNodes = ((Element)propsNodes.item(0)).getElementsByTagName(PROPERTY);
587         for (int i=0;i<propNodes.getLength();i++) {
588           NamedNodeMap attributeMap = propNodes.item(i).getAttributes(); 
589           Node valueAttribute = attributeMap.getNamedItem(ATTR_VALUE);
590           Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
591           String name = nameAttribute.getNodeValue();
592           String value = valueAttribute.getNodeValue();
593           Hashtable temp = new Hashtable();
594           temp.put("propertyName",name);
595           temp.put("propertyValue",jsEscape(value));
596           resultMap.put(name,temp);
597         }
598         /*
599         Hashtable temp = new Hashtable();
600         temp.put("propertyName","test");
601         temp.put("propertyValue",jsEscape("test"));
602         resultMap.put("test",temp);
603         */
604       }
605       result = new Hashtable[resultMap.size()];
606       result = (Map[])resultMap.values().toArray(result);
607       return result;
608     }
609     
610     public String jsEscape(String inString) {
611       String result = "";
612       result = inString.replace("//","////");
613       return result;  
614     }
615     
616     public Map[] getTestCaseInfos(Element testSuiteElement) {
617       Map[] result = null;
618       TreeMap resultMap = new TreeMap();
619       String noPackage = "&lt;none&gt;";
620       NodeList testCaseNodes = testSuiteElement.getElementsByTagName(TESTCASE);
621       NodeList sysOutNodes = testSuiteElement.getElementsByTagName(SYSTEM_OUT);
622       NodeList sysErrNodes = testSuiteElement.getElementsByTagName(SYSTEM_ERR);
623       String hasSystemErr = "false";
624       if (sysErrNodes.getLength() > 0) hasSystemErr = "true";
625       String hasSystemOut = "false";
626       if (sysOutNodes.getLength() > 0) hasSystemOut = "true";
627       
628       for (int i=0;i<testCaseNodes.getLength();i++) {
629         Element testCaseNode = (Element)testCaseNodes.item(i);
630           NodeList testCaseErrors = testCaseNode.getElementsByTagName(ERROR);
631           NodeList testCaseFailures = testCaseNode.getElementsByTagName(FAILURE);
632           NodeList testCaseJavadoc = testCaseNode.getElementsByTagName(JAVADOC);
633         String status = "SUCCESS";
634         int errors = testCaseErrors.getLength();
635         int failures = testCaseFailures.getLength();
636         String typeMessage = "";
637         String typeCode = "";
638         if (errors > 0) {
639           typeMessage = testCaseErrors.item(0).getAttributes().getNamedItem(ATTR_MESSAGE).getNodeValue();
640           typeCode = testCaseErrors.item(0).getChildNodes().item(0).getNodeValue();
641           status = "ERROR";
642         } else if (failures > 0) {
643           typeMessage = testCaseFailures.item(0).getAttributes().getNamedItem(ATTR_MESSAGE).getNodeValue();
644           typeCode = testCaseFailures.item(0).getChildNodes().item(0).getNodeValue();
645           status = "FAILURE";
646         }
647         
648         String javadoc = "";
649         if (testCaseJavadoc.getLength() > 0) {
650           javadoc = testCaseJavadoc.item(0).getChildNodes().item(0).getNodeValue();
651         }
652         NamedNodeMap attributeMap = testCaseNode.getAttributes(); 
653         Node classNameAttribute = attributeMap.getNamedItem(ATTR_CLASSNAME);
654         String className = classNameAttribute.getNodeValue();
655         Node nameAttribute      = attributeMap.getNamedItem(ATTR_NAME);
656         String testCaseName = nameAttribute.getNodeValue();
657         Node timeAttribute      = attributeMap.getNamedItem(ATTR_TIME);
658         double time = 0.0;
659         try {time += Double.parseDouble(timeAttribute.getNodeValue()); } catch (Exception e) {}
660 
661         String stylesheet = "TableRowColor";
662         if (errors > 0) stylesheet = "Error";
663         else if (failures > 0) stylesheet = "Failure";
664         
665         String mapKeyString = className+"/"+testCaseName;
666         Map temp = new Hashtable();
667         if (resultMap.containsKey(mapKeyString)) {
668           temp = (Map)resultMap.get(mapKeyString);
669           temp.put("testCaseName",testCaseName);
670           temp.put("typeMessage",typeMessage);            
671           temp.put("className",className);            
672           temp.put("typeCode",typeCode);            
673           temp.put("time",getTimeFormat(time));            
674           temp.put("status",status);            
675           temp.put("javadoc",javadoc);
676           temp.put("stylesheet", stylesheet);
677         } else {
678           temp.put("hasSystemErr",hasSystemErr);
679           temp.put("hasSystemOut",hasSystemOut);
680           temp.put("testCaseName",testCaseName);
681           temp.put("className",className);            
682           temp.put("typeMessage",typeMessage);            
683           temp.put("typeCode",typeCode);            
684           temp.put("status",status);            
685           temp.put("time",getTimeFormat(time));            
686           temp.put("stylesheet", stylesheet);
687           temp.put("javadoc",javadoc);
688           resultMap.put(mapKeyString,temp);
689         }
690       }
691       result = new Hashtable[resultMap.size()];
692       result = (Map[])resultMap.values().toArray(result);
693       for (int i=0;i<result.length;i++){
694         Map tempMap = result[i];
695         tempMap.put("ankerName","anker"+i);
696         tempMap.put("indexNo",""+i);
697         result[i] = tempMap;
698       }  
699       return result;
700     }
701   
702     public String getTimeFormat(double time) {
703       String result = "";
704       try {
705         result = dnf.format(time);
706       } catch (Exception e) {}
707       return result;
708     }
709 
710     public void generateTestClassInfo(Element testCaseElement)
711     {
712         try
713         {
714             String noPackage = "&lt;none&gt;";
715             NamedNodeMap attributeMap = testCaseElement.getAttributes();
716             NodeList sysOutNodes = testCaseElement.getElementsByTagName(SYSTEM_OUT);
717             String hasSystemOut = "false";
718             String systemOut = "";
719             String systemErr = "";
720             if (sysOutNodes.getLength() > 0) {
721               NodeList outChilds = sysOutNodes.item(0).getChildNodes();
722               if (outChilds.getLength() > 0) {
723                 systemOut = outChilds.item(0).getNodeValue();
724               }
725             }   
726             if (!systemOut.trim().equals("")) {   
727               hasSystemOut = "true";
728             }
729             NodeList sysErrNodes = testCaseElement.getElementsByTagName(SYSTEM_ERR);
730             String hasSystemErr = "false";
731             if (sysErrNodes.getLength() > 0) {
732               NodeList errChilds = sysErrNodes.item(0).getChildNodes();
733               if (errChilds.getLength() > 0) {
734                 systemErr = errChilds.item(0).getNodeValue();
735               }
736             }   
737             if (!systemErr.trim().equals("")) {
738               hasSystemErr = "true";
739             }
740     
741             Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
742             Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
743             Node testsAttribute = attributeMap.getNamedItem(ATTR_TESTS);
744             Node errorAttribute = attributeMap.getNamedItem(ATTR_ERRORS);
745             Node failureAttribute = attributeMap.getNamedItem(ATTR_FAILURES);
746             Node timeAttribute = attributeMap.getNamedItem(ATTR_TIME);
747             String packageName = packageAttribute.getNodeValue();
748             String className = nameAttribute.getNodeValue();
749             if(packageName == null || packageName.trim().equals(""))
750                 packageName = noPackage;
751             String packagePath = packageName.replace(".", "/");
752             String fqclassname = (new StringBuilder()).append(packageName).append(".").append(className).toString();
753             int tests = 0;
754             int errors = 0;
755             int failures = 0;
756             double time = 0.0D;
757             try
758             {
759                 tests += Integer.parseInt(testsAttribute.getNodeValue());
760             }
761             catch(Exception e) { }
762             try
763             {
764                 errors += Integer.parseInt(errorAttribute.getNodeValue());
765             }
766             catch(Exception e) { }
767             try
768             {
769                 failures += Integer.parseInt(failureAttribute.getNodeValue());
770             }
771             catch(Exception e) { }
772             try
773             {
774                 time += Double.parseDouble(timeAttribute.getNodeValue());
775             }
776             catch(Exception e) { }
777             Map testCases[] = getTestCaseInfos(testCaseElement);
778             Map properties[] = getTestClassProperties(testCaseElement);
779             String stylesheet = getSummaryStylesheetClass(errors, failures);
780             VelocityContext context = new VelocityContext();
781             StringTokenizer st = new StringTokenizer(packageName, ".");
782             int packageNameParts = st.countTokens();
783             if(packageName.equals(noPackage))
784                 packageNameParts = 0;
785             String stylesheetUrl = "stylesheet.css";
786             for(int i = 0; i < packageNameParts; i++)
787                 stylesheetUrl = (new StringBuilder()).append("../").append(stylesheetUrl).toString();
788 
789             context.put("fqclassname", fqclassname);
790             context.put("className", className);
791             context.put("hasSystemOut", hasSystemOut);
792             context.put("hasSystemErr", hasSystemErr);
793             context.put("packageName", packageName);
794             context.put("testsCount", (new StringBuilder()).append("").append(tests).toString());
795             context.put("errorCount", (new StringBuilder()).append("").append(errors).toString());
796             context.put("failureCount", (new StringBuilder()).append("").append(failures).toString());
797             context.put("time", (new StringBuilder()).append("").append(getTimeFormat(time)).toString());
798             context.put("stylesheet", stylesheet);
799             context.put("stylesheetUrl", stylesheetUrl);
800             context.put("testCases", testCases);
801             context.put("properties", properties);
802             Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/testclass.vm");
803             String baseDirString = (new StringBuilder()).append(toDir).append("/html").toString();
804             if(!packageName.equals(noPackage))
805                 baseDirString = (new StringBuilder()).append(baseDirString).append("/").append(packagePath).toString();
806             File baseDir = new File(baseDirString);
807             if(!baseDir.exists())
808                 baseDir.mkdirs();
809             FileWriter fw = new FileWriter((new StringBuilder()).append(baseDir).append("/").append(className).append(".html").toString());
810             BufferedWriter writer = new BufferedWriter(fw);
811             template.merge(context, writer);
812             writer.close();
813         }
814         catch(Exception e)
815         {
816             e.printStackTrace();
817         }
818     }
819 
820     public void generateTestClassSystemOut(Element testCaseElement) {
821       try {
822         String noPackage = "&lt;none&gt;";
823         
824         NamedNodeMap attributeMap =testCaseElement.getAttributes(); 
825         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
826         Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
827         NodeList sysOutNodes = testCaseElement.getElementsByTagName(SYSTEM_OUT);
828         if (sysOutNodes.getLength() > 0) {
829           NodeList outChilds = sysOutNodes.item(0).getChildNodes();
830           if (outChilds.getLength() > 0) {
831             String systemOut = outChilds.item(0).getNodeValue();
832             if (!systemOut.trim().equals("")) {
833               
834               String packageName = packageAttribute.getNodeValue();
835               String className = nameAttribute.getNodeValue();
836               if (packageName == null || packageName.trim().equals("")) {
837                 packageName = noPackage;
838               }        
839               String packagePath = packageName.replace(".","/");
840               String fqclassname = packageName+"."+className;
841       
842               VelocityContext context = new VelocityContext();
843               StringTokenizer st = new StringTokenizer(packageName,".");
844               int packageNameParts = st.countTokens();
845               if (packageName.equals(noPackage)) packageNameParts = 0;
846               context.put("fqclassname", fqclassname);
847               context.put("systemOut", systemOut);
848       
849               Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/systemout.vm");
850               String baseDirString = toDir+"/html";
851               if (!packageName.equals(noPackage)) {
852                 baseDirString += "/"+packagePath;
853               }
854               File baseDir = new File(baseDirString);
855               if (!baseDir.exists()) baseDir.mkdirs();
856               FileWriter fw = new FileWriter(baseDir+"/"+className+"-out.txt");
857               BufferedWriter writer = new BufferedWriter(fw);
858               template.merge(context , writer);  
859               writer.close();
860             }
861           }
862         }
863       } catch (Exception e) {
864         e.printStackTrace();
865       }
866     }
867 
868     public void generateTestClassSystemErr(Element testCaseElement) {
869       try {
870         String noPackage = "&lt;none&gt;";
871         
872         NamedNodeMap attributeMap =testCaseElement.getAttributes(); 
873         Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
874         Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
875         NodeList sysErrNodes = testCaseElement.getElementsByTagName(SYSTEM_ERR);
876         if (sysErrNodes.getLength() > 0) {
877           NodeList errChilds = sysErrNodes.item(0).getChildNodes();
878           if (errChilds.getLength() > 0) {
879             String systemErr = errChilds.item(0).getNodeValue();
880             if (!systemErr.trim().equals("")) {
881               String packageName = packageAttribute.getNodeValue();
882               String className = nameAttribute.getNodeValue();
883               if (packageName == null || packageName.trim().equals("")) {
884                 packageName = noPackage;
885               }        
886               String packagePath = packageName.replace(".","/");
887               String fqclassname = packageName+"."+className;
888       
889               VelocityContext context = new VelocityContext();
890               StringTokenizer st = new StringTokenizer(packageName,".");
891               int packageNameParts = st.countTokens();
892               if (packageName.equals(noPackage)) packageNameParts = 0;
893               context.put("fqclassname", fqclassname);
894               context.put("systemErr", systemErr);
895       
896               Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/systemerr.vm");
897               String baseDirString = toDir+"/html";
898               if (!packageName.equals(noPackage)) {
899                 baseDirString += "/"+packagePath;
900               }
901               File baseDir = new File(baseDirString);
902               if (!baseDir.exists()) baseDir.mkdirs();
903               FileWriter fw = new FileWriter(baseDir+"/"+className+"-err.txt");
904               BufferedWriter writer = new BufferedWriter(fw);
905               template.merge(context , writer);  
906               writer.close();
907             }
908           }
909         }
910       } catch (Exception e) {
911         e.printStackTrace();
912       }
913     }
914 
915     public void generateTestClassInfos(Element rootElement) {
916         String noPackage = "&lt;none&gt;";
917         NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
918         for (int i=0;i<testsuiteNodes.getLength();i++) {
919           Element testsuiteNode = (Element)testsuiteNodes.item(i);
920           generateTestClassInfo(testsuiteNode);
921           generateTestClassSystemOut(testsuiteNode);
922           generateTestClassSystemErr(testsuiteNode);
923         }
924     }
925 
926     public void generatePackageSummaryFiles(Element rootElement) {
927       try {
928         String noPackage = "&lt;none&gt;";
929         
930         NodeList testsuiteNodes = rootElement.getElementsByTagName(TESTSUITE);
931         for (int i=0;i<testsuiteNodes.getLength();i++) {
932           Element testuiteNode = (Element)testsuiteNodes.item(i);
933           NamedNodeMap attributeMap =testuiteNode.getAttributes(); 
934           Node packageAttribute = attributeMap.getNamedItem(ATTR_PACKAGE);
935           Node nameAttribute = attributeMap.getNamedItem(ATTR_NAME);
936           
937           
938           String packageName = packageAttribute.getNodeValue();
939           String className = nameAttribute.getNodeValue();
940           String packagePath = "";
941           if (packageName == null || packageName.trim().equals("")) {
942             packageName = noPackage;
943           } else {
944             packagePath = packageName.replace(".","/");
945           }        
946 
947           String fqclassname = packageName+"."+className;
948   
949           VelocityContext context = new VelocityContext();
950           StringTokenizer st = new StringTokenizer(packageName,".");
951           int packageNameParts = st.countTokens();
952           if (packageName.equals(noPackage)) packageNameParts = 0;
953           String stylesheetUrl = "stylesheet.css";
954           for(int k = 0; k < packageNameParts; k++)
955               stylesheetUrl = (new StringBuilder()).append("../").append(stylesheetUrl).toString();
956           
957           context.put("packageName", packageName);
958           context.put("stylesheetUrl", stylesheetUrl);
959           context.put("className", className);
960           context.put("fqclassname", fqclassname);
961           context.put("packagePath", packagePath);
962           
963           Map[] classes = getSpecialPackageStatisticsMap(rootElement, packageName);
964           context.put("classes",classes); // stylesheet, tests, errors, failures, time
965   
966           Template template = Velocity.getTemplate("org/woopi/ant/taskdefs/junit/vm/package-summary.vm");
967           String baseDirString = toDir+"/html";
968           if (!packageName.equals(noPackage)) {
969             baseDirString += "/"+packagePath;
970           }
971           File baseDir = new File(baseDirString);
972           if (!baseDir.exists()) baseDir.mkdirs();
973           FileWriter fw = new FileWriter(baseDir+"/package-summary.html");
974           BufferedWriter writer = new BufferedWriter(fw);
975           template.merge(context , writer);  
976           writer.close();
977         }
978       } catch (Exception e) {
979         e.printStackTrace();
980       }
981     }
982 
983     /***
984      * Get the full destination file where to write the result. It is made of
985      * the <tt>todir</tt> and <tt>tofile</tt> attributes.
986      * @return the destination file where should be written the result file.
987      */
988     protected File getDestinationFile() {
989         if (toFile == null) {
990             toFile = DEFAULT_FILENAME;
991         }
992         if (toDir == null) {
993             toDir = getProject().resolveFile(DEFAULT_DIR);
994         }
995         return new File(toDir, toFile);
996     }
997 
998     /***
999      * Get all <code>.xml</code> files in the fileset.
1000      *
1001      * @return all files in the fileset that end with a '.xml'.
1002      */
1003     protected File[] getFiles() {
1004         Vector v = new Vector();
1005         final int size = filesets.size();
1006         for (int i = 0; i < size; i++) {
1007             FileSet fs = (FileSet) filesets.elementAt(i);
1008             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
1009             ds.scan();
1010             String[] f = ds.getIncludedFiles();
1011             for (int j = 0; j < f.length; j++) {
1012                 String pathname = f[j];
1013                 if (pathname.endsWith(".xml")) {
1014                     File file = new File(ds.getBasedir(), pathname);
1015                     file = getProject().resolveFile(file.getPath());
1016                     v.addElement(file);
1017                 }
1018             }
1019         }
1020 
1021         File[] files = new File[v.size()];
1022         v.copyInto(files);
1023         return files;
1024     }
1025 
1026     //----- from now, the methods are all related to DOM tree manipulation
1027 
1028     /***
1029      * Write the DOM tree to a file.
1030      * @param doc the XML document to dump to disk.
1031      * @param file the filename to write the document to. Should obviouslly be a .xml file.
1032      * @throws IOException thrown if there is an error while writing the content.
1033      */
1034     protected void writeDOMTree(Document doc, File file) throws IOException {
1035         OutputStream out = null;
1036         PrintWriter wri = null;
1037         try {
1038             out = new BufferedOutputStream(new FileOutputStream(file));
1039             wri = new PrintWriter(new OutputStreamWriter(out, "UTF8"));
1040             wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
1041             (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, "  ");
1042             wri.flush();
1043             // writers do not throw exceptions, so check for them.
1044             if (wri.checkError()) {
1045                 throw new IOException("Error while writing DOM content");
1046             }
1047         } finally {
1048             if (wri != null) {
1049                 wri.close();
1050                 out = null;
1051             }
1052             if (out != null) {
1053                 out.close();
1054             }
1055         }
1056     }
1057 
1058     /***
1059      * <p> Create a DOM tree.
1060      * Has 'testsuites' as firstchild and aggregates all
1061      * testsuite results that exists in the base directory.
1062      * @return  the root element of DOM tree that aggregates all testsuites.
1063      */
1064     protected Element createDocument() {
1065         // create the dom tree
1066         DocumentBuilder builder = getDocumentBuilder();
1067         Document doc = builder.newDocument();
1068         Element rootElement = doc.createElement(TESTSUITES);
1069         doc.appendChild(rootElement);
1070 
1071         // get all files and add them to the document
1072         File[] files = getFiles();
1073         for (int i = 0; i < files.length; i++) {
1074             try {
1075                 log("Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE);
1076                 //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object
1077                 // will investigate later. It does not use the given directory but
1078                 // the vm dir instead ? Works fine with crimson.
1079                 Document testsuiteDoc
1080                     = builder.parse("file:///" + files[i].getAbsolutePath());
1081                 Element elem = testsuiteDoc.getDocumentElement();
1082                 // make sure that this is REALLY a testsuite.
1083                 if (TESTSUITE.equals(elem.getNodeName())) {
1084                     addTestSuite(rootElement, elem);
1085                 } else {
1086                     // issue a warning.
1087                     log("the file " + files[i]
1088                         + " is not a valid testsuite XML document",
1089                         Project.MSG_WARN);
1090                 }
1091             } catch (SAXException e) {
1092                 // a testcase might have failed and write a zero-length document,
1093                 // It has already failed, but hey.... mm. just put a warning
1094                 log("The file " + files[i] + " is not a valid XML document. "
1095                     + "It is possibly corrupted.", Project.MSG_WARN);
1096                 log(StringUtils.getStackTrace(e), Project.MSG_DEBUG);
1097             } catch (IOException e) {
1098                 log("Error while accessing file " + files[i] + ": "
1099                     + e.getMessage(), Project.MSG_ERR);
1100             }
1101         }
1102         return rootElement;
1103     }
1104 
1105     /***
1106      * <p> Add a new testsuite node to the document.
1107      * The main difference is that it
1108      * split the previous fully qualified name into a package and a name.
1109      * <p> For example: <tt>org.apache.Whatever</tt> will be split into
1110      * <tt>org.apache</tt> and <tt>Whatever</tt>.
1111      * @param root the root element to which the <tt>testsuite</tt> node should
1112      *        be appended.
1113      * @param testsuite the element to append to the given root. It will slightly
1114      *        modify the original node to change the name attribute and add
1115      *        a package one.
1116      */
1117     protected void addTestSuite(Element root, Element testsuite) {
1118         String fullclassname = testsuite.getAttribute(ATTR_NAME);
1119         int pos = fullclassname.lastIndexOf('.');
1120 
1121         // a missing . might imply no package at all. Don't get fooled.
1122         String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos);
1123         String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
1124         Element copy = (Element) DOMUtil.importNode(root, testsuite);
1125 
1126         // modify the name attribute and set the package
1127         copy.setAttribute(ATTR_NAME, classname);
1128         copy.setAttribute(ATTR_PACKAGE, pkgName);
1129     }
1130 
1131     /***
1132      * Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt>
1133      * if something is going wrong. It is fatal anyway.
1134      * @todo factorize this somewhere else. It is duplicated code.
1135      * @return a new document builder to create a DOM
1136      */
1137     private static DocumentBuilder getDocumentBuilder() {
1138         try {
1139             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
1140         } catch (Exception exc) {
1141             throw new ExceptionInInitializerError(exc);
1142         }
1143     }
1144 
1145 }