/*
 *
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 *
 */

/**
 * Transforms an XML file according to given XSL file.
 */
package com.sun.xml.transform;

import java.io.*;
import java.util.*;

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.*;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;

/**
 * Represents transformation
 */
class Transformation {
    /** XML file name to apply transformation to */
    public String xmlFileName = "";

    /** XSL files to be applied */
    public String xslFileName = null;

    /** Transformation parameters: names */
    public Vector xslParamNames = new Vector();

    /** Transformation parameters: values */
    public Vector xslparamValues = new Vector();

    /** Output file name */
    public String outFileName = "";

    /** Do input file validation */
    public boolean validate = false;
}


/**
 *  Driver class with main(). Parses command line arguments and invokes
 *  CodeTransformerImpl instance that does all the transformation work.
 */
public class CodeTransformer {
    /** Transformer that does actual transformation */
    private static CodeTransformerImpl transformer = null;

    /** Transformations to perform */
    private static Vector transformations = new Vector();

    /** Print debug output while running */
    private static boolean debug = false;

    /** Print usage info and exit */
    private static boolean printHelp = false;


    /**
     * Main method
     *
     * @param args Command line arguments
     */
    public static void main(String[] args) {
        try {
            parseArgs(args);
            if (printHelp) {
                printHelp();
                return;
            }

            transformer = new CodeTransformerImpl(debug);
            for (int i = 0; i < transformations.size(); ++i) {
                Transformation tr =
                    (Transformation)transformations.elementAt(i);
                transformer.transform(tr);
            }
        } catch (SAXException e) {
            // error was already reported
            e.printStackTrace();
            System.exit(1);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Parse command line arguments, adding Transformation objects to
     * <tt>transformations</tt> vector for each transformation specified.
     *
     * @param args command line arguments
     */
    private static void parseArgs(String[] args)
    {
        Transformation tr = new Transformation();
        for (int i = 0; i < args.length; ++i) {
            String arg = args[i];
            if (debug) {
                System.err.println("arg: " + arg);
            }
            if (arg.equals("-xml")) {
                tr.xmlFileName = args[++i];
            } else if (arg.equals("-xsl")) {
                tr.xslFileName = args[++i];
            } else if (arg.equals("-params")) {
                int j = i + 1;
                for (; j < args.length; j += 2)  {
                    if ('-' == args[j].charAt(0)) {
                        break;
                    }
                    if (debug) {
                       System.err.println("pname : " + (String)args[j]);
                       System.err.println("pvalue: " + (String)args[j + 1]);
                    }
                    tr.xslParamNames.add(args[j]);
                    tr.xslparamValues.add(args[j + 1]);
                }
                i = j - 1;
            } else if (arg.equals("-validate")) {
                tr.validate = true;
            } else if (arg.equals("-out")) {
                tr.outFileName = args[++i];
                // -out ends current transformation arguments,
                // i.e all agruments coming after it belong
                // to next transformation(s)
                transformations.add(tr);
                tr = new Transformation();
            } else if (arg.equals("-debug")) {
                debug = true;
            } else {
                printHelp = true;
            }
        }
    }

    /**
     * Print usage information
     */
    private static void printHelp() {
        /**
         * Following options are recognized:
         * -validate:   Do validation of input XML file.
         * -xml:        XML file to transform.
         * -xsl:        XSL file to apply to given XML file.
         * -out:        Output file. If empty, output will be to stdout.
         * -params:     Transformations parameters in form of "Name"
         *              "Value" pairs.
         * -help:       Print usage information
         * -debug:      Be verbose: print some debug info while running.
         *
         * In order to improve performance, CodeTransformer is capable of
         * doing multiple transformations per invocation. -out option marks
         * the end of arguments for single transformation. All arguments
         * after it belong to next transformation(s).
         */
        System.err.println("Usage: java -jar <code_transformer_jar_file>"
            + "[-validate] "
            + "-xml <localXMLFile> "
            + "-xsl <localXSLFile> "
            + "-params <paramName> <paramValue>... "
            + "-out <localOutputFile> "
            + "-xml <localXMLFile> "
            + "... "
            + "[-debug] "
            + "[-help]");
    }
}


/**
 * Perform the transformation
 */
class CodeTransformerImpl {
    /** Factory constructing Transformer objects */
    private TransformerFactory transformerFactory =
        TransformerFactory.newInstance();

    /**
     * Since most of transformations are applied to the same XML file,
     * we don't want to load it on each transformation, so we cache last
     * used XML file as DOMSource
     */
    /** Last source used */
    private DOMSource lastSource = null;
    /** Last document used */
    private Document lastDoc = null;
    /** File name of the last used source */
    private String lastSourceFileName = null;

    /** Be verbose: print some debug info while running */
    private boolean debug = false;


    /**
     * Constructor
     *
     * @param dbg print some debug output while running
     */
    public CodeTransformerImpl(boolean dbg)
    {
        debug = dbg;
    }

    /**
     * Converts errors.
     */
    class TransformerErrorHandler
        implements ErrorHandler {

        /**
         * Handles errors.
         * @param e the parsing exception
         */
        public void error(SAXParseException e)
            throws SAXParseException {
            reportError(e);
            // rethrow exception to stop processing on first error
            throw e;
        }

        /**
         * Handles fatal errors.
         * @param e the parsing exception
         */
        public void fatalError(SAXParseException e) {
            reportError(e);
        }

        /**
         * Handles warnings.
         * @param e the parsing exception
         */
        public void warning(SAXParseException e) {
            reportError(e);
        }

        /**
         * Outputs diagnostic messages.
         * @param e the parsing exception
         */
        private void reportError(SAXParseException e) {
            String msg = e.getMessage();
            String location = e.getSystemId();
            int line = e.getLineNumber();

            System.err.print("Error: URI=" + location);
            System.err.println(" Line=" + line + ": " + msg);
        }
    }

    /**
     * Do the actual transformation
     *
     * @param tr transformation to perform
     */
    public void transform(Transformation tr)
        throws Exception {

        if (debug) {
            System.err.println("xml file: " + tr.xmlFileName);
            System.err.println("out file: " + tr.outFileName);
            System.err.println("prev xml: " + lastSourceFileName);
        }

        // source XML file
        DOMSource source = null;
        Document doc;
        if (lastSource != null &&
                lastSourceFileName.equals(tr.xmlFileName)) {
            source = lastSource;
            doc = lastDoc;
        } else {
            // load XML file as DOM tree
            DocumentBuilderFactory domFactory =
                DocumentBuilderFactory.newInstance();

            if (tr.validate) {
                domFactory.setValidating(true);
            }
            DocumentBuilder domBuilder = domFactory.newDocumentBuilder();

            domBuilder.setErrorHandler(new TransformerErrorHandler());
            doc = domBuilder.parse(new File(tr.xmlFileName));

            // make source from it
            source = new DOMSource(doc);
        }

        // if output and input files are the same,
        // we can't reuse cached input file since
        // it's going to change
        if ((tr.xmlFileName).equals(tr.outFileName)) {
            lastSource = null;
            lastDoc = null;
            lastSourceFileName = null;
        } else {
            lastSource = source;
            lastDoc = doc;
            lastSourceFileName = tr.xmlFileName;
        }

        // apply XSL stylesheet
        if (debug) {
            System.err.println("xsl file: " + tr.xslFileName);
        }

        // output file
        StreamResult outStream = null;
        if (tr.outFileName.length() == 0) {
            // send transformed output to the stdout
            outStream = new StreamResult(System.out);
        } else  {
            // send transformed output to the file
            makeDirectoryTree(tr.outFileName);
            outStream = new StreamResult(new File(tr.outFileName));
        }

        // create Transformer that will apply stylesheet
        StreamSource xslFile = new
            StreamSource(new File(tr.xslFileName));
        Transformer transformer =
            transformerFactory.newTransformer(xslFile);

        // pass parameters to Transformer
        for (int j = 0; j < tr.xslParamNames.size(); ++j) {
            transformer.setParameter(
                (String)tr.xslParamNames.elementAt(j),
                tr.xslparamValues.elementAt(j));
        }

        // finally, apply the stylesheet
        transformer.transform(source, outStream);
    }

    /**
     * Creates a directory structure.
     *
     * @param fullFileName Full path to the file to be created. If directory
     * in which file is to be created doesn't exists, it will be created
     * @exception IOException is thrown if directory couldn't be created
     */
    private void makeDirectoryTree(String fullFileName) throws IOException {
        if (debug == true) {
            System.out.println("mkdir: " + fullFileName);
        }
        int index = fullFileName.lastIndexOf(File.separatorChar);
        if (index == -1) {
            // To be compatible with MKS-hosted build on win32, which
            // does not translate / to \.
            index = fullFileName.lastIndexOf('/');
        }
        File outputDirectory = new File(fullFileName.substring(0, index));

        if (!(outputDirectory).exists()) {
            if (!(outputDirectory).mkdirs()) {
                throw new IOException("failed to create output directory");
            }
        }
    }
}
