TransformWrapper that uses in-memory byte streams instead of files/URIs

diff --git a/java/src/org/apache/qetest/xslwrapper/TraxStreamWrapper.java b/java/src/org/apache/qetest/xslwrapper/TraxStreamWrapper.java
new file mode 100644
index 0000000..4f9ccca
--- /dev/null
+++ b/java/src/org/apache/qetest/xslwrapper/TraxStreamWrapper.java
@@ -0,0 +1,530 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2001 The Apache Software Foundation.  All rights 
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Xalan" and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written 
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, Lotus
+ * Development Corporation., http://www.lotus.com.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.apache.qetest.xslwrapper;
+import org.apache.qetest.QetestUtils;
+
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+/**
+ * Implementation of TransformWrapper that uses the TrAX API and 
+ * uses in-memory streams for it's sources.
+ *
+ * <p>This implementation separates the process of reading xml and xsl 
+ * files from disk into byte arrays out from the time processing of 
+ * a new StreamSource(byte[]) takes to build a stylesheet.
+ * It also separates the time of performing the transformation 
+ * to a StreamResult(byte[]) from the time spent simply sending 
+ * the byte[] through a FileOutputStream to disk.</p>
+ * 
+ * <p><b>Important!</b>  The underlying System property of 
+ * javax.xml.transform.TransformerFactory will determine the actual 
+ * TrAX implementation used.  This value will be reported out in 
+ * our getProcessorInfo() method.</p>
+ *
+ * <p>//@todo: currently limited to reading in files whose length 
+ * will fit into an int value.  The actual file reading routine 
+ * should be updated in several of our methods.</p>
+ * 
+ * @author Shane Curcuru
+ * @version $Id$
+ */
+public class TraxStreamWrapper extends TransformWrapperHelper
+{
+
+    /**
+     * TransformerFactory to use; constructed in newProcessor().
+     */
+    protected TransformerFactory factory = null;
+
+
+    /**
+     * Templates to use for buildStylesheet().
+     */
+    protected Templates builtTemplates = null;
+
+
+    /**
+     * Cached copy of newProcessor() Hashtable.
+     */
+    protected Hashtable newProcessorOpts = null;
+
+
+    /**
+     * Get a general description of this wrapper itself.
+     *
+     * @return Uses TrAX to perform transforms from StreamSource(systemId)
+     */
+    public String getDescription()
+    {
+        return "Uses TrAX to perform transforms from StreamSource(stream)";
+    }
+
+
+    /**
+     * Get a specific description of the wrappered processor.  
+     *
+     * @return specific description of the underlying processor or 
+     * transformer implementation: this should include both the 
+     * general product name, as well as specific version info.  If 
+     * possible, should be implemented without actively creating 
+     * an underlying processor.
+     */
+    public Properties getProcessorInfo()
+    {
+        Properties p = TraxWrapperUtils.getTraxInfo();
+        p.put("traxwrapper.method", "streams");
+        p.put("traxwrapper.desc", getDescription());
+        return p;
+    }
+
+
+    /**
+     * Actually create/initialize an underlying processor or factory.
+     * 
+     * For TrAX/javax.xml.transform implementations, this creates 
+     * a new TransformerFactory.  
+     *
+     * @param options Hashtable of options, unused.
+     *
+     * @return (Object)getProcessor() as a side-effect, this will 
+     * be null if there was any problem creating the processor OR 
+     * if the underlying implementation doesn't use this
+     *
+     * @throws Exception covers any underlying exceptions thrown 
+     * by the actual implementation
+     */
+    public Object newProcessor(Hashtable options) throws Exception
+    {
+        newProcessorOpts = options;
+        //@todo do we need to do any other cleanup?
+        reset(false);
+        factory = TransformerFactory.newInstance();
+        return (Object)factory;
+    }
+
+
+    /**
+     * Transform supplied xmlName file with the stylesheet in the 
+     * xslName file into a resultName file.
+     *
+     * Names are assumed to be local path\filename references, and 
+     * will be read as byte streams before being passed to 
+     * underlying StreamSources, etc.  
+     *
+     * @param xmlName local path\filename of XML file to transform
+     * @param xslName local path\filename of XSL stylesheet to use
+     * @param resultName local path\filename to put result in
+     *
+     * @return array of longs denoting timing of all parts of 
+     * our operation: IDX_OVERALL, IDX_XSLREAD, IDX_XSLBUILD, 
+     * IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE
+     *
+     * @throws Exception any underlying exceptions from the 
+     * wrappered processor are simply allowed to propagate; throws 
+     * a RuntimeException if any other problems prevent us from 
+     * actually completing the operation
+     */
+    public long[] transform(String xmlName, String xslName, String resultName)
+        throws Exception
+    {
+        preventFootShooting();
+        long startTime = 0;
+        long xslRead = 0;
+        long xslBuild = 0;
+        long xmlRead = 0;
+        long transform = 0;
+        long resultWrite = 0;
+   
+        File xslFile = new File(xslName);
+        int xslLength = new Long(xslFile.length()).intValue(); //@todo warning: possible overflow
+        byte[] xslBytes = new byte[xslLength];
+        FileInputStream xslStream = new FileInputStream(xslFile);
+        // Timed: read xsl into a byte array
+        startTime = System.currentTimeMillis();
+        int xslRetVal = xslStream.read(xslBytes);
+        xslRead = System.currentTimeMillis() - startTime;
+
+        // Untimed: create StreamSource and setSystemId
+        StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes));
+        // Note that systemIds must be a legal URI
+        xslSource.setSystemId(QetestUtils.filenameToURL(xslName));
+
+        // Timed: build Transformer from StreamSource
+        startTime = System.currentTimeMillis();
+        Transformer transformer = factory.newTransformer(xslSource);
+        xslBuild = System.currentTimeMillis() - startTime;
+
+        File xmlFile = new File(xmlName);
+        int xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow
+        byte[] xmlBytes = new byte[xmlLength];
+        FileInputStream xmlStream = new FileInputStream(xmlFile);
+        // Timed: read xml into a byte array
+        startTime = System.currentTimeMillis();
+        int xmlRetVal = xmlStream.read(xmlBytes);
+        xmlRead = System.currentTimeMillis() - startTime;
+
+        // Untimed: create StreamSource and setSystemId
+        StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes));
+        xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName));
+
+        // Untimed: create StreamResult
+        ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+        StreamResult byteResult = new StreamResult(outBytes);
+        
+        // Untimed: Apply any parameters needed
+        applyParameters(transformer);
+
+        // Timed: build xml (so to speak) and transform
+        startTime = System.currentTimeMillis();
+        transformer.transform(xmlSource, byteResult);
+        transform = System.currentTimeMillis() - startTime;
+
+        // Timed: writeResults from the byte array
+        startTime = System.currentTimeMillis();
+        byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
+        FileOutputStream writeStream = new FileOutputStream(resultName);
+        writeStream.write(writeBytes);
+        writeStream.close();
+        resultWrite = System.currentTimeMillis() - startTime;
+
+        long[] times = getTimeArray();
+        times[IDX_OVERALL] = xslRead + xslBuild + xmlRead 
+                             + transform + resultWrite;
+        times[IDX_XSLREAD] = xslRead;
+        times[IDX_XSLBUILD] = xslBuild;
+        times[IDX_XMLREAD] = xmlRead;
+        times[IDX_TRANSFORM] = transform;
+        times[IDX_RESULTWRITE] = resultWrite;
+        return times;
+    }
+
+
+    /**
+     * Pre-build/pre-compile a stylesheet.
+     *
+     * Although the actual mechanics are implementation-dependent, 
+     * most processors have some method of pre-setting up the data 
+     * needed by the stylesheet itself for later use in transforms.
+     * In TrAX/javax.xml.transform, this equates to creating a 
+     * Templates object.
+     * 
+     * Sets isStylesheetReady() to true if it succeeds.  Users can 
+     * then call transformWithStylesheet(xmlName, resultName) to 
+     * actually perform a transformation with this pre-built 
+     * stylesheet.
+     *
+     * @param xslName local path\filename of XSL stylesheet to use
+     *
+     * @return array of longs denoting timing of all parts of 
+     * our operation: IDX_OVERALL, IDX_XSLREAD, IDX_XSLBUILD
+     *
+     * @throws Exception any underlying exceptions from the 
+     * wrappered processor are simply allowed to propagate; throws 
+     * a RuntimeException if any other problems prevent us from 
+     * actually completing the operation
+     *
+     * @see #transformWithStylesheet(String xmlName, String resultName)
+     */
+    public long[] buildStylesheet(String xslName) throws Exception
+    {
+        preventFootShooting();
+        long startTime = 0;
+        long xslRead = 0;
+        long xslBuild = 0;
+        
+        File xslFile = new File(xslName);
+        int xslLength = new Long(xslFile.length()).intValue(); //@todo warning: possible overflow
+        byte[] xslBytes = new byte[xslLength];
+        FileInputStream xslStream = new FileInputStream(xslFile);
+        // Timed: read xsl into a byte array
+        startTime = System.currentTimeMillis();
+        int xslRetVal = xslStream.read(xslBytes);
+        xslRead = System.currentTimeMillis() - startTime;
+
+        // Untimed: create StreamSource and setSystemId
+        StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes));
+        // Note that systemIds must be a legal URI
+        xslSource.setSystemId(QetestUtils.filenameToURL(xslName));
+
+        // Timed: build Transformer from StreamSource
+        startTime = System.currentTimeMillis();
+        builtTemplates = factory.newTemplates(xslSource);
+        xslBuild = System.currentTimeMillis() - startTime;
+
+        // Set internal state that we have a templates ready
+        //  Note: in theory, there's no need to check builtTemplates
+        //  since the newTemplates should never return null
+        //  (it might have thrown an exception, but we don't care)
+        m_stylesheetReady = true;
+
+        long[] times = getTimeArray();
+        times[IDX_OVERALL] = xslBuild + xslRead;
+        times[IDX_XSLREAD] = xslRead;
+        times[IDX_XSLBUILD] = xslBuild;
+        return times;
+    }
+
+
+    /**
+     * Transform supplied xmlName file with a pre-built/pre-compiled 
+     * stylesheet into a resultName file.  
+     *
+     * User must have called buildStylesheet(xslName) beforehand,
+     * obviously.
+     * Names are assumed to be local path\filename references, and 
+     * will be converted to URLs as needed.
+     *
+     * @param xmlName local path\filename of XML file to transform
+     * @param resultName local path\filename to put result in
+     *
+     * @return array of longs denoting timing of all parts of 
+     * our operation: IDX_OVERALL, 
+     * IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE
+     *
+     * @throws Exception any underlying exceptions from the 
+     * wrappered processor are simply allowed to propagate; throws 
+     * a RuntimeException if any other problems prevent us from 
+     * actually completing the operation; throws an 
+     * IllegalStateException if isStylesheetReady() == false.
+     *
+     * @see #buildStylesheet(String xslName)
+     */
+    public long[] transformWithStylesheet(String xmlName, String resultName)
+        throws Exception
+    {
+        if (!isStylesheetReady())
+            throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
+
+        preventFootShooting();
+        long startTime = 0;
+        long xslRead = 0;
+        long xslBuild = 0;
+        long xmlRead = 0;
+        long transform = 0;
+        long resultWrite = 0;
+   
+        // UNTimed: get Transformer from Templates
+        Transformer transformer = builtTemplates.newTransformer();
+
+        File xmlFile = new File(xmlName);
+        int xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow
+        byte[] xmlBytes = new byte[xmlLength];
+        FileInputStream xmlStream = new FileInputStream(xmlFile);
+        // Timed: read xml into a byte array
+        startTime = System.currentTimeMillis();
+        int xmlRetVal = xmlStream.read(xmlBytes);
+        xmlRead = System.currentTimeMillis() - startTime;
+
+        // Untimed: create StreamSource and setSystemId
+        StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes));
+        xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName));
+
+        // Untimed: create StreamResult
+        ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+        StreamResult byteResult = new StreamResult(outBytes);
+        
+        // Untimed: Apply any parameters needed
+        applyParameters(transformer);
+
+        // Timed: build xml (so to speak) and transform
+        startTime = System.currentTimeMillis();
+        transformer.transform(xmlSource, byteResult);
+        transform = System.currentTimeMillis() - startTime;
+
+        // Timed: writeResults from the byte array
+        startTime = System.currentTimeMillis();
+        byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
+        FileOutputStream writeStream = new FileOutputStream(resultName);
+        writeStream.write(writeBytes);
+        writeStream.close();
+        resultWrite = System.currentTimeMillis() - startTime;
+
+        long[] times = getTimeArray();
+        times[IDX_OVERALL] = xmlRead + transform + resultWrite;
+        times[IDX_XMLREAD] = xmlRead;
+        times[IDX_TRANSFORM] = transform;
+        times[IDX_RESULTWRITE] = resultWrite;
+        return times;
+    }
+
+
+    /**
+     * Transform supplied xmlName file with a stylesheet found in an 
+     * xml-stylesheet PI into a resultName file.
+     *
+     * Names are assumed to be local path\filename references, and 
+     * will be converted to URLs as needed.  Implementations will 
+     * use whatever facilities exist in their wrappered processor 
+     * to fetch and build the stylesheet to use for the transform.
+     *
+     * @param xmlName local path\filename of XML file to transform
+     * @param resultName local path\filename to put result in
+     *
+     * @return array of longs denoting timing of only these parts of 
+     * our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL
+     * reference from the xml-stylesheet PI), IDX_XSLBUILD, (time 
+     * to then build the Transformer therefrom), IDX_TRANSFORM
+     *
+     * @throws Exception any underlying exceptions from the 
+     * wrappered processor are simply allowed to propagate; throws 
+     * a RuntimeException if any other problems prevent us from 
+     * actually completing the operation
+     */
+    public long[] transformEmbedded(String xmlName, String resultName)
+        throws Exception
+    {
+        preventFootShooting();
+        long startTime = 0;
+        long xslRead = 0;
+        long xslBuild = 0;
+        long transform = 0;
+
+        throw new RuntimeException("transformEmbedded not implemented yet!");
+    }
+
+
+    /**
+     * Reset our parameters and wrapper state, and optionally 
+     * force creation of a new underlying processor implementation.
+     *
+     * This always clears our built stylesheet and any parameters 
+     * that have been set.  If newProcessor is true, also forces a 
+     * re-creation of our underlying processor as if by calling 
+     * newProcessor().
+     *
+     * @param newProcessor if we should reset our underlying 
+     * processor implementation as well
+     */
+    public void reset(boolean newProcessor)
+    {
+        super.reset(newProcessor); // clears indent and parameters
+        m_stylesheetReady = false;
+        builtTemplates = null;
+        if (newProcessor)
+        {
+            try
+            {
+                newProcessor(newProcessorOpts);
+            }
+            catch (Exception e)
+            {
+                //@todo Hmm: what should we do here?
+            }
+        }
+    }
+
+
+    /**
+     * Apply a single parameter to a Transformer.
+     *
+     * Overridden to take a Transformer and call setParameter().
+     *
+     * @param passThru to be passed to each applyParameter() method 
+     * call - for TrAX, you might pass a Transformer object.
+     * @param namespace for the parameter, may be null
+     * @param name for the parameter, should not be null
+     * @param value for the parameter, may be null
+     */
+    protected void applyParameter(Object passThru, String namespace, 
+                                  String name, Object value)
+    {
+        try
+        {
+            Transformer t = (Transformer)passThru;
+            // Munge the namespace into the name per 
+            //  javax.xml.transform.Transformer.setParameter()
+            if (null != namespace)
+            {
+                name = "{" + namespace + "}" + name;
+            }
+            t.setParameter(name, value);
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("applyParameter threw: " + e.toString());
+        }
+    }
+
+
+    /**
+     * Ensure newProcessor has been called when needed.
+     *
+     * Prevent users from shooting themselves in the foot by 
+     * calling a transform* API before newProcessor().
+     *
+     * (Sorry, I couldn't resist)
+     */
+    public void preventFootShooting() throws Exception
+    {
+        if (null == factory)
+            newProcessor(newProcessorOpts);
+    }
+}