diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java
index 464740257..9f926bbbd 100644
--- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java
@@ -56,17 +56,14 @@ package org.apache.tools.ant.taskdefs.optional.junit;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.EnumeratedAttribute;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
+import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
+import java.lang.reflect.Field;
+import java.net.URL;
import org.apache.xalan.xslt.XSLTProcessorFactory;
import org.apache.xalan.xslt.XSLTProcessor;
@@ -76,6 +73,13 @@ import org.apache.xalan.xslt.XSLTResultTarget;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.Result;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -84,6 +88,7 @@ import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.SAXException;
+import org.xml.sax.InputSource;
/**
* Transform a JUnit xml report.
@@ -91,60 +96,21 @@ import org.xml.sax.SAXException;
* style. The non-framed style is convenient to have a concise report via mail, the
* framed report is much more convenient if you want to browse into different
* packages or testcases since it is a Javadoc like report.
- * In the framed report, there are 3 frames:
- *
- * - packageListFrame - list of all packages.
- *
- classListFrame - summary of all testsuites belonging to a package or tests list.
- *
- classFrame - details of all tests made for a package or for a testsuite.
- *
- * As a default, the transformer will use its default stylesheets, they may not be
- * be appropriate for users who wants to customize their reports, so you can indicates
- * your own stylesheets by using setStyleDir().
- * Stylesheets must be as follows:
- *
- * - all-packages.xsl create the package list. It creates
- * all-packages.html file in the html folder and it is load in the packageListFrame
- * - all-classes.xsl creates the class list. It creates the all-classes.html
- * file in the html folder is loaded by the 'classListFrame' frame
- * - overview-packages.xsl allows to get summary on all tests made
- * for each packages and each class that not include in a package. The filename
- * is overview-packages.html
- * - class-detail.xsl is the style for the detail of the tests made on a class.
- * the Html resulting page in write in the directory of the package and the name
- * of this page is the name of the class with "-detail" element. For instance,
- * the style is applied on the MyClass testsuite, the resulting filename is
- * MyClass-detail.html. This file is load in the "classFrame" frame.
- * - package-summary.xsl allows to create a summary on the package.
- * The resulting html file is write in the package directory. The name of this
- * file is package-summary.html This file is load in the "classFrame" frame.
- * - classes-list.xsl create the list of the class in this package.
- * The resulting html file is write in the package directory and it is load in
- * the 'classListFrame' frame. The name of the resulting file is class-list.html
- * -
*
- * @author Stephane Bailliez
- * @author Nicolas Delahaye
+ * @author Stephane Bailliez
*/
-public class AggregateTransformer {
-
- public final static String ALLPACKAGES = "all-packages";
-
- public final static String ALLCLASSES = "all-classes";
-
- public final static String OVERVIEW_PACKAGES = "overview-packages";
-
- public final static String CLASS_DETAILS = "class-details";
-
- public final static String CLASSES_LIST = "classes-list";
-
- public final static String PACKAGE_SUMMARY = "package-summary";
-
- public final static String OVERVIEW_SUMMARY = "overview-summary";
+public class AggregateTransformer {
public final static String FRAMES = "frames";
public final static String NOFRAMES = "noframes";
+ public static class Format extends EnumeratedAttribute {
+ public String[] getValues(){
+ return new String[]{FRAMES, NOFRAMES};
+ }
+ }
+
/** Task */
protected Task task;
@@ -160,9 +126,6 @@ public class AggregateTransformer {
/** the format to use for the report. Must be FRAMES or NOFRAMES */
protected String format;
- /** the file extension of the generated files. As a default it will be .html */
- protected String extension;
-
/** XML Parser factory */
protected static final DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
@@ -170,8 +133,8 @@ public class AggregateTransformer {
this.task = task;
}
- public void setFormat(String format){
- this.format = format;
+ public void setFormat(Format format){
+ this.format = format.getValue();
}
public void setXmlDocument(Document doc){
@@ -183,9 +146,16 @@ public class AggregateTransformer {
* to set the file directly. Much more for testing purposes.
* @param xmlfile xml file to be processed
*/
- void setXmlfile(File xmlfile) throws BuildException {
+ protected void setXmlfile(File xmlfile) throws BuildException {
try {
- setXmlDocument(readDocument(xmlfile));
+ DocumentBuilder builder = dbfactory.newDocumentBuilder();
+ InputStream in = new FileInputStream(xmlfile);
+ try {
+ Document doc = builder.parse(in);
+ setXmlDocument(doc);
+ } finally {
+ in.close();
+ }
} catch (Exception e){
throw new BuildException("Error while parsing document: " + xmlfile, e);
}
@@ -208,15 +178,7 @@ public class AggregateTransformer {
/** set the extension of the output files */
public void setExtension(String ext){
- this.extension = ext;
- }
-
- /** get the extension, if it is null, it will use .html as the default */
- protected String getExtension(){
- if (extension == null) {
- extension = ".html";
- }
- return extension;
+ task.log("extension is not used anymore", Project.MSG_WARN);
}
public void transform() throws BuildException {
@@ -224,19 +186,8 @@ public class AggregateTransformer {
final long t0 = System.currentTimeMillis();
try {
Element root = document.getDocumentElement();
-
- if (NOFRAMES.equals(format)) {
- //createCascadingStyleSheet();
- createSinglePageSummary(root);
- } else {
- createFrameStructure();
- createCascadingStyleSheet();
- createPackageList(root);
- createClassList(root);
- createPackageOverview(root);
- createAllTestSuiteDetails(root);
- createAllPackageDetails(root);
- }
+ XalanExecutor executor = XalanExecutor.newInstance(this);
+ executor.execute();
} catch (Exception e){
throw new BuildException("Errors while applying transformations", e);
}
@@ -246,283 +197,146 @@ public class AggregateTransformer {
/** check for invalid options */
protected void checkOptions() throws BuildException {
- if ( !FRAMES.equals(format) && !NOFRAMES.equals(format)) {
- throw new BuildException("Invalid format. Must be 'frames' or 'noframes' but was: '" + format + "'");
- }
// set the destination directory relative from the project if needed.
if (toDir == null) {
toDir = task.getProject().resolveFile(".");
} else if ( !toDir.isAbsolute() ) {
toDir = task.getProject().resolveFile(toDir.getPath());
}
- // create the directories if needed
- if (!toDir.exists()) {
- if (!toDir.mkdirs()){
- throw new BuildException("Could not create directory " + toDir);
- }
- }
- }
-
- /** create a single page summary */
- protected void createSinglePageSummary(Element root) throws IOException, SAXException {
- transform(root, OVERVIEW_SUMMARY + ".xsl", OVERVIEW_SUMMARY + getExtension());
}
/**
- * read the xml file that should be the resuiting file of the testcase.
- * @param filename name of the xml resulting file of the testcase.
+ * Get the systemid of the appropriate stylesheet based on its
+ * name and styledir. If no styledir is defined it will load
+ * it as a java resource in the current package, otherwise it
+ * will get it from the given directory.
+ * @throws IOException thrown if the requested stylesheet does
+ * not exist.
*/
- protected Document readDocument(File file) throws IOException, SAXException, ParserConfigurationException {
- DocumentBuilder builder = dbfactory.newDocumentBuilder();
- InputStream in = new FileInputStream(file);
- try {
- return builder.parse(in);
- } finally {
- in.close();
- }
- }
-
- protected void createCascadingStyleSheet() throws IOException, SAXException {
- InputStream in = null;
- if (styleDir == null) {
- in = getResourceAsStream("html/stylesheet.css");
- } else {
- in = new FileInputStream(new File(styleDir, "stylesheet.css"));
+ protected String getStylesheetSystemId() throws IOException {
+ String xslname = "junit-frames.xsl";
+ if (NOFRAMES.equals(format)){
+ xslname = "junit-noframes.xsl";
}
- OutputStream out = new FileOutputStream( new File(toDir, "stylesheet.css"));
- copy(in, out);
- }
-
- protected void createFrameStructure() throws IOException, SAXException{
- InputStream in = null;
- if (styleDir == null) {
- in = getResourceAsStream("html/index.html");
+ URL url = null;
+ if (styleDir == null){
+ url = getClass().getResource(xslname);
+ if (url == null){
+ throw new FileNotFoundException("Could not find jar resource " + xslname);
+ }
} else {
- in = new FileInputStream(new File(styleDir, "index.html"));
- }
- OutputStream out = new FileOutputStream( new File(toDir, "index.html") );
- copy(in, out);
- }
-
- /**
- * Create the list of all packages.
- * @param root root of the xml document.
- */
- protected void createPackageList(Node root) throws SAXException {
- transform(root, ALLPACKAGES + ".xsl", ALLPACKAGES + getExtension());
- }
-
- /**
- * Create the list of all classes.
- * @param root root of the xml document.
- */
- protected void createClassList(Node root) throws SAXException {
- transform(root, ALLCLASSES + ".xsl", ALLCLASSES + getExtension());
- }
-
- /**
- * Create the summary used in the overview.
- * @param root root of the xml document.
- */
- protected void createPackageOverview(Node root) throws SAXException {
- transform(root, OVERVIEW_PACKAGES + ".xsl", OVERVIEW_PACKAGES + getExtension());
- }
-
- /**
- * @return the list of all packages that exists defined in testsuite nodes
- */
- protected Enumeration getPackages(Element root){
- Hashtable map = new Hashtable();
- NodeList testsuites = root.getElementsByTagName(XMLConstants.TESTSUITE);
- final int size = testsuites.getLength();
- for (int i = 0; i < size; i++){
- Element testsuite = (Element) testsuites.item(i);
- String packageName = testsuite.getAttribute(XMLConstants.ATTR_PACKAGE);
- if (packageName == null){
- //@todo replace the exception by something else
- throw new IllegalStateException("Invalid 'testsuite' node: should contains 'package' attribute");
+ File file = new File(styleDir, xslname);
+ if (!file.exists()){
+ throw new FileNotFoundException("Could not find file '" + file + "'");
}
- map.put(packageName, packageName);
+ url = new URL("file", "", file.getAbsolutePath());
}
- return map.keys();
+ return url.toExternalForm();
}
- /**
- * create all resulting html pages for all testsuites.
- * @param root should be 'testsuites' node.
- */
- protected void createAllTestSuiteDetails(Element root) throws SAXException {
- NodeList testsuites = root.getElementsByTagName(XMLConstants.TESTSUITE);
- final int size = testsuites.getLength();
- for (int i = 0; i < size; i++){
- Element testsuite = (Element) testsuites.item(i);
- createTestSuiteDetails(testsuite);
- }
- }
+}
- /**
- * create the html resulting page of one testsuite.
- * @param root should be 'testsuite' node.
- */
- protected void createTestSuiteDetails(Element testsuite) throws SAXException {
-
- String packageName = testsuite.getAttribute(XMLConstants.ATTR_PACKAGE);
- String pkgPath = packageToPath(packageName);
-
- // get the class name
- String name = testsuite.getAttribute(XMLConstants.ATTR_NAME);
-
- // get the name of the testsuite and create the filename of the ouput html page
- String filename = name + "-details" + getExtension();
- String fullpathname = pkgPath + filename; // there's already end path separator to pkgPath
-
- // apply the style on the document.
- transform(testsuite, CLASS_DETAILS + ".xsl", fullpathname);
- }
+/**
+ * Command class that encapsulate specific behavior for each
+ * Xalan version. The right executor will be instantiated at
+ * runtime via class lookup. For instance, it will check first
+ * for Xalan2, then for Xalan1.
+ */
+abstract class XalanExecutor {
+ /** the transformer caller */
+ protected AggregateTransformer caller;
- /**
- * create the html resulting page of the summary of each package of the root element.
- * @param root should be 'testsuites' node.
- */
- protected void createAllPackageDetails(Element root) throws SAXException, ParserConfigurationException {
- Enumeration packages = getPackages(root);
- while ( packages.hasMoreElements() ){
- String pkgname = (String)packages.nextElement();
- // for each package get the list of its testsuite.
- DOMUtil.NodeFilter pkgFilter = new PackageFilter(pkgname);
- NodeList testsuites = DOMUtil.listChildNodes(root, pkgFilter, false);
- Element doc = buildDocument(testsuites);
- // skip package details if the package does not exist (root package)
- if( !pkgname.equals("") ){
- createPackageDetails(doc, pkgname);
- }
- }
+ /** set the caller for this object. */
+ private final void setCaller(AggregateTransformer caller){
+ this.caller = caller;
}
- protected String packageToPath(String pkgname){
- if (!pkgname.equals("")) {
- return pkgname.replace('.', File.separatorChar) + File.separatorChar;
+ /** get the appropriate stream based on the format (frames/noframes) */
+ protected OutputStream getOutputStream() throws IOException {
+ if (caller.FRAMES.equals(caller.format)){
+ // dummy output for the framed report
+ // it's all done by extension...
+ return new ByteArrayOutputStream();
+ } else {
+ return new FileOutputStream(new File(caller.toDir, "junit-noframes.html"));
}
- return "." + File.separatorChar;
- }
-
- /**
- * create the html resulting page of the summary of a package .
- * @param root should be 'testsuites' node.
- * @param pkgname Name of the package that we want a summary.
- */
- protected void createPackageDetails(Node root, String pkgname) throws SAXException {
- String path = packageToPath(pkgname);
-
- // apply style to get the list of the classes of this package and
- // display it in the classListFrame.
- transform(root, CLASSES_LIST + ".xsl", path + CLASSES_LIST + getExtension());
-
- // apply style to get a summary on this package.
- transform(root, PACKAGE_SUMMARY + ".xsl", path + PACKAGE_SUMMARY + getExtension());
}
- /**
- * Create an element root ("testsuites")
- * and import all nodes as children of this element.
- *
- */
- protected Element buildDocument(NodeList list) throws ParserConfigurationException {
- DocumentBuilder builder = dbfactory.newDocumentBuilder();
- Document doc = builder.newDocument();
- Element elem = doc.createElement(XMLConstants.TESTSUITES);
- final int len = list.getLength();
- for(int i=0 ; i < len ; i++) {
- DOMUtil.importNode(elem, list.item(i));
- }
- return elem;
- }
+ /** override to perform transformation */
+ abstract void execute() throws Exception;
/**
- * Apply a template on a part of the xml document.
- *
- * @param root root of the document fragment
- * @param xslfile style file
- * @param outfilename filename of the result of the style applied on the Node
- *
- * @throws SAXException SAX Parsing Error on the style Sheet.
+ * Create a valid Xalan executor. It checks first if Xalan2 is
+ * present, if not it checks for xalan1. If none is available, it
+ * fails.
+ * @param caller object containing the transformation information.
+ * @throws BuildException thrown if it could not find a valid xalan
+ * executor.
*/
- protected void transform(Node root, String xslname, String htmlname) throws SAXException {
- try{
- XSLTInputSource xsl_source = getXSLStreamSource(xslname);
- XSLTProcessor processor = XSLTProcessorFactory.getProcessor();
- File htmlfile = new File(toDir, htmlname);
- // create the directory if it does not exist
- File dir = new File(htmlfile.getParent()); // getParentFile is in JDK1.2+
- if (!dir.exists()) {
- dir.mkdirs();
+ static XalanExecutor newInstance(AggregateTransformer caller) throws BuildException {
+ Class procVersion = null;
+ XalanExecutor executor = null;
+ try {
+ procVersion = Class.forName("org.apache.xalan.processor.XSLProcessorVersion");
+ executor = new Xalan2Executor();
+ } catch (Exception xalan2missing){
+ try {
+ procVersion = Class.forName("org.apache.xalan.xslt.XSLProcessorVersion");
+ executor = new Xalan1Executor();
+ } catch (Exception xalan1missing){
+ throw new BuildException("Could not find xalan2 nor xalan1 in the classpath. Check http://xml.apache.org/xalan-j");
}
- task.log("Applying '" + xslname + "'. Generating '" + htmlfile + "'", Project.MSG_VERBOSE);
- processor.process( new XSLTInputSource(root), xsl_source, new XSLTResultTarget(htmlfile.getAbsolutePath()) );
- } catch (IOException e){
- task.log(e.getMessage(), Project.MSG_ERR);
- e.printStackTrace(); //@todo bad, change this
- throw new SAXException(e.getMessage());
}
+ String version = getXalanVersion(procVersion);
+ caller.task.log("Using Xalan version: " + version);
+ executor.setCaller(caller);
+ return executor;
}
-
- /**
- * default xsls are embedded in the distribution jar. As a default we will use
- * them, otherwise we will get the one supplied by the client in a given
- * directory. It must have the same name.
- */
- protected XSLTInputSource getXSLStreamSource(String name) throws IOException {
- InputStream in;
- String systemId; //we need this because there are references in xsls
- if (styleDir == null){
- in = getResourceAsStream("xsl/" + name);
- systemId = getClass().getResource("xsl/" + name).toString();
- } else {
- File f = new File(styleDir, name);
- in= new FileInputStream(f);
- systemId = "file:///" + f.getAbsolutePath();
- }
- XSLTInputSource ss = new XSLTInputSource(in);
- ss.setSystemId(systemId);
- return ss;
- }
-
- private InputStream getResourceAsStream(String name) throws FileNotFoundException {
- InputStream in = getClass().getResourceAsStream(name);
- if (in == null) {
- throw new FileNotFoundException("Could not find resource '" + name + "'");
+ /** pretty useful data (Xalan version information) to display. */
+ private static String getXalanVersion(Class procVersion) {
+ try {
+ Field f = procVersion.getField("S_VERSION");
+ return f.get(null).toString();
+ } catch (Exception e){
+ return "?";
}
- return in;
}
+}
- /** Do some raw stream copying */
- private static void copy(InputStream in, OutputStream out) throws IOException {
- int size = -1;
- byte[] buffer = new byte[1024];
- // Make the copy
- while( (size = in.read(buffer)) != -1){
- out.write(buffer,0,size);
- }
+/**
+ * Xalan executor via JAXP. Nothing special must exists in the classpath
+ * besides of course, a parser, jaxp and xalan.
+ */
+class Xalan2Executor extends XalanExecutor {
+ void execute() throws Exception {
+ TransformerFactory tfactory = TransformerFactory.newInstance();
+ String system_id = caller.getStylesheetSystemId();
+ Source xsl_src = new StreamSource(system_id);
+ Transformer tformer = tfactory.newTransformer(xsl_src);
+ Source xml_src = new DOMSource(caller.document);
+ OutputStream os = getOutputStream();
+ tformer.setParameter("output.dir", caller.toDir.getAbsolutePath());
+ Result result = new StreamResult(os);
+ tformer.transform(xml_src, result);
}
+}
-
- /**
- * allow us to check if the node is a object of a specific package.
- */
- protected static class PackageFilter implements DOMUtil.NodeFilter {
- private final String pkgName;
-
- PackageFilter(String pkgname) {
- this.pkgName = pkgname;
- }
- /**
- * if the node receive is not a element then return false
- * check if the node is a class of this package.
- */
- public boolean accept(Node node) {
- String pkgname = DOMUtil.getNodeAttribute(node, XMLConstants.ATTR_PACKAGE);
- return pkgName.equals(pkgname);
- }
+/**
+ * Xalan 1 executor. It will need a lot of things in the classpath:
+ * xerces for the serialization, xalan and bsf for the extension.
+ * @todo do everything via reflection to avoid compile problems ?
+ */
+class Xalan1Executor extends XalanExecutor {
+ void execute() throws Exception {
+ XSLTProcessor processor = XSLTProcessorFactory.getProcessor();
+ // need to quote otherwise it breaks because of "extra illegal tokens"
+ processor.setStylesheetParam("output.dir", "'" + caller.toDir.getAbsolutePath() + "'");
+ XSLTInputSource xml_src = new XSLTInputSource(caller.document);
+ String system_id = caller.getStylesheetSystemId();
+ XSLTInputSource xsl_src = new XSLTInputSource(system_id);
+ OutputStream os = getOutputStream();
+ XSLTResultTarget target = new XSLTResultTarget(os);
+ processor.process( xml_src, xsl_src, target);
}
}