Browse Source

Huge summer cleanup to the code...

- takes advantage of Xalan redirect extension, therefore I don't need extra code to split the document several pieces and process them with different stylesheets.
- now using a strategy to determine a valid Xalan executor at runtime. It first check for Xalan2 then for Xalan1.
As a convenience the Xalan version is displayed in the output.


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@269504 13f79535-47bb-0310-9956-ffa450edef68
master
Stephane Bailliez 24 years ago
parent
commit
ce8272e144
1 changed files with 140 additions and 326 deletions
  1. +140
    -326
      src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java

+ 140
- 326
src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java View File

@@ -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:
* <ul>
* <li>packageListFrame - list of all packages.
* <li>classListFrame - summary of all testsuites belonging to a package or tests list.
* <li>classFrame - details of all tests made for a package or for a testsuite.
* </ul>
* 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 <tt>setStyleDir()</tt>.
* Stylesheets must be as follows:
* <ul>
* <li><b>all-packages.xsl</b> create the package list. It creates
* all-packages.html file in the html folder and it is load in the packageListFrame</li>
* <li><b>all-classes.xsl</b> creates the class list. It creates the all-classes.html
* file in the html folder is loaded by the 'classListFrame' frame</li>
* <li><b>overview-packages.xsl</b> 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</li>
* <li><b>class-detail.xsl</b> 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
* <u>MyClass-detail.html</u>. This file is load in the "classFrame" frame.</li>
* <li><b>package-summary.xsl</b> allows to create a summary on the package.
* The resulting html file is write in the package directory. The name of this
* file is <u>package-summary.html</u> This file is load in the "classFrame" frame.</li>
* <li><b>classes-list.xsl</b> 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 <u>class-list.html</u></li>
* <li>
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
* @author <a href="mailto:ndelahaye@imediation.com">Nicolas Delahaye</a>
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/
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 <tt>FRAMES</tt> or <tt>NOFRAMES</tt> */
protected String format;

/** the file extension of the generated files. As a default it will be <tt>.html</tt> */
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);
}
}

Loading…
Cancel
Save