/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
/*
 * $Id$
 */

package org.apache.xalan.xsltc.trax;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.xalan.xsltc.compiler.SourceLoader;
import org.apache.xalan.xsltc.compiler.XSLTC;
import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
import org.apache.xalan.xsltc.dom.XSLTCDTMManager;
import org.apache.xml.utils.StopParseException;
import org.apache.xml.utils.StylesheetPIHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * Implementation of a JAXP1.1 TransformerFactory for Translets.
 * @author G. Todd Miller 
 * @author Morten Jorgensen
 * @author Santiago Pericas-Geertsen
 */
public class TransformerFactoryImpl
    extends SAXTransformerFactory implements SourceLoader, ErrorListener 
{
    // Public constants for attributes supported by the XSLTC TransformerFactory.
    public final static String TRANSLET_NAME = "translet-name";
    public final static String DESTINATION_DIRECTORY = "destination-directory";
    public final static String PACKAGE_NAME = "package-name";
    public final static String JAR_NAME = "jar-name";
    public final static String GENERATE_TRANSLET = "generate-translet";
    public final static String AUTO_TRANSLET = "auto-translet";
    public final static String USE_CLASSPATH = "use-classpath";
    public final static String DEBUG = "debug";
    public final static String ENABLE_INLINING = "enable-inlining";
    public final static String INDENT_NUMBER = "indent-number";
        
    /**
     * This error listener is used only for this factory and is not passed to
     * the Templates or Transformer objects that we create.
     */
    private ErrorListener _errorListener = this; 

    /**
     * This URIResolver is passed to all created Templates and Transformers
     */
    private URIResolver _uriResolver = null;

    /** 
     * As Gregor Samsa awoke one morning from uneasy dreams he found himself
     * transformed in his bed into a gigantic insect. He was lying on his hard,
     * as it were armour plated, back, and if he lifted his head a little he
     * could see his big, brown belly divided into stiff, arched segments, on
     * top of which the bed quilt could hardly keep in position and was about
     * to slide off completely. His numerous legs, which were pitifully thin
     * compared to the rest of his bulk, waved helplessly before his eyes.
     * "What has happened to me?", he thought. It was no dream....
     */
    protected final static String DEFAULT_TRANSLET_NAME = "GregorSamsa";
    
    /**
     * The class name of the translet
     */
    private String _transletName = DEFAULT_TRANSLET_NAME;
    
    /**
     * The destination directory for the translet
     */
    private String _destinationDirectory = null;
    
    /**
     * The package name prefix for all generated translet classes
     */
    private String _packageName = null;
    
    /**
     * The jar file name which the translet classes are packaged into
     */
    private String _jarFileName = null;

    /**
     * This Hashtable is used to store parameters for locating
     * <?xml-stylesheet ...?> processing instructions in XML docs.
     */
    private Hashtable _piParams = null;

    /**
     * The above hashtable stores objects of this class.
     */
    private static class PIParamWrapper {
	public String _media = null;
	public String _title = null;
	public String _charset = null;
	
	public PIParamWrapper(String media, String title, String charset) {
	    _media = media;
	    _title = title;
	    _charset = charset;
	}
    }

    /**
     * Set to <code>true</code> when debugging is enabled.
     */
    private boolean _debug = false;

    /**
     * Set to <code>true</code> when templates are inlined.
     */
    private boolean _enableInlining = false;
    
    /**
     * Set to <code>true</code> when we want to generate 
     * translet classes from the stylesheet.
     */
    private boolean _generateTranslet = false;
    
    /**
     * If this is set to <code>true</code>, we attempt to use translet classes
     * for transformation if possible without compiling the stylesheet. The
     * translet class is only used if its timestamp is newer than the timestamp
     * of the stylesheet.
     */
    private boolean _autoTranslet = false;
    
    /**
     * If this is set to <code>true</code>, we attempt to load the translet
     * from the CLASSPATH.
     */
    private boolean _useClasspath = false;

    /**
     * Number of indent spaces when indentation is turned on.
     */
    private int _indentNumber = -1;

    /**
     * The provider of the XSLTC DTM Manager service.  This is fixed for any
     * instance of this class.  In order to change service providers, a new
     * XSLTC <code>TransformerFactory</code> must be instantiated.
     * @see XSLTCDTMManager#getDTMManagerClass()
     */
    private Class m_DTMManagerClass;

    /**
     * <p>State of secure processing feature.</p>
     */
    private boolean _isSecureProcessing = false;

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     */
    public TransformerFactoryImpl() {
        m_DTMManagerClass = XSLTCDTMManager.getDTMManagerClass();
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Set the error event listener for the TransformerFactory, which is used
     * for the processing of transformation instructions, and not for the
     * transformation itself.
     *
     * @param listener The error listener to use with the TransformerFactory
     * @throws IllegalArgumentException
     */
    public void setErrorListener(ErrorListener listener) 
	throws IllegalArgumentException 
    {
	if (listener == null) {
	    ErrorMsg err = new ErrorMsg(ErrorMsg.ERROR_LISTENER_NULL_ERR,
					"TransformerFactory");
            throw new IllegalArgumentException(err.toString());
	}
	_errorListener = listener;
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Get the error event handler for the TransformerFactory.
     *
     * @return The error listener used with the TransformerFactory
     */
    public ErrorListener getErrorListener() { 
	return _errorListener;
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Returns the value set for a TransformerFactory attribute
     *
     * @param name The attribute name
     * @return An object representing the attribute value
     * @throws IllegalArgumentException
     */
    public Object getAttribute(String name) 
	throws IllegalArgumentException 
    { 
	// Return value for attribute 'translet-name'
	if (name.equals(TRANSLET_NAME)) {
	    return _transletName;
	}
	else if (name.equals(GENERATE_TRANSLET)) {
	    return _generateTranslet ? Boolean.TRUE : Boolean.FALSE;
	}
	else if (name.equals(AUTO_TRANSLET)) {
	    return _autoTranslet ? Boolean.TRUE : Boolean.FALSE;
	}
	else if (name.equals(ENABLE_INLINING)) {
	    if (_enableInlining)
	      return Boolean.TRUE;
	    else
	      return Boolean.FALSE;
	}

	// Throw an exception for all other attributes
	ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_INVALID_ATTR_ERR, name);
	throw new IllegalArgumentException(err.toString());
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Sets the value for a TransformerFactory attribute.
     *
     * @param name The attribute name
     * @param value An object representing the attribute value
     * @throws IllegalArgumentException
     */
    public void setAttribute(String name, Object value) 
	throws IllegalArgumentException 
    { 
	// Set the default translet name (ie. class name), which will be used
	// for translets that cannot be given a name from their system-id.
	if (name.equals(TRANSLET_NAME) && value instanceof String) {
	    _transletName = (String) value;	      
	    return;
	}
	else if (name.equals(DESTINATION_DIRECTORY) && value instanceof String) {
	    _destinationDirectory = (String) value;
	    return;
	}
	else if (name.equals(PACKAGE_NAME) && value instanceof String) {
	    _packageName = (String) value;
	    return;
	}
	else if (name.equals(JAR_NAME) && value instanceof String) {
	    _jarFileName = (String) value;
	    return;
	}
	else if (name.equals(GENERATE_TRANSLET)) {
	    if (value instanceof Boolean) {
		_generateTranslet = ((Boolean) value).booleanValue();
		return;
	    }
	    else if (value instanceof String) {
		_generateTranslet = ((String) value).equalsIgnoreCase("true");
		return;
	    }
	}
	else if (name.equals(AUTO_TRANSLET)) {
	    if (value instanceof Boolean) {
		_autoTranslet = ((Boolean) value).booleanValue();
		return;
	    }
	    else if (value instanceof String) {
		_autoTranslet = ((String) value).equalsIgnoreCase("true");
		return;
	    }
	}
	else if (name.equals(USE_CLASSPATH)) {
	    if (value instanceof Boolean) {
		_useClasspath = ((Boolean) value).booleanValue();
		return;
	    }
	    else if (value instanceof String) {
		_useClasspath = ((String) value).equalsIgnoreCase("true");
		return;
	    }	    
	}
	else if (name.equals(DEBUG)) {
	    if (value instanceof Boolean) {
		_debug = ((Boolean) value).booleanValue();
		return;
	    }
	    else if (value instanceof String) {
		_debug = ((String) value).equalsIgnoreCase("true");
		return;
	    }
	}
	else if (name.equals(ENABLE_INLINING)) {
	    if (value instanceof Boolean) {
		_enableInlining = ((Boolean) value).booleanValue();
		return;
	    }
	    else if (value instanceof String) {
		_enableInlining = ((String) value).equalsIgnoreCase("true");
		return;
	    }
	}
	else if (name.equals(INDENT_NUMBER)) {
	    if (value instanceof String) {
		try {
		    _indentNumber = Integer.parseInt((String) value);
		    return;
		}
		catch (NumberFormatException e) {
		    // Falls through
		}
	    }
	    else if (value instanceof Integer) {
		_indentNumber = ((Integer) value).intValue();
		return;
	    }
	}

	// Throw an exception for all other attributes
	final ErrorMsg err 
	    = new ErrorMsg(ErrorMsg.JAXP_INVALID_ATTR_ERR, name);
	throw new IllegalArgumentException(err.toString());
    }

    /**
     * <p>Set a feature for this <code>TransformerFactory</code> and <code>Transformer</code>s
     * or <code>Template</code>s created by this factory.</p>
     * 
     * <p>
     * Feature names are fully qualified {@link java.net.URI}s.
     * Implementations may define their own features.
     * An {@link TransformerConfigurationException} is thrown if this <code>TransformerFactory</code> or the
     * <code>Transformer</code>s or <code>Template</code>s it creates cannot support the feature.
     * It is possible for an <code>TransformerFactory</code> to expose a feature value but be unable to change its state.
     * </p>
     * 
     * <p>See {@link javax.xml.transform.TransformerFactory} for full documentation of specific features.</p>
     * 
     * @param name Feature name.
     * @param value Is feature state <code>true</code> or <code>false</code>.
     *  
     * @throws TransformerConfigurationException if this <code>TransformerFactory</code>
     *   or the <code>Transformer</code>s or <code>Template</code>s it creates cannot support this feature.
     * @throws NullPointerException If the <code>name</code> parameter is null.
     */
    public void setFeature(String name, boolean value)
        throws TransformerConfigurationException {

	// feature name cannot be null
	if (name == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_SET_FEATURE_NULL_NAME);
    	    throw new NullPointerException(err.toString());
	}		
	// secure processing?
	else if (name.equals(XMLConstants.FEATURE_SECURE_PROCESSING)) {
	    _isSecureProcessing = value;		
	    // all done processing feature
	    return;
	}
	else {	
	    // unknown feature
            ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNSUPPORTED_FEATURE, name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Look up the value of a feature (to see if it is supported).
     * This method must be updated as the various methods and features of this
     * class are implemented.
     *
     * @param name The feature name
     * @return 'true' if feature is supported, 'false' if not
     */
    public boolean getFeature(String name) { 
	// All supported features should be listed here
	String[] features = {
	    DOMSource.FEATURE,
	    DOMResult.FEATURE,
	    SAXSource.FEATURE,
	    SAXResult.FEATURE,
	    StreamSource.FEATURE,
	    StreamResult.FEATURE,
	    SAXTransformerFactory.FEATURE,
	    SAXTransformerFactory.FEATURE_XMLFILTER
	};

	// feature name cannot be null
	if (name == null) {
    	    ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_GET_FEATURE_NULL_NAME);
    	    throw new NullPointerException(err.toString());
	}

	// Inefficient, but array is small
	for (int i =0; i < features.length; i++) {
	    if (name.equals(features[i])) {
		return true;
	    }
	}
	// secure processing?
	if (name.equals(XMLConstants.FEATURE_SECURE_PROCESSING)) {
		return _isSecureProcessing;
	}

	// Feature not supported
	return false;
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Get the object that is used by default during the transformation to
     * resolve URIs used in document(), xsl:import, or xsl:include.
     *
     * @return The URLResolver used for this TransformerFactory and all
     * Templates and Transformer objects created using this factory
     */    
    public URIResolver getURIResolver() {
	return _uriResolver;
    } 

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Set the object that is used by default during the transformation to
     * resolve URIs used in document(), xsl:import, or xsl:include. Note that
     * this does not affect Templates and Transformers that are already
     * created with this factory.
     *
     * @param resolver The URLResolver used for this TransformerFactory and all
     * Templates and Transformer objects created using this factory
     */    
    public void setURIResolver(URIResolver resolver) {
	_uriResolver = resolver;
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Get the stylesheet specification(s) associated via the xml-stylesheet
     * processing instruction (see http://www.w3.org/TR/xml-stylesheet/) with
     * the document document specified in the source parameter, and that match
     * the given criteria.
     *
     * @param source The XML source document.
     * @param media The media attribute to be matched. May be null, in which
     * case the prefered templates will be used (i.e. alternate = no).
     * @param title The value of the title attribute to match. May be null.
     * @param charset The value of the charset attribute to match. May be null.
     * @return A Source object suitable for passing to the TransformerFactory.
     * @throws TransformerConfigurationException
     */
    public Source  getAssociatedStylesheet(Source source, String media,
					  String title, String charset)
	throws TransformerConfigurationException {

        String baseId;
        XMLReader reader = null;
        InputSource isource = null;


        /**
         * Fix for bugzilla bug 24187
         */
        StylesheetPIHandler _stylesheetPIHandler = new StylesheetPIHandler(null,media,title,charset);

        try {
  
            if (source instanceof DOMSource ) {
                final DOMSource domsrc = (DOMSource) source;
                baseId = domsrc.getSystemId();
                final org.w3c.dom.Node node = domsrc.getNode();
                final DOM2SAX dom2sax = new DOM2SAX(node);

                _stylesheetPIHandler.setBaseId(baseId);

                dom2sax.setContentHandler( _stylesheetPIHandler);
                dom2sax.parse();
            } else {
                isource = SAXSource.sourceToInputSource(source);
                baseId = isource.getSystemId();

                SAXParserFactory factory = SAXParserFactory.newInstance();
                factory.setNamespaceAware(true);
                
                if (_isSecureProcessing) {
                    try {
                        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
                    }
                    catch (org.xml.sax.SAXException e) {}
                }
                
                SAXParser jaxpParser = factory.newSAXParser();

                reader = jaxpParser.getXMLReader();
                if (reader == null) {
                    reader = XMLReaderFactory.createXMLReader();
                }

                _stylesheetPIHandler.setBaseId(baseId);
                reader.setContentHandler(_stylesheetPIHandler);
                reader.parse(isource);

            }

            if (_uriResolver != null ) {
                _stylesheetPIHandler.setURIResolver(_uriResolver);
            }

        } catch (StopParseException e ) {
          // startElement encountered so do not parse further

        } catch (javax.xml.parsers.ParserConfigurationException e) {

             throw new TransformerConfigurationException(
             "getAssociatedStylesheets failed", e);

        } catch (org.xml.sax.SAXException se) {

             throw new TransformerConfigurationException(
             "getAssociatedStylesheets failed", se);


        } catch (IOException ioe ) {
           throw new TransformerConfigurationException(
           "getAssociatedStylesheets failed", ioe);

        }

         return _stylesheetPIHandler.getAssociatedStylesheet();

    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Create a Transformer object that copies the input document to the result.
     *
     * @return A Transformer object that simply copies the source to the result.
     * @throws TransformerConfigurationException
     */    
    public Transformer newTransformer()
	throws TransformerConfigurationException 
    { 
	TransformerImpl result = new TransformerImpl(new Properties(), 
	    _indentNumber, this);
	if (_uriResolver != null) {
	    result.setURIResolver(_uriResolver);
	}
	
	if (_isSecureProcessing) {
	    result.setSecureProcessing(true);
	}
	return result;
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Process the Source into a Templates object, which is a a compiled
     * representation of the source. Note that this method should not be
     * used with XSLTC, as the time-consuming compilation is done for each
     * and every transformation.
     *
     * @return A Templates object that can be used to create Transformers.
     * @throws TransformerConfigurationException
     */
    public Transformer newTransformer(Source source) throws
	TransformerConfigurationException 
    {
	final Templates templates = newTemplates(source);
	final Transformer transformer = templates.newTransformer();
	if (_uriResolver != null) {
	    transformer.setURIResolver(_uriResolver);
	}
	return(transformer);
    }

    /**
     * Pass warning messages from the compiler to the error listener
     */
    private void passWarningsToListener(Vector messages) 
	throws TransformerException 
    {
	if (_errorListener == null || messages == null) {
	    return;
	}
	// Pass messages to listener, one by one
	final int count = messages.size();
	for (int pos = 0; pos < count; pos++) {
	    ErrorMsg msg = (ErrorMsg)messages.elementAt(pos);
	    // Workaround for the TCK failure ErrorListener.errorTests.error001.
	    if (msg.isWarningError())
	        _errorListener.error(
		    new TransformerConfigurationException(msg.toString()));
	    else
	    	_errorListener.warning(
		    new TransformerConfigurationException(msg.toString()));
	}
    }

    /**
     * Pass error messages from the compiler to the error listener
     */
    private void passErrorsToListener(Vector messages) {
	try {
	    if (_errorListener == null || messages == null) {
		return;
	    }
	    // Pass messages to listener, one by one
	    final int count = messages.size();
	    for (int pos = 0; pos < count; pos++) {
		String message = messages.elementAt(pos).toString();
		_errorListener.error(new TransformerException(message));
	    }
	}
	catch (TransformerException e) {
	    // nada
	}
    }

    /**
     * javax.xml.transform.sax.TransformerFactory implementation.
     * Process the Source into a Templates object, which is a a compiled
     * representation of the source.
     *
     * @param source The input stylesheet - DOMSource not supported!!!
     * @return A Templates object that can be used to create Transformers.
     * @throws TransformerConfigurationException
     */
    public Templates newTemplates(Source source)
	throws TransformerConfigurationException 
    {
	// If the _useClasspath attribute is true, try to load the translet from
	// the CLASSPATH and create a template object using the loaded
	// translet.
	if (_useClasspath) {
	    String transletName = getTransletBaseName(source);
	            
	    if (_packageName != null)
	        transletName = _packageName + "." + transletName;
	        
	    try {
                final Class clazz = ObjectFactory.findProviderClass(
                    transletName, ObjectFactory.findClassLoader(), true);
	        resetTransientAttributes();
	            
	        return new TemplatesImpl(new Class[]{clazz}, transletName, null, _indentNumber, this);
	    }
	    catch (ClassNotFoundException cnfe) {
	        ErrorMsg err = new ErrorMsg(ErrorMsg.CLASS_NOT_FOUND_ERR, transletName);
	        throw new TransformerConfigurationException(err.toString());
	    }
	    catch (Exception e) {
	        ErrorMsg err = new ErrorMsg(
                                     new ErrorMsg(ErrorMsg.RUNTIME_ERROR_KEY)
                                     + e.getMessage());
	        throw new TransformerConfigurationException(err.toString());
	    }
	}
	
	// If _autoTranslet is true, we will try to load the bytecodes
	// from the translet classes without compiling the stylesheet.
	if (_autoTranslet)  {
	    byte[][] bytecodes = null;
	    String transletClassName = getTransletBaseName(source);
	    
	    if (_packageName != null)
	        transletClassName = _packageName + "." + transletClassName;
	    
	    if (_jarFileName != null)
	    	bytecodes = getBytecodesFromJar(source, transletClassName);
	    else
	    	bytecodes = getBytecodesFromClasses(source, transletClassName);	    
	  
	    if (bytecodes != null) {
	    	if (_debug) {
	      	    if (_jarFileName != null)
	        	System.err.println(new ErrorMsg(
	            	    ErrorMsg.TRANSFORM_WITH_JAR_STR, transletClassName, _jarFileName));
	            else
	            	System.err.println(new ErrorMsg(
	            	    ErrorMsg.TRANSFORM_WITH_TRANSLET_STR, transletClassName));
	    	}

	    	// Reset the per-session attributes to their default values
	    	// after each newTemplates() call.
	    	resetTransientAttributes();
	    
	    	return new TemplatesImpl(bytecodes, transletClassName, null, _indentNumber, this);	    
	    }
	}
	
	// Create and initialize a stylesheet compiler
	final XSLTC xsltc = new XSLTC();
	if (_debug) xsltc.setDebug(true);
	if (_enableInlining) 
		xsltc.setTemplateInlining(true);
	else
		xsltc.setTemplateInlining(false);
	if (_isSecureProcessing) xsltc.setSecureProcessing(true);
	xsltc.init();

	// Set a document loader (for xsl:include/import) if defined
	if (_uriResolver != null) {
	    xsltc.setSourceLoader(this);
	}

	// Pass parameters to the Parser to make sure it locates the correct
	// <?xml-stylesheet ...?> PI in an XML input document
	if ((_piParams != null) && (_piParams.get(source) != null)) {
	    // Get the parameters for this Source object
	    PIParamWrapper p = (PIParamWrapper)_piParams.get(source);
	    // Pass them on to the compiler (which will pass then to the parser)
	    if (p != null) {
		xsltc.setPIParameters(p._media, p._title, p._charset);
	    }
	}

	// Set the attributes for translet generation
	int outputType = XSLTC.BYTEARRAY_OUTPUT;
	if (_generateTranslet || _autoTranslet) {
	    // Set the translet name
	    xsltc.setClassName(getTransletBaseName(source));
	  
	    if (_destinationDirectory != null)
	    	xsltc.setDestDirectory(_destinationDirectory);
	    else {
	    	String xslName = getStylesheetFileName(source);
	    	if (xslName != null) {
	      	    File xslFile = new File(xslName);
	            String xslDir = xslFile.getParent();
	    
	      	    if (xslDir != null)
	                xsltc.setDestDirectory(xslDir);
	    	}
	    }
	  
	    if (_packageName != null)
	        xsltc.setPackageName(_packageName);
	
	    if (_jarFileName != null) {
	    	xsltc.setJarFileName(_jarFileName);
	    	outputType = XSLTC.BYTEARRAY_AND_JAR_OUTPUT;
	    }
	    else
	    	outputType = XSLTC.BYTEARRAY_AND_FILE_OUTPUT;
	}

	// Compile the stylesheet
	final InputSource input = Util.getInputSource(xsltc, source);
	byte[][] bytecodes = xsltc.compile(null, input, outputType);
	final String transletName = xsltc.getClassName();

	// Output to the jar file if the jar file name is set.
	if ((_generateTranslet || _autoTranslet)
	   	&& bytecodes != null && _jarFileName != null) {
	    try {
	    	xsltc.outputToJar();
	    }
	    catch (java.io.IOException e) { }
	}

	// Reset the per-session attributes to their default values
	// after each newTemplates() call.
	resetTransientAttributes();

	// Pass compiler warnings to the error listener
	if (_errorListener != this) {
	    try {
		passWarningsToListener(xsltc.getWarnings());
	    }
	    catch (TransformerException e) {
		throw new TransformerConfigurationException(e);
	    }
	}
	else {
	    xsltc.printWarnings();
	}

	// Check that the transformation went well before returning
    if (bytecodes == null) {
        
        ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR);
        TransformerConfigurationException exc =  new TransformerConfigurationException(err.toString());
        
        // Pass compiler errors to the error listener
        if (_errorListener != null) {
            passErrorsToListener(xsltc.getErrors());
            
            // As required by TCK 1.2, send a fatalError to the
            // error listener because compilation of the stylesheet
            // failed and no further processing will be possible.
            try {
                _errorListener.fatalError(exc);
            } catch (TransformerException te) {
                // well, we tried.
            }    
        }
        else {
            xsltc.printErrors();
        }
        throw exc;
    }

	return new TemplatesImpl(bytecodes, transletName, 
	    xsltc.getOutputProperties(), _indentNumber, this);
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Get a TemplatesHandler object that can process SAX ContentHandler
     * events into a Templates object.
     *
     * @return A TemplatesHandler object that can handle SAX events
     * @throws TransformerConfigurationException
     */
    public TemplatesHandler newTemplatesHandler() 
	throws TransformerConfigurationException 
    { 
	final TemplatesHandlerImpl handler = 
	    new TemplatesHandlerImpl(_indentNumber, this);
	if (_uriResolver != null) {
	    handler.setURIResolver(_uriResolver);
	}
	return handler;
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Get a TransformerHandler object that can process SAX ContentHandler
     * events into a Result. This method will return a pure copy transformer.
     *
     * @return A TransformerHandler object that can handle SAX events
     * @throws TransformerConfigurationException
     */
    public TransformerHandler newTransformerHandler() 
	throws TransformerConfigurationException 
    {
	final Transformer transformer = newTransformer();
	if (_uriResolver != null) {
	    transformer.setURIResolver(_uriResolver);
	}
	return new TransformerHandlerImpl((TransformerImpl) transformer);
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Get a TransformerHandler object that can process SAX ContentHandler
     * events into a Result, based on the transformation instructions
     * specified by the argument.
     *
     * @param src The source of the transformation instructions.
     * @return A TransformerHandler object that can handle SAX events
     * @throws TransformerConfigurationException
     */
    public TransformerHandler newTransformerHandler(Source src) 
	throws TransformerConfigurationException 
    { 
	final Transformer transformer = newTransformer(src);
	if (_uriResolver != null) {
	    transformer.setURIResolver(_uriResolver);
	}
	return new TransformerHandlerImpl((TransformerImpl) transformer);
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Get a TransformerHandler object that can process SAX ContentHandler
     * events into a Result, based on the transformation instructions
     * specified by the argument.
     *
     * @param templates Represents a pre-processed stylesheet
     * @return A TransformerHandler object that can handle SAX events
     * @throws TransformerConfigurationException
     */    
    public TransformerHandler newTransformerHandler(Templates templates) 
	throws TransformerConfigurationException  
    {
	final Transformer transformer = templates.newTransformer();
	final TransformerImpl internal = (TransformerImpl)transformer;
	return new TransformerHandlerImpl(internal);
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Create an XMLFilter that uses the given source as the
     * transformation instructions.
     *
     * @param src The source of the transformation instructions.
     * @return An XMLFilter object, or null if this feature is not supported.
     * @throws TransformerConfigurationException
     */
    public XMLFilter newXMLFilter(Source src) 
	throws TransformerConfigurationException 
    {
	Templates templates = newTemplates(src);
	if (templates == null) return null; 
	return newXMLFilter(templates);
    }

    /**
     * javax.xml.transform.sax.SAXTransformerFactory implementation.
     * Create an XMLFilter that uses the given source as the
     * transformation instructions.
     *
     * @param templates The source of the transformation instructions.
     * @return An XMLFilter object, or null if this feature is not supported.
     * @throws TransformerConfigurationException
     */
    public XMLFilter newXMLFilter(Templates templates) 
	throws TransformerConfigurationException 
    {
	try {
      	    return new org.apache.xalan.xsltc.trax.TrAXFilter(templates);
    	}
	catch (TransformerConfigurationException e1) {
      	    if (_errorListener != null) {
                try {
          	    _errorListener.fatalError(e1);
          	    return null;
        	}
		catch (TransformerException e2) {
          	    new TransformerConfigurationException(e2);
        	}
      	    }
      	    throw e1;
    	}
    }

    /**
     * Receive notification of a recoverable error. 
     * The transformer must continue to provide normal parsing events after
     * invoking this method. It should still be possible for the application
     * to process the document through to the end.
     *
     * @param e The warning information encapsulated in a transformer 
     * exception.
     * @throws TransformerException if the application chooses to discontinue
     * the transformation (always does in our case).
     */
    public void error(TransformerException e)
	throws TransformerException 
    {
	Throwable wrapped = e.getException();
        if (wrapped != null) {
            System.err.println(new ErrorMsg(ErrorMsg.ERROR_PLUS_WRAPPED_MSG,
                                            e.getMessageAndLocation(),
                                            wrapped.getMessage()));
        } else {
            System.err.println(new ErrorMsg(ErrorMsg.ERROR_MSG,
                                            e.getMessageAndLocation()));
	}
	throw e; 	
    }

    /**
     * Receive notification of a non-recoverable error. 
     * The application must assume that the transformation cannot continue
     * after the Transformer has invoked this method, and should continue
     * (if at all) only to collect addition error messages. In fact,
     * Transformers are free to stop reporting events once this method has
     * been invoked.
     *
     * @param e warning information encapsulated in a transformer
     * exception.
     * @throws TransformerException if the application chooses to discontinue
     * the transformation (always does in our case).
     */
    public void fatalError(TransformerException e)
	throws TransformerException 
    {
	Throwable wrapped = e.getException();
        if (wrapped != null) {
            System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_PLUS_WRAPPED_MSG,
                                            e.getMessageAndLocation(),
                                            wrapped.getMessage()));
        } else {
            System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_MSG,
                                            e.getMessageAndLocation()));
        }
	throw e;
    }

    /**
     * Receive notification of a warning.
     * Transformers can use this method to report conditions that are not
     * errors or fatal errors. The default behaviour is to take no action.
     * After invoking this method, the Transformer must continue with the
     * transformation. It should still be possible for the application to
     * process the document through to the end.
     *
     * @param e The warning information encapsulated in a transformer
     * exception.
     * @throws TransformerException if the application chooses to discontinue
     * the transformation (never does in our case).
     */
    public void warning(TransformerException e)
	throws TransformerException 
    {
	Throwable wrapped = e.getException();
	if (wrapped != null) {
            System.err.println(new ErrorMsg(ErrorMsg.WARNING_PLUS_WRAPPED_MSG,
                                            e.getMessageAndLocation(),
                                            wrapped.getMessage()));
	} else {
            System.err.println(new ErrorMsg(ErrorMsg.WARNING_MSG,
                                            e.getMessageAndLocation()));
        }
    }

    /**
     * This method implements XSLTC's SourceLoader interface. It is used to
     * glue a TrAX URIResolver to the XSLTC compiler's Input and Import classes.
     *
     * @param href The URI of the document to load
     * @param context The URI of the currently loaded document
     * @param xsltc The compiler that resuests the document
     * @return An InputSource with the loaded document
     */
    public InputSource loadSource(String href, String context, XSLTC xsltc) {
	try {
	    if (_uriResolver != null) {
		final Source source = _uriResolver.resolve(href, context);
		if (source != null) {
		    return Util.getInputSource(xsltc, source);
		}
	    }
	}
	catch (TransformerException e) {
	    // Falls through
	}
	return null;
    }

    /**
     * Reset the per-session attributes to their default values
     */
    private void resetTransientAttributes() {
	_transletName = DEFAULT_TRANSLET_NAME;
	_destinationDirectory = null;
	_packageName = null;
	_jarFileName = null;    
    }
        
    /**
     * Load the translet classes from local .class files and return
     * the bytecode array.
     *
     * @param source The xsl source
     * @param fullClassName The full name of the translet
     * @return The bytecode array
     */
    private byte[][] getBytecodesFromClasses(Source source, String fullClassName)
    {
    	if (fullClassName == null)
    	    return null;
    	  
    	String xslFileName = getStylesheetFileName(source);
    	File xslFile = null;
    	if (xslFileName != null)
    	    xslFile = new File(xslFileName);
    	
    	// Find the base name of the translet
    	final String transletName;
    	int lastDotIndex = fullClassName.lastIndexOf('.');
    	if (lastDotIndex > 0)
    	    transletName = fullClassName.substring(lastDotIndex+1);
    	else
    	    transletName = fullClassName;
    	    	
    	// Construct the path name for the translet class file
    	String transletPath = fullClassName.replace('.', '/');
    	if (_destinationDirectory != null) {
    	    transletPath = _destinationDirectory + "/" + transletPath + ".class";
    	}
    	else {
    	    if (xslFile != null && xslFile.getParent() != null)
    	    	transletPath = xslFile.getParent() + "/" + transletPath + ".class";
    	    else
    	    	transletPath = transletPath + ".class";
    	}
    	    	    	
    	// Return null if the translet class file does not exist.
    	File transletFile = new File(transletPath);
    	if (!transletFile.exists())
    	    return null;
    	    	  
    	// Compare the timestamps of the translet and the xsl file.
    	// If the translet is older than the xsl file, return null 
    	// so that the xsl file is used for the transformation and
    	// the translet is regenerated.
    	if (xslFile != null && xslFile.exists()) {
    	    long xslTimestamp = xslFile.lastModified();
    	    long transletTimestamp = transletFile.lastModified();
    	    if (transletTimestamp < xslTimestamp)
    	    	return null;
    	}
    	
    	// Load the translet into a bytecode array.
    	List bytecodes = new ArrayList();
    	int fileLength = (int)transletFile.length();
    	if (fileLength > 0) {
    	    FileInputStream input = null;
    	    try {
    	    	input = new FileInputStream(transletFile);
    	    }
    	    catch (FileNotFoundException e) {
    	    	return null;
    	    }
    	  
    	    byte[] bytes = new byte[fileLength];
    	    try {
	    	readFromInputStream(bytes, input, fileLength);
	    	input.close();
	    }
	    catch (IOException e) {
    	    	return null;
    	    }
    	  
    	    bytecodes.add(bytes);
    	}
    	else
    	    return null;
    	
    	// Find the parent directory of the translet.
    	String transletParentDir = transletFile.getParent();
    	if (transletParentDir == null)
    	    transletParentDir = System.getProperty("user.dir");
    	  
    	File transletParentFile = new File(transletParentDir);
    	
    	// Find all the auxiliary files which have a name pattern of "transletClass$nnn.class".
    	final String transletAuxPrefix = transletName + "$";
    	File[] auxfiles = transletParentFile.listFiles(new FilenameFilter() {
        	public boolean accept(File dir, String name)
    		{
    		    return (name.endsWith(".class") && name.startsWith(transletAuxPrefix));	
    		}
    	      });
    	
    	// Load the auxiliary class files and add them to the bytecode array.
    	for (int i = 0; i < auxfiles.length; i++)
    	{
    	    File auxfile = auxfiles[i];
    	    int auxlength = (int)auxfile.length();
    	    if (auxlength > 0) {
    	    	FileInputStream auxinput = null;
    	    	try {
    	      	    auxinput = new FileInputStream(auxfile);
    	    	}
    	    	catch (FileNotFoundException e) {
    	      	    continue;
    	    	}
    	  
    	    	byte[] bytes = new byte[auxlength];
    	    
    	    	try {
    	      	    readFromInputStream(bytes, auxinput, auxlength);
    	      	    auxinput.close();
    	    	}
    	    	catch (IOException e) {
    	      	    continue;
    	    	}
    	    
    	    	bytecodes.add(bytes);   	    
    	    }
    	}
    	
    	// Convert the Vector of byte[] to byte[][].
    	final int count = bytecodes.size();
    	if ( count > 0) {
    	    final byte[][] result = new byte[count][1];
    	    for (int i = 0; i < count; i++) {
    	    	result[i] = (byte[])bytecodes.get(i);
    	    }
    	  
    	    return result;
    	}
    	else
    	    return null;
    }
    
    /**
     * Load the translet classes from the jar file and return the bytecode.
     *
     * @param source The xsl source
     * @param fullClassName The full name of the translet
     * @return The bytecode array
     */
    private byte[][] getBytecodesFromJar(Source source, String fullClassName)
    {
    	String xslFileName = getStylesheetFileName(source);
    	File xslFile = null;
    	if (xslFileName != null)
    	    xslFile = new File(xslFileName);
      
      	// Construct the path for the jar file
      	String jarPath = null;
      	if (_destinationDirectory != null)
            jarPath = _destinationDirectory + "/" + _jarFileName;
      	else {
      	    if (xslFile != null && xslFile.getParent() != null)
    	    	jarPath = xslFile.getParent() + "/" + _jarFileName;
    	    else
    	    	jarPath = _jarFileName;
    	}
            
      	// Return null if the jar file does not exist.
      	File file = new File(jarPath);
      	if (!file.exists())
            return null;

     	// Compare the timestamps of the jar file and the xsl file. Return null
     	// if the xsl file is newer than the jar file.
    	if (xslFile != null && xslFile.exists()) {
    	    long xslTimestamp = xslFile.lastModified();
    	    long transletTimestamp = file.lastModified();
    	    if (transletTimestamp < xslTimestamp)
    	        return null;
    	}
      
      	// Create a ZipFile object for the jar file
      	ZipFile jarFile = null;
      	try {
            jarFile = new ZipFile(file);
      	}
      	catch (IOException e) {
            return null;
      	}
      
      	String transletPath = fullClassName.replace('.', '/');
      	String transletAuxPrefix = transletPath + "$";
      	String transletFullName = transletPath + ".class";
      
      	List bytecodes = new ArrayList();      
      
      	// Iterate through all entries in the jar file to find the 
      	// translet and auxiliary classes.
      	Enumeration entries = jarFile.entries();
      	while (entries.hasMoreElements())
      	{
            ZipEntry entry = (ZipEntry)entries.nextElement();
            String entryName = entry.getName();
            if (entry.getSize() > 0 && 
            	  (entryName.equals(transletFullName) ||
              	  (entryName.endsWith(".class") && 
              	      entryName.startsWith(transletAuxPrefix))))
            {
            	try {
              	    InputStream input = jarFile.getInputStream(entry);
              	    int size = (int)entry.getSize();
              	    byte[] bytes = new byte[size];
              	    readFromInputStream(bytes, input, size);
              	    input.close();
              	    bytecodes.add(bytes);
            	}
            	catch (IOException e) {
              	    return null;
            	}          
            }
      	}
      
        // Convert the Vector of byte[] to byte[][].
    	final int count = bytecodes.size();
    	if (count > 0) {
    	    final byte[][] result = new byte[count][1];
    	    for (int i = 0; i < count; i++) {
    	    	result[i] = (byte[])bytecodes.get(i);
    	    }
    	  
    	    return result;
    	}
    	else
    	    return null;
    }
    
    /**
     * Read a given number of bytes from the InputStream into a byte array.
     *
     * @param bytes The byte array to store the input content.
     * @param input The input stream.
     * @param size The number of bytes to read.
     */
    private void readFromInputStream(byte[] bytes, InputStream input, int size)
      	throws IOException
    {
      int n = 0;
      int offset = 0;
      int length = size;
      while (length > 0 && (n = input.read(bytes, offset, length)) > 0) {
          offset = offset + n;
          length = length - n;
      }    
    }

    /**
     * Return the base class name of the translet.
     * The translet name is resolved using the following rules:
     * 1. if the _transletName attribute is set and its value is not "GregorSamsa",
     *    then _transletName is returned.
     * 2. otherwise get the translet name from the base name of the system ID
     * 3. return "GregorSamsa" if the result from step 2 is null.
     *
     * @param source The input Source
     * @return The name of the translet class
     */
    private String getTransletBaseName(Source source)
    {      
        String transletBaseName = null;
        if (!_transletName.equals(DEFAULT_TRANSLET_NAME))
            return _transletName;
      	else {
            String systemId = source.getSystemId();
            if (systemId != null) {
          	String baseName = Util.baseName(systemId);
		if (baseName != null) {
		    baseName = Util.noExtName(baseName);
		    transletBaseName = Util.toJavaName(baseName);
		}
            }
      	}
      
        return (transletBaseName != null) ? transletBaseName : DEFAULT_TRANSLET_NAME;
    }
        
    /**
     *  Return the local file name from the systemId of the Source object
     *
     * @param source The Source
     * @return The file name in the local filesystem, or null if the
     * systemId does not represent a local file.
     */
    private String getStylesheetFileName(Source source)
    {
    	String systemId = source.getSystemId();
      	if (systemId != null) {
            File file = new File(systemId);
            if (file.exists())
                return systemId;
            else {
              	URL url = null;
          	try {
            	    url = new URL(systemId);
          	}
          	catch (MalformedURLException e) {
            	    return null;
          	}
          
          	if ("file".equals(url.getProtocol()))
            	    return url.getFile();
          	else
            	    return null;
            }
      	}
      	else
            return null;
    }

    /**
     * Returns the Class object the provides the XSLTC DTM Manager service.
     */
    protected Class getDTMManagerClass() {
        return m_DTMManagerClass;
    }
}
