/*
 * Copyright 2004,2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.bsf.engines.xslt;

import java.io.File;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.util.Vector;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;

import org.apache.bsf.BSFDeclaredBean;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.BSFEngineImpl;
import org.apache.bsf.util.BSFFunctions;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xpath.objects.XObject;
import org.w3c.dom.Node;

/**
 * Xerces XSLT interface to BSF. Requires Xalan and Xerces from Apache.
 * 
 * This integration uses the BSF registry to pass in any src document
 * and stylesheet base URI that the user may wish to set. 
 *
 * @author   Sanjiva Weerawarana
 * @author   Sam Ruby
 *
 * Re-implemented for the Xalan 2 codebase
 * 
 * @author   Victor J. Orlikowski
 */
public class XSLTEngine extends BSFEngineImpl {
    TransformerFactory tFactory;
    Transformer transformer;
    
    Log logger = LogFactory.getLog(this.getClass().getName());

    /**
     * call the named method of the given object.
     */
    public Object call (Object object, String method, Object[] args) 
        throws BSFException {
	throw new BSFException (BSFException.REASON_UNSUPPORTED_FEATURE,
                                "BSF:XSLTEngine can't call methods");
    }

    /**
     * Declare a bean by setting it as a parameter
     */
    public void declareBean (BSFDeclaredBean bean) throws BSFException {
        transformer.setParameter (bean.name, new XObject (bean.bean));
    }

    /**
     * Evaluate an expression. In this case, an expression is assumed
     * to be a stylesheet of the template style (see the XSLT spec).
     */
    public Object eval (String source, int lineNo, int columnNo, 
                        Object oscript) throws BSFException {
	// get the style base URI (the place from where Xerces XSLT will
	// look for imported/included files and referenced docs): if a
	// bean named "xslt:styleBaseURI" is registered, then cvt it
	// to a string and use that. Otherwise use ".", which means the
	// base is the directory where the process is running from
	Object sbObj = mgr.lookupBean ("xslt:styleBaseURI");
	String styleBaseURI = (sbObj == null) ? "." : sbObj.toString ();

	// Locate the stylesheet.
	StreamSource styleSource;

        styleSource = 
            new StreamSource(new StringReader(oscript.toString ()));
        styleSource.setSystemId(styleBaseURI);

        try {
            transformer = tFactory.newTransformer(styleSource);
        } catch (Exception e) {
        	logger.error("Exception from Xerces XSLT:", e);
            throw new BSFException (BSFException.REASON_EXECUTION_ERROR,
                                    "Exception from Xerces XSLT: " + e, e);
        }

	// get the src to work on: if a bean named "xslt:src" is registered
	// and its a Node, then use it as the source. If its not a Node, then
	// if its a URL parse it, if not treat it as a file and make a URL and
	// parse it and go. If no xslt:src is found, use an empty document
	// (stylesheet is treated as a literal result element stylesheet)
	Object srcObj = mgr.lookupBean ("xslt:src");
	Object xis = null;
	if (srcObj != null) {
            if (srcObj instanceof Node) {
		xis = new DOMSource((Node)srcObj);
            } else {
		try {
                    String mesg = "as anything";
                    if (srcObj instanceof Reader) {
			xis = new StreamSource ((Reader) srcObj);
			mesg = "as a Reader";
                    } else if (srcObj instanceof File) {
                        xis = new StreamSource ((File) srcObj);
                        mesg = "as a file";
                    } else {
                        String srcObjstr=srcObj.toString();
                        xis = new StreamSource (new StringReader(srcObjstr));
                        if (srcObj instanceof URL) {
                            mesg = "as a URL";
                        } else {
                            ((StreamSource) xis).setPublicId (srcObjstr);
                            mesg = "as an XML string";
                        }
                    }

                    if (xis == null) {
			throw new Exception ("Unable to get input from '" +
                                             srcObj + "' " + mesg);
                    }
		} catch (Exception e) {
                    throw new BSFException (BSFException.REASON_EXECUTION_ERROR,
                                            "BSF:XSLTEngine: unable to get " +
                                            "input from '" + srcObj + "' as XML", e);
		}
            }
	} else {
            // create an empty document - real src must come into the 
            // stylesheet using "doc(...)" [see XSLT spec] or the stylesheet
            // must be of literal result element type
            xis = new StreamSource();
	}
	
	// set all declared beans as parameters.
	for (int i = 0; i < declaredBeans.size (); i++) {
            BSFDeclaredBean b = (BSFDeclaredBean) declaredBeans.elementAt (i);
            transformer.setParameter (b.name, new XObject (b.bean));
	}

	// declare a "bsf" parameter which is the BSF handle so that 
	// the script can do BSF stuff if it wants to
	transformer.setParameter ("bsf", 
                                  new XObject (new BSFFunctions (mgr, this)));

	// do it
	try {
            DOMResult result = new DOMResult();
            transformer.transform ((StreamSource) xis, result);
            return new XSLTResultNode (result.getNode());
	} catch (Exception e) {
            throw new BSFException (BSFException.REASON_EXECUTION_ERROR,
                                    "exception while eval'ing XSLT script" + e, e);
	}
    }

    /**
     * Initialize the engine.
     */
    public void initialize (BSFManager mgr, String lang,
                            Vector declaredBeans) throws BSFException {
	super.initialize (mgr, lang, declaredBeans);

        tFactory = TransformerFactory.newInstance();
    }

    /**
     * Undeclare a bean by setting he parameter represeting it to null
     */
    public void undeclareBean (BSFDeclaredBean bean) throws BSFException {
        // Cannot clear only one parameter in Xalan 2, so we set it to null
        if ((transformer.getParameter (bean.name)) != null) {
            transformer.setParameter (bean.name, null);
        }
    }
}
