/*
 * Approved for Public Release; Distribution Unlimited. Case Number 14-4275
 * Copyright 2014 The MITRE Corporation.  All rights reserved.
 */
package org.mitre.deluge;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

/**
 * An XML/XSLT transformer that can handle streaming XSLT by sending the results to a SAXResult (via transformStreaming())<p>
 * It can also handle regular transformations (via transform()).<P>
 * Special thanks to Michael Kay for the original example of this code.<p>
 * @author James Garriss
 */
public final class DelugeTransformer
{
    private static String IDENTITY_XSLT = "rsc/xslt/identity.xsl";
    
    /**
     * Transforms the XML with the XSLT and sends the results to the output.<p>
     * Supports XSLTs with xsl:stream element.<P>
     * Convenience method.<p>
     * @param xml    The XML to transform
     * @param xsl    The XSLT that does the transformation
     * @param output The output of the transformation 
     * @throws FileNotFoundException Thrown if one of the files cannot be found.
     * @throws TransformerException  Thrown if the transformation fails.
     * @throws IOException                       Thrown if the identity.xsl cannot be found
     */
    public final void transformStreaming(File xml, File xsl, File output) throws FileNotFoundException, TransformerException, IOException
    {
        PrintStream outputPrintStream = new PrintStream(new BufferedOutputStream(new FileOutputStream(output)), true);
        transformStreaming(xml, xsl, output, outputPrintStream);
    }
    
    /**
     * Transforms the XML with the XSLT and sends the results to the output.<P>
     * Supports XSLTs with xsl:stream element.<P>
     * Convenience method.<P>
     * @param xml    The XML to transform
     * @param xsl    The XSLT that does the transformation
     * @param output The output of the transformation 
     * @throws FileNotFoundException Thrown if one of the files cannot be found.
     * @throws TransformerException  Thrown if the transformation fails.
     * @throws IOException                       Thrown if the identity.xsl cannot be found
     */
    public final void transformStreaming(File xml, File xsl, String output) throws FileNotFoundException, TransformerException, IOException
    {
        PrintStream outputPrintStream = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File(output))), true);
        transformStreaming(xml, xsl, new File(output), outputPrintStream);
    }
    
    /**
     * Transforms the XML with the XSLT and sends the results to the output.<p>
     * Supports XSLTs with xsl:stream element.  Uses a standard identity transformation to clean things up.<P>
     * This is where the real work is done.<p>
     * @param xml    The XML to transform
     * @param xsl    The XSLT that does the transformation
     * @param outputFile The output of the transformation
     * @param outputPrintStream The output of the transformation 
     * @throws TransformerConfigurationException Thrown if the transformer cannot be configured correctly.
     * @throws FileNotFoundException             Thrown if one of the files cannot be found.
     * @throws TransformerException              Thrown if the transformation fails.
     * @throws IOException                       Thrown if the identity.xsl cannot be found
     */
    private void transformStreaming(File xml, File xsl, File outputFile, PrintStream outputPrintStream) throws TransformerConfigurationException, FileNotFoundException, TransformerException, IOException
    {
        // XSL
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer(convertFileToStreamSource(xsl));
        // XML
        StreamSource xmlSource = convertFileToStreamSource(xml);
        // Output
        DelugeContentHandler handler = new DelugeContentHandler(outputPrintStream);  // Custom content handler
        Result result = new SAXResult(handler);
        // Transform
        transformer.transform(xmlSource, result);
        // Unfortunately, the custom content handler used above is less than perfect.  For example, it repeats the namespace at every level.  To clean this up, we're going to run it through an identity transformation.
        transform(outputFile, new File(IDENTITY_XSLT), outputFile);
    }
    
    /**
     * Transforms an XML with an XSL and write the result to the output.<P>
     * This is where the real work is done.<p>
     * @param xml    The XML to transform
     * @param xsl    The XSLT that does the transformation
     * @param output The output of the transformation
     * @throws TransformerConfigurationException Thrown if the transformer cannot be configured correctly.
     * @throws FileNotFoundException             Thrown if one of the files cannot be found.
     * @throws TransformerException              Thrown if the transformation fails.
     * @throws IOException                       Thrown if one of the files cannot be found.
     */
    public final void transform(File xml, File xsl, File output) throws TransformerConfigurationException, FileNotFoundException, TransformerException, IOException
    {
        // XSL
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer(convertFileToStreamSource(xsl));
        // XML
        StreamSource xmlSource = convertFileToStreamSource(xml);
        // Output - Various attempts to get the output to work with a generic handler, none of which were successful.
        //TransformerHandler transformerHandler = ((SAXTransformerFactory) SAXTransformerFactory.newInstance()).newTransformerHandler();
        //transformerHandler.setResult(new StreamResult(output));
        // A set of alternatives, none of which seem to work with streaming...
        //transformerHandler.setResult(new StreamResult(new FileOutputStream(output)));
        //transformerHandler.setResult(new StreamResult(new PrintWriter(new FileOutputStream(output, true), true)));
        //transformerHandler.setResult(new StreamResult(new PrintWriter(new BufferedOutputStream(new FileOutputStream(output, true), 10), true)));  // 10 is a buffer size.  Doesn't help.
        //transformerHandler.setResult(new StreamResult(new FileWriter(output)));  
        //ContentHandler contentHandler = (ContentHandler) transformerHandler;
        //SAXResult result = new SAXResult(transformerHandler);
        // Output
        StreamResult result = new StreamResult(output);
        // Transform
        transformer.transform(xmlSource, result);
    }
    
    /**
     * Transforms an XML with an XSL and write the result to the output.<P>
     * A convenience method.<P>
     * @param xml    The XML to transform
     * @param xsl    The XSLT that does the transformation
     * @param output The output of the transformation
     * @throws TransformerException              Thrown if the transformation fails.
     * @throws TransformerConfigurationException Thrown if the transformer cannot be configured correctly.
     * @throws IOException                       Thrown if one of the files cannot be found.
     */
    public final void transform(File xml, File xsl, String output) throws TransformerException, TransformerConfigurationException, IOException
    {
        File outputFile = new File(output);
        transform(xml, xsl, outputFile);
    }
    
    /**
     * Convenience method that converts a File into a StreamSource<p>
     * @param file The file to convert
     * @return The resulting StreamSource
     * @throws FileNotFoundException Thrown if the File cannot be found
     */
    private StreamSource convertFileToStreamSource(File file) throws FileNotFoundException
    {
        return new StreamSource(new FileInputStream(file));
    }
}
