/*
 * 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: TransformerImpl.java 475979 2006-11-16 23:32:48Z minchau $
 */
package org.apache.xalan.transformer;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Transformer;
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.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.xalan.extensions.ExtensionsTable;
import org.apache.xalan.res.XSLMessages;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xml.serializer.Method;
import org.apache.xml.serializer.Serializer;
import org.apache.xml.serializer.SerializerFactory;
import org.apache.xalan.templates.AVT;
import org.apache.xalan.templates.Constants;
import org.apache.xalan.templates.ElemAttributeSet;
import org.apache.xalan.templates.ElemForEach;
import org.apache.xalan.templates.ElemSort;
import org.apache.xalan.templates.ElemTemplate;
import org.apache.xalan.templates.ElemTemplateElement;
import org.apache.xalan.templates.ElemTextLiteral;
import org.apache.xalan.templates.ElemVariable;
import org.apache.xalan.templates.OutputProperties;
import org.apache.xalan.templates.Stylesheet;
import org.apache.xalan.templates.StylesheetComposed;
import org.apache.xalan.templates.StylesheetRoot;
import org.apache.xalan.templates.XUnresolvedVariable;
import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.DTMIterator;
import org.apache.xml.dtm.DTMManager;
import org.apache.xml.dtm.DTMWSFilter;
import org.apache.xml.serializer.ToSAXHandler;
import org.apache.xml.serializer.ToTextStream;
import org.apache.xml.serializer.ToXMLSAXHandler;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xml.utils.BoolStack;
import org.apache.xml.utils.DOMBuilder;
import org.apache.xml.utils.NodeVector;
import org.apache.xml.utils.ObjectPool;
import org.apache.xml.utils.ObjectStack;
import org.apache.xml.utils.QName;
import org.apache.xml.utils.SAXSourceLocator;
import org.apache.xml.utils.ThreadControllerWrapper;
import org.apache.xpath.Arg;
import org.apache.xpath.ExtensionsProvider;
import org.apache.xpath.VariableStack;
import org.apache.xpath.XPathContext;
import org.apache.xpath.functions.FuncExtFunction;
import org.apache.xpath.objects.XObject;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.ext.LexicalHandler;

/**
 * This class implements the
 * {@link javax.xml.transform.Transformer} interface, and is the core
 * representation of the transformation execution.</p>
 * @xsl.usage advanced
 */
public class TransformerImpl extends Transformer
        implements Runnable, DTMWSFilter, ExtensionsProvider, org.apache.xml.serializer.SerializerTrace
{

  // Synch object to gaurd against setting values from the TrAX interface 
  // or reentry while the transform is going on.

  /** NEEDSDOC Field m_reentryGuard          */
  private Boolean m_reentryGuard = new Boolean(true);

  /**
   * This is null unless we own the stream.
   */
  private java.io.FileOutputStream m_outputStream = null;

  /** The thread that the transformer is running on. */
  private Thread m_transformThread;

  /** The base URL of the source tree. */
  private String m_urlOfSource = null;

  /** The Result object at the start of the transform, if any. */
  private Result m_outputTarget = null;

  /**
   * The output format object set by the user.  May be null.
   */
  private OutputProperties m_outputFormat;


  /**
   * The content handler for the source input tree.
   */
  ContentHandler m_inputContentHandler;

  /**
   * The content handler for the result tree.
   */
  private ContentHandler m_outputContentHandler = null;

  //  /*
  //   * Use member variable to store param variables as they're
  //   * being created, use member variable so we don't
  //   * have to create a new vector every time.
  //   */
  //  private Vector m_newVars = new Vector();

  /**
   * A pool of ResultTreeHandlers, for serialization of a subtree to text.
   *  Please note that each of these also holds onto a Text Serializer.  
   */
  private ObjectPool m_textResultHandlerObjectPool =
    new ObjectPool(ToTextStream.class);

  /**
   * Related to m_textResultHandlerObjectPool, this is a pool of
   * StringWriters, which are passed to the Text Serializers.
   * (I'm not sure if this is really needed any more.  -sb)      
   */
  private ObjectPool m_stringWriterObjectPool =
    new ObjectPool(StringWriter.class);

  /**
   * A static text format object, which can be used over and
   * over to create the text serializers.    
   */
  private OutputProperties m_textformat = new OutputProperties(Method.TEXT);

  // Commenteded out in response to problem reported by 
  // Nicola Brown <Nicola.Brown@jacobsrimell.com>
  //  /**
  //   * Flag to let us know if an exception should be reported inside the 
  //   * postExceptionFromThread method.  This is needed if the transform is 
  //   * being generated from SAX events, and thus there is no central place 
  //   * to report the exception from.  (An exception is usually picked up in 
  //   * the main thread from the transform thread in {@link #transform(Source source)} 
  //   * from {@link #getExceptionThrown()}. )
  //   */
  //  private boolean m_reportInPostExceptionFromThread = false;

  /**
   * A node vector used as a stack to track the current
   * ElemTemplateElement.  Needed for the
   * org.apache.xalan.transformer.TransformState interface,
   * so a tool can discover the calling template. Note the use of an array 
   * for this limits the recursion depth to 4K.
   */
  ObjectStack m_currentTemplateElements 
      = new ObjectStack(XPathContext.RECURSIONLIMIT);
  
  /** The top of the currentTemplateElements stack. */
  //int m_currentTemplateElementsTop = 0;

  /**
   * A node vector used as a stack to track the current
   * ElemTemplate that was matched.
   * Needed for the
   * org.apache.xalan.transformer.TransformState interface,
   * so a tool can discover the matched template
   */
  Stack m_currentMatchTemplates = new Stack();

  /**
   * A node vector used as a stack to track the current
   * node that was matched.
   * Needed for the
   * org.apache.xalan.transformer.TransformState interface,
   * so a tool can discover the matched
   * node. 
   */
  NodeVector m_currentMatchedNodes = new NodeVector();

  /**
   * The root of a linked set of stylesheets.
   */
  private StylesheetRoot m_stylesheetRoot = null;

  /**
   * If this is set to true, do not warn about pattern
   * match conflicts.
   */
  private boolean m_quietConflictWarnings = true;

  /**
   * The liason to the XML parser, so the XSL processor
   * can handle included files, and the like, and do the
   * initial parse of the XSL document.
   */
  private XPathContext m_xcontext;

  /**
   * Output handler to bottleneck SAX events.
   */
  private SerializationHandler m_serializationHandler;  

  /** The key manager, which manages xsl:keys. */
  private KeyManager m_keyManager = new KeyManager();

  /**
   * Stack for the purposes of flagging infinite recursion with
   * attribute sets.
   */
  Stack m_attrSetStack = null;

  /**
   * The table of counters for xsl:number support.
   */
  CountersTable m_countersTable = null;

  /**
   * Is > 0 when we're processing a for-each.
   */
  BoolStack m_currentTemplateRuleIsNull = new BoolStack();

  /**
   * Keeps track of the result delivered by any EXSLT <code>func:result</code>
   * instruction that has been executed for the currently active EXSLT
   * <code>func:function</code>
   */
  ObjectStack m_currentFuncResult = new ObjectStack();
  
  /**
   * The message manager, which manages error messages, warning
   * messages, and other types of message events.   
   */
  private MsgMgr m_msgMgr;

  /**
   * The flag for the setting of the optimize feature;
   * This flag should have the same value as the FEATURE_OPTIMIZE feature
   * which is set by the TransformerFactory.setAttribut() method before a
   * Transformer is created
   */    
  private boolean m_optimizer = true;

  /**
   * The flag for the setting of the incremental feature;
   * This flag should have the same value as the FEATURE_INCREMENTAL feature
   * which is set by the TransformerFactory.setAttribut() method before a
   * Transformer is created
   */    
  private boolean m_incremental = false;

  /**
   * The flag for the setting of the source_location feature;
   * This flag should have the same value as the FEATURE_SOURCE_LOCATION feature
   * which is set by the TransformerFactory.setAttribut() method before a
   * Transformer is created
   */  
  private boolean m_source_location = false;
    
  /**
   * The SAX error handler, where errors and warnings are sent.
   */
  private ErrorListener m_errorHandler =
    new org.apache.xml.utils.DefaultErrorHandler(false);

  /**
   * If the transform thread throws an exception, the exception needs to
   * be stashed away so that the main thread can pass it on to the
   * client. 
   */
  private Exception m_exceptionThrown = null;

  /**
   * This is needed for support of setSourceTreeDocForThread(Node doc),
   * which must be called in order for the transform thread's run
   * method to obtain the root of the source tree to be transformed.     
   */
  private int m_doc;

  /** Flag to to tell if the tranformer needs to be reset. */
  private boolean m_hasBeenReset = false;

  /** NEEDSDOC Field m_shouldReset          */
  private boolean m_shouldReset = true;

  /**
   * A stack of current template modes.
   */
  private Stack m_modes = new Stack();

  //==========================================================
  // SECTION: Constructor
  //==========================================================

  /**
   * Construct a TransformerImpl.
   *
   * @param stylesheet The root of the stylesheet tree.
   */
  public TransformerImpl(StylesheetRoot stylesheet)
   // throws javax.xml.transform.TransformerException    
  {
    m_optimizer = stylesheet.getOptimizer();
    m_incremental = stylesheet.getIncremental();
    m_source_location = stylesheet.getSource_location();  	
    setStylesheet(stylesheet);
    XPathContext xPath = new XPathContext(this);
    xPath.setIncremental(m_incremental);
    xPath.getDTMManager().setIncremental(m_incremental);
    xPath.setSource_location(m_source_location);
    xPath.getDTMManager().setSource_location(m_source_location);
    
    if (stylesheet.isSecureProcessing())
      xPath.setSecureProcessing(true);
    
    setXPathContext(xPath);
    getXPathContext().setNamespaceContext(stylesheet);
  }
  
  // ================ ExtensionsTable ===================

  /**
   * The table of ExtensionHandlers.
   */
  private ExtensionsTable m_extensionsTable = null;

  /**
   * Get the extensions table object. 
   *
   * @return The extensions table.
   */
  public ExtensionsTable getExtensionsTable()
  {
    return m_extensionsTable;
  }

  /**
   * If the stylesheet contains extensions, set the extensions table object.
   *
   *
   * @param sroot The stylesheet.
   * @throws javax.xml.transform.TransformerException
   */
  void setExtensionsTable(StylesheetRoot sroot)
       throws javax.xml.transform.TransformerException
  {
    try
    {
      if (sroot.getExtensions() != null)
        m_extensionsTable = new ExtensionsTable(sroot);
    }
    catch (javax.xml.transform.TransformerException te)
    {te.printStackTrace();}
  }
  
  //== Implementation of the XPath ExtensionsProvider interface.
  
  public boolean functionAvailable(String ns, String funcName)
          throws javax.xml.transform.TransformerException
  {
    return getExtensionsTable().functionAvailable(ns, funcName);
  }
  
  public boolean elementAvailable(String ns, String elemName)
          throws javax.xml.transform.TransformerException
  {
    return getExtensionsTable().elementAvailable(ns, elemName);   
  }
   
  public Object extFunction(String ns, String funcName, 
                            Vector argVec, Object methodKey)
            throws javax.xml.transform.TransformerException
  {//System.out.println("TransImpl.extFunction() " + ns + " " + funcName +" " + getExtensionsTable());
    return getExtensionsTable().extFunction(ns, funcName, 
                                        argVec, methodKey,
                                        getXPathContext().getExpressionContext());   
  }

  public Object extFunction(FuncExtFunction extFunction, Vector argVec)
            throws javax.xml.transform.TransformerException
  {
    return getExtensionsTable().extFunction(extFunction, argVec,
                                            getXPathContext().getExpressionContext());   
  }
  
  //=========================

  /**
   * Reset the state.  This needs to be called after a process() call
   * is invoked, if the processor is to be used again.
   */
  public void reset()
  {

    if (!m_hasBeenReset && m_shouldReset)
    {
      m_hasBeenReset = true;

      if (this.m_outputStream != null)
      {
        try
        {
          m_outputStream.close();
        }
        catch (java.io.IOException ioe){}
      }

      m_outputStream = null;

      // I need to look more carefully at which of these really
      // needs to be reset.
      m_countersTable = null;

      m_xcontext.reset();
      
      m_xcontext.getVarStack().reset();
      resetUserParameters();
      

      m_currentTemplateElements.removeAllElements();     
      m_currentMatchTemplates.removeAllElements();
      m_currentMatchedNodes.removeAllElements();
      
      m_serializationHandler = null;      
      m_outputTarget = null;
      m_keyManager = new KeyManager();
      m_attrSetStack = null;
      m_countersTable = null;
      m_currentTemplateRuleIsNull = new BoolStack();
      // m_xmlSource = null; // android-removed
      m_doc = DTM.NULL;
      // m_isTransformDone = false; // android-removed
      m_transformThread = null;

      // m_inputContentHandler = null;
      // For now, reset the document cache each time.
      m_xcontext.getSourceTreeManager().reset();
    }

    //    m_reportInPostExceptionFromThread = false;
  }

  // ========= Transformer Interface Implementation ==========

  /**
   * Get the thread that the transform process is on.
   *
   * @return The thread that the transform process is on, or null.
   * @xsl.usage internal
   */
  public Thread getTransformThread()
  {
    return m_transformThread;
  }

  /**
   * Get the thread that the transform process is on.
   *
   * @param t The transform thread, may be null.
   * @xsl.usage internal
   */
  public void setTransformThread(Thread t)
  {
    m_transformThread = t;
  }

  /** NEEDSDOC Field m_hasTransformThreadErrorCatcher          */
  private boolean m_hasTransformThreadErrorCatcher = false;

  /**
   * Return true if the transform was initiated from the transform method,
   * otherwise it was probably done from a pure parse events.
   *
   * NEEDSDOC ($objectName$) @return
   */
  public boolean hasTransformThreadErrorCatcher()
  {
    return m_hasTransformThreadErrorCatcher;
  }
        
        /**
   * Process the source tree to SAX parse events.
   * @param source  The input for the source tree.
   *
   * @throws TransformerException
   */
  public void transform(Source source) throws TransformerException
  {
                transform(source, true); 
        }

  /**
   * Process the source tree to SAX parse events.
   * @param source  The input for the source tree.
   * @param shouldRelease  Flag indicating whether to release DTMManager.
   *
   * @throws TransformerException
   */
  public void transform(Source source, boolean shouldRelease) throws TransformerException
  {

    try
    {
        
      // Patch for bugzilla #13863.  If we don't reset the namespaceContext
      // then we will get a NullPointerException if transformer is reused 
      // (for stylesheets that use xsl:key).  Not sure if this should go 
      // here or in reset(). -is  
      if(getXPathContext().getNamespaceContext() == null){
         getXPathContext().setNamespaceContext(getStylesheet());
      }
      String base = source.getSystemId();
      
      // If no systemID of the source, use the base of the stylesheet.
      if(null == base)
      {
        base = m_stylesheetRoot.getBaseIdentifier();
      }

      // As a last resort, use the current user dir.
      if(null == base)
      {
        String currentDir = "";
        try {
          currentDir = System.getProperty("user.dir");
        }
        catch (SecurityException se) {}// user.dir not accessible from applet
              
        if (currentDir.startsWith(java.io.File.separator))
          base = "file://" + currentDir;
        else
          base = "file:///" + currentDir;
        
        base = base + java.io.File.separatorChar
               + source.getClass().getName();
      }
      setBaseURLOfSource(base);
      DTMManager mgr = m_xcontext.getDTMManager();
      /*
       * According to JAXP1.2, new SAXSource()/StreamSource()
       * should create an empty input tree, with a default root node. 
       * new DOMSource()creates an empty document using DocumentBuilder.
       * newDocument(); Use DocumentBuilder.newDocument() for all 3 situations,
       * since there is no clear spec. how to create an empty tree when
       * both SAXSource() and StreamSource() are used.
       */
      if ((source instanceof StreamSource && source.getSystemId()==null &&
         ((StreamSource)source).getInputStream()==null &&
         ((StreamSource)source).getReader()==null)||
         (source instanceof SAXSource &&
         ((SAXSource)source).getInputSource()==null &&
         ((SAXSource)source).getXMLReader()==null )||
         (source instanceof DOMSource && ((DOMSource)source).getNode()==null)){
        try {
          DocumentBuilderFactory builderF = 
                   DocumentBuilderFactory.newInstance();
          DocumentBuilder builder = builderF.newDocumentBuilder();
          String systemID = source.getSystemId();
          source = new DOMSource(builder.newDocument());

          // Copy system ID from original, empty Source to new Source
          if (systemID != null) {
            source.setSystemId(systemID);
          }
        } catch (ParserConfigurationException e) {
          fatalError(e);
        }           
      }
      DTM dtm = mgr.getDTM(source, false, this, true, true);
      dtm.setDocumentBaseURI(base);
      
      boolean hardDelete = true;  // %REVIEW% I have to think about this. -sb

      try
      {
      	// NOTE: This will work because this is _NOT_ a shared DTM, and thus has
      	// only a single Document node. If it could ever be an RTF or other
      	// shared DTM, look at dtm.getDocumentRoot(nodeHandle).
        this.transformNode(dtm.getDocument());
      }
      finally
      {
        if (shouldRelease)
          mgr.release(dtm, hardDelete);
      }

      // Kick off the parse.  When the ContentHandler gets 
      // the startDocument event, it will call transformNode( node ).
      // reader.parse( xmlSource );
      // This has to be done to catch exceptions thrown from 
      // the transform thread spawned by the STree handler.
      Exception e = getExceptionThrown();

      if (null != e)
      {
        if (e instanceof javax.xml.transform.TransformerException)
        {
          throw (javax.xml.transform.TransformerException) e;
        }
        else if (e instanceof org.apache.xml.utils.WrappedRuntimeException)
        {
          fatalError(
              ((org.apache.xml.utils.WrappedRuntimeException) e).getException());
        }
        else
        {
          throw new javax.xml.transform.TransformerException(e);
        }
      }
      else if (null != m_serializationHandler)
      {
        m_serializationHandler.endDocument();
      }
    }
    catch (org.apache.xml.utils.WrappedRuntimeException wre)
    {
      Throwable throwable = wre.getException();

      while (throwable
             instanceof org.apache.xml.utils.WrappedRuntimeException)
      {
        throwable =
          ((org.apache.xml.utils.WrappedRuntimeException) throwable).getException();
      }

      fatalError(throwable);
    }

    // Patch attributed to David Eisenberg <david@catcode.com>
    catch (org.xml.sax.SAXParseException spe)
    {
      fatalError(spe);
    }
    catch (org.xml.sax.SAXException se)
    {
      m_errorHandler.fatalError(new TransformerException(se));
    }
    finally
    {
      m_hasTransformThreadErrorCatcher = false;

      // This looks to be redundent to the one done in TransformNode.
      reset();
    }
  }
  
  private void fatalError(Throwable throwable) throws TransformerException
  {
    if (throwable instanceof org.xml.sax.SAXParseException)
      m_errorHandler.fatalError(new TransformerException(throwable.getMessage(),new SAXSourceLocator((org.xml.sax.SAXParseException)throwable)));
    else
      m_errorHandler.fatalError(new TransformerException(throwable));
    
  }

  /**
   * Get the base URL of the source.
   *
   *
   * NEEDSDOC @param base
   * @return The base URL of the source tree, or null.
   */
  public void setBaseURLOfSource(String base)
  {
    m_urlOfSource = base;
  }

  /**
   * Get an output property that is in effect for the
   * transformation.  The property specified may be a property
   * that was set with setOutputProperty, or it may be a
   * property specified in the stylesheet.
   *
   * NEEDSDOC @param qnameString
   *
   * @return The string value of the output property, or null
   * if no property was found.
   *
   * @throws IllegalArgumentException If the property is not supported.
   *
   * @see javax.xml.transform.OutputKeys
   */
  public String getOutputProperty(String qnameString)
          throws IllegalArgumentException
  {

    String value = null;
    OutputProperties props = getOutputFormat();

    value = props.getProperty(qnameString);

    if (null == value)
    {
      if (!OutputProperties.isLegalPropertyKey(qnameString))
        throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{qnameString})); //"output property not recognized: "
                                           //+ qnameString);
    }

    return value;
  }

  /**
   * Get the value of a property, without using the default properties.  This
   * can be used to test if a property has been explicitly set by the stylesheet
   * or user.
   *
   * NEEDSDOC @param qnameString
   *
   * @return The value of the property, or null if not found.
   *
   * @throws IllegalArgumentException If the property is not supported,
   * and is not namespaced.
   */
  public String getOutputPropertyNoDefault(String qnameString)
          throws IllegalArgumentException
  {

    String value = null;
    OutputProperties props = getOutputFormat();

    value = (String) props.getProperties().get(qnameString);

    if (null == value)
    {
      if (!OutputProperties.isLegalPropertyKey(qnameString))
        throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{qnameString})); //"output property not recognized: "
                                          // + qnameString);
    }

    return value;
  }

  /**
   * This method is used to set or override the value
   * of the effective xsl:output attribute values
   * specified in the stylesheet.
   * <p>
   * The recognized standard output properties are:
   * <ul>
   * <li>cdata-section-elements
   * <li>doctype-system
   * <li>doctype-public
   * <li>indent
   * <li>media-type
   * <li>method
   * <li>omit-xml-declaration
   * <li>standalone
   * <li>version
   * </ul>
   * <p>
   * For example:
   * <pre>
   *   tran.setOutputProperty("standalone", "yes");
   * </pre>
   * <p>
   * In the case of the cdata-section-elements property,
   * the value should be a whitespace separated list of
   * element names.  The element name is the local name
   * of the element, if it is in no namespace, or, the URI
   * in braces followed immediately by the local name
   * if the element is in that namespace. For example: 
   * <pre>
   * tran.setOutputProperty(
   *   "cdata-section-elements", 
   *   "elem1 {http://example.uri}elem2 elem3");
   * </pre>
   * <p>
   * The recognized Xalan extension elements are: 
   * <ul>
   * <li>content-handler
   * <li>entities
   * <li>indent-amount
   * <li>line-separator
   * <li>omit-meta-tag
   * <li>use-url-escaping
   * </ul>
   * <p>
   * These must be in the extension namespace of
   * "http://xml.apache.org/xalan".  This is accomplished
   * by putting the namespace URI in braces before the 
   * property name, for example:
   * <pre>
   *   tran.setOutputProperty(
   *     "{http://xml.apache.org/xalan}line-separator" ,
   *     "\n");
   * </pre> 
   *
   * @param name The property name.
   * @param value The requested value for the property.
   * @throws IllegalArgumentException if the property name is not legal.
   */
  public void setOutputProperty(String name, String value)
          throws IllegalArgumentException
  {

    synchronized (m_reentryGuard)
    {

      // Get the output format that was set by the user, otherwise get the 
      // output format from the stylesheet.
      if (null == m_outputFormat)
      {
        m_outputFormat =
          (OutputProperties) getStylesheet().getOutputComposed().clone();
      }

      if (!OutputProperties.isLegalPropertyKey(name))
        throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
                                           //+ name);

      m_outputFormat.setProperty(name, value);
    }
  }

  /**
   * Set the output properties for the transformation.  These
   * properties will override properties set in the templates
   * with xsl:output.
   *
   * <p>If argument to this function is null, any properties
   * previously set will be removed.</p>
   *
   * @param oformat A set of output properties that will be
   * used to override any of the same properties in effect
   * for the transformation.
   *
   * @see javax.xml.transform.OutputKeys
   * @see java.util.Properties
   *
   * @throws IllegalArgumentException if any of the argument keys are not
   * recognized and are not namespace qualified.   
   */
  public void setOutputProperties(Properties oformat)
  		throws IllegalArgumentException
  {

    synchronized (m_reentryGuard)
    {
      if (null != oformat)
      {

        // See if an *explicit* method was set.
        String method = (String) oformat.get(OutputKeys.METHOD);

        if (null != method)
          m_outputFormat = new OutputProperties(method);
        else if(m_outputFormat==null)
          m_outputFormat = new OutputProperties();

        m_outputFormat.copyFrom(oformat);
        // copyFrom does not set properties that have been already set, so 
        // this must be called after, which is a bit in the reverse from 
        // what one might think.
        m_outputFormat.copyFrom(m_stylesheetRoot.getOutputProperties());
      }
      else {
        // if oformat is null JAXP says that any props previously set are removed
        // and we are to revert back to those in the templates object (i.e. Stylesheet).
        m_outputFormat = null;
      }
    }
  }

  /**
   * Get a copy of the output properties for the transformation.  These
   * properties will override properties set in the templates
   * with xsl:output.
   *
   * <p>Note that mutation of the Properties object returned will not
   * effect the properties that the transformation contains.</p>
   *
   * @return  A copy of the set of output properties in effect
   * for the next transformation.
   *
   * NEEDSDOC ($objectName$) @return
   */
  public Properties getOutputProperties()
  {
    return (Properties) getOutputFormat().getProperties().clone();
  }

    /**
     * Create a result ContentHandler from a Result object, based
     * on the current OutputProperties.
     *
     * @param outputTarget Where the transform result should go,
     * should not be null.
     *
     * @return A valid ContentHandler that will create the
     * result tree when it is fed SAX events.
     *
     * @throws TransformerException
     */
    public SerializationHandler createSerializationHandler(Result outputTarget)
            throws TransformerException
    {
       SerializationHandler xoh =
        createSerializationHandler(outputTarget, getOutputFormat());
       return xoh;
    }

    /**
     * Create a ContentHandler from a Result object and an OutputProperties.
     *
     * @param outputTarget Where the transform result should go,
     * should not be null.
     * @param format The OutputProperties object that will contain
     * instructions on how to serialize the output.
     *
     * @return A valid ContentHandler that will create the
     * result tree when it is fed SAX events.
     *
     * @throws TransformerException
     */
    public SerializationHandler createSerializationHandler(
            Result outputTarget, OutputProperties format)
              throws TransformerException
    {

      SerializationHandler xoh;

      // If the Result object contains a Node, then create
      // a ContentHandler that will add nodes to the input node.
      org.w3c.dom.Node outputNode = null;

      if (outputTarget instanceof DOMResult)
      {
        outputNode = ((DOMResult) outputTarget).getNode();
        org.w3c.dom.Node nextSibling = ((DOMResult)outputTarget).getNextSibling();

        org.w3c.dom.Document doc;
        short type;

        if (null != outputNode)
        {
          type = outputNode.getNodeType();
          doc = (org.w3c.dom.Node.DOCUMENT_NODE == type)
                ? (org.w3c.dom.Document) outputNode
                : outputNode.getOwnerDocument();
        }
        else
        {
          boolean isSecureProcessing = m_stylesheetRoot.isSecureProcessing();
          doc = org.apache.xml.utils.DOMHelper.createDocument(isSecureProcessing);
          outputNode = doc;
          type = outputNode.getNodeType();

          ((DOMResult) outputTarget).setNode(outputNode);
        }

        DOMBuilder handler =
          (org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE == type)
          ? new DOMBuilder(doc, (org.w3c.dom.DocumentFragment) outputNode)
          : new DOMBuilder(doc, outputNode);
        
        if (nextSibling != null)
          handler.setNextSibling(nextSibling);
        
          String encoding = format.getProperty(OutputKeys.ENCODING);          
          xoh = new ToXMLSAXHandler(handler, (LexicalHandler)handler, encoding);
      }
      else if (outputTarget instanceof SAXResult)
      {
        ContentHandler handler = ((SAXResult) outputTarget).getHandler();
        
        if (null == handler)
           throw new IllegalArgumentException(
             "handler can not be null for a SAXResult"); 
             
        LexicalHandler lexHandler;
        if (handler instanceof LexicalHandler)     
            lexHandler = (LexicalHandler)  handler;
        else
            lexHandler = null;
            
        String encoding = format.getProperty(OutputKeys.ENCODING); 
        String method = format.getProperty(OutputKeys.METHOD);

        ToXMLSAXHandler toXMLSAXHandler = new ToXMLSAXHandler(handler, lexHandler, encoding);
        toXMLSAXHandler.setShouldOutputNSAttr(false);
        xoh = toXMLSAXHandler;   


        String publicID = format.getProperty(OutputKeys.DOCTYPE_PUBLIC); 
        String systemID = format.getProperty(OutputKeys.DOCTYPE_SYSTEM); 
        if (systemID != null)
            xoh.setDoctypeSystem(systemID);
        if (publicID != null)
            xoh.setDoctypePublic(publicID);
        
        if (handler instanceof TransformerClient) {
            XalanTransformState state = new XalanTransformState();
            ((TransformerClient)handler).setTransformState(state);
            ((ToSAXHandler)xoh).setTransformState(state);
        }

 
      }

      // Otherwise, create a ContentHandler that will serialize the
      // result tree to either a stream or a writer.
      else if (outputTarget instanceof StreamResult)
      {
        StreamResult sresult = (StreamResult) outputTarget;

        try
        {
          SerializationHandler serializer =
            (SerializationHandler) SerializerFactory.getSerializer(format.getProperties());

          if (null != sresult.getWriter())
            serializer.setWriter(sresult.getWriter());
          else if (null != sresult.getOutputStream())
            serializer.setOutputStream(sresult.getOutputStream());
          else if (null != sresult.getSystemId())
          {
            String fileURL = sresult.getSystemId();

            if (fileURL.startsWith("file:///"))
            {
              if (fileURL.substring(8).indexOf(":") >0)
                fileURL = fileURL.substring(8);
              else
                fileURL = fileURL.substring(7);
            }
            else if (fileURL.startsWith("file:/"))
            {
                if (fileURL.substring(6).indexOf(":") >0)
                    fileURL = fileURL.substring(6);
                  else
                    fileURL = fileURL.substring(5);            	
            }

            m_outputStream = new java.io.FileOutputStream(fileURL);

            serializer.setOutputStream(m_outputStream);
            
            xoh = serializer;
          }
          else
            throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_OUTPUT_SPECIFIED, null)); //"No output specified!");

          // handler = serializer.asContentHandler();

        //  this.setSerializer(serializer);

          xoh = serializer;  
        }
//        catch (UnsupportedEncodingException uee)
//        {
//          throw new TransformerException(uee);
//        }
        catch (IOException ioe)
        {
          throw new TransformerException(ioe);
        }
      }
      else
      {
        throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_TO_RESULT_TYPE, new Object[]{outputTarget.getClass().getName()})); //"Can't transform to a Result of type "
                                       //+ outputTarget.getClass().getName()
                                       //+ "!");
      }
      
      // before we forget, lets make the created handler hold a reference
      // to the current TransformImpl object
      xoh.setTransformer(this);

      SourceLocator srcLocator = getStylesheet();
      xoh.setSourceLocator(srcLocator);
      
      
      return xoh;

   
    }
        
        /**
   * Process the source tree to the output result.
   * @param xmlSource  The input for the source tree.
   * @param outputTarget The output source target.
   *
   * @throws TransformerException
   */
  public void transform(Source xmlSource, Result outputTarget)
          throws TransformerException
  {
                transform(xmlSource, outputTarget, true);
        }

  /**
   * Process the source tree to the output result.
   * @param xmlSource  The input for the source tree.
   * @param outputTarget The output source target.
   * @param shouldRelease  Flag indicating whether to release DTMManager. 
   *
   * @throws TransformerException
   */
  public void transform(Source xmlSource, Result outputTarget, boolean shouldRelease)
          throws TransformerException
  {

    synchronized (m_reentryGuard)
    {
      SerializationHandler xoh = createSerializationHandler(outputTarget);
      this.setSerializationHandler(xoh);        

      m_outputTarget = outputTarget;

      transform(xmlSource, shouldRelease);
    }
  }

  /**
   * Process the source node to the output result, if the
   * processor supports the "http://xml.org/trax/features/dom/input"
   * feature.
   * %REVIEW% Do we need a Node version of this?
   * @param node  The input source node, which can be any valid DTM node.
   * @param outputTarget The output source target.
   *
   * @throws TransformerException
   */
  public void transformNode(int node, Result outputTarget)
          throws TransformerException
  {
    

    SerializationHandler xoh = createSerializationHandler(outputTarget);
    this.setSerializationHandler(xoh);

    m_outputTarget = outputTarget;

    transformNode(node);
  }

  /**
   * Process the source node to the output result, if the
   * processor supports the "http://xml.org/trax/features/dom/input"
   * feature.
   * %REVIEW% Do we need a Node version of this?
   * @param node  The input source node, which can be any valid DTM node.
   *
   * @throws TransformerException
   */
  public void transformNode(int node) throws TransformerException
  {
    //dml
    setExtensionsTable(getStylesheet());
    // Make sure we're not writing to the same output content handler.
    synchronized (m_serializationHandler)
    {
      m_hasBeenReset = false;
      
      XPathContext xctxt = getXPathContext();
      DTM dtm = xctxt.getDTM(node);

      try
      {
        pushGlobalVars(node);

        // ==========
        // Give the top-level templates a chance to pass information into 
        // the context (this is mainly for setting up tables for extensions).
        StylesheetRoot stylesheet = this.getStylesheet();
        int n = stylesheet.getGlobalImportCount();

        for (int i = 0; i < n; i++)
        {
          StylesheetComposed imported = stylesheet.getGlobalImport(i);
          int includedCount = imported.getIncludeCountComposed();

          for (int j = -1; j < includedCount; j++)
          {
            Stylesheet included = imported.getIncludeComposed(j);

            included.runtimeInit(this);

            for (ElemTemplateElement child = included.getFirstChildElem();
                    child != null; child = child.getNextSiblingElem())
            {
              child.runtimeInit(this);
            }
          }
        }
        // ===========        
        // System.out.println("Calling applyTemplateToNode - "+Thread.currentThread().getName());
        DTMIterator dtmIter = new org.apache.xpath.axes.SelfIteratorNoPredicate();
        dtmIter.setRoot(node, xctxt);
        xctxt.pushContextNodeList(dtmIter);
        try
        {
          this.applyTemplateToNode(null, null, node);
        }
        finally
        {
          xctxt.popContextNodeList();
        }
        // m_stylesheetRoot.getStartRule().execute(this);

        // System.out.println("Done with applyTemplateToNode - "+Thread.currentThread().getName());
        if (null != m_serializationHandler)
        {
          m_serializationHandler.endDocument();
        }
      }
      catch (Exception se)
      {

        // System.out.println(Thread.currentThread().getName()+" threw an exception! "
        //                   +se.getMessage());
        // If an exception was thrown, we need to make sure that any waiting 
        // handlers can terminate, which I guess is best done by sending 
        // an endDocument.
        
        // SAXSourceLocator
        while(se instanceof org.apache.xml.utils.WrappedRuntimeException)
        {
          Exception e = ((org.apache.xml.utils.WrappedRuntimeException)se).getException();
          if(null != e)
            se = e;
        }
        
        if (null != m_serializationHandler)
        {
          try
          {
            if(se instanceof org.xml.sax.SAXParseException)
              m_serializationHandler.fatalError((org.xml.sax.SAXParseException)se);
            else if(se instanceof TransformerException)
            {
              TransformerException te = ((TransformerException)se);
              SAXSourceLocator sl = new SAXSourceLocator( te.getLocator() );
              m_serializationHandler.fatalError(new org.xml.sax.SAXParseException(te.getMessage(), sl, te)); 
            }
            else
            {
              m_serializationHandler.fatalError(new org.xml.sax.SAXParseException(se.getMessage(), new SAXSourceLocator(), se)); 
            }             
          }
          catch (Exception e){}
        }        
        
        if(se instanceof TransformerException)
        {
          m_errorHandler.fatalError((TransformerException)se);
        }
        else if(se instanceof org.xml.sax.SAXParseException)
        {
          m_errorHandler.fatalError(new TransformerException(se.getMessage(), 
                      new SAXSourceLocator((org.xml.sax.SAXParseException)se), 
                      se));
        }
        else
        {
          m_errorHandler.fatalError(new TransformerException(se));
        }
        
      }
      finally
      {
        this.reset();
      }
    }
  }

  /**
   * Get a SAX2 ContentHandler for the input.
   *
   * @return A valid ContentHandler, which should never be null, as
   * long as getFeature("http://xml.org/trax/features/sax/input")
   * returns true.
   */
  public ContentHandler getInputContentHandler()
  {
    return getInputContentHandler(false);
  }

  /**
   * Get a SAX2 ContentHandler for the input.
   *
   * @param doDocFrag true if a DocumentFragment should be created as
   * the root, rather than a Document.
   *
   * @return A valid ContentHandler, which should never be null, as
   * long as getFeature("http://xml.org/trax/features/sax/input")
   * returns true.
   */
  public ContentHandler getInputContentHandler(boolean doDocFrag)
  {

    if (null == m_inputContentHandler)
    {

      //      if(null == m_urlOfSource && null != m_stylesheetRoot)
      //        m_urlOfSource = m_stylesheetRoot.getBaseIdentifier();
      m_inputContentHandler = new TransformerHandlerImpl(this, doDocFrag,
              m_urlOfSource);
    }

    return m_inputContentHandler;
  }

  /**
   * Set the output properties for the transformation.  These
   * properties will override properties set in the templates
   * with xsl:output.
   *
   * @param oformat A valid OutputProperties object (which will
   * not be mutated), or null.
   */
  public void setOutputFormat(OutputProperties oformat)
  {
    m_outputFormat = oformat;
  }

  /**
   * Get the output properties used for the transformation.
   *
   * @return the output format that was set by the user,
   * otherwise the output format from the stylesheet.
   */
  public OutputProperties getOutputFormat()
  {

    // Get the output format that was set by the user, otherwise get the 
    // output format from the stylesheet.
    OutputProperties format = (null == m_outputFormat)
                              ? getStylesheet().getOutputComposed()
                              : m_outputFormat;

    return format;
  }

  /**
   * Set a parameter for the templates.
   * 
   * @param name The name of the parameter.
   * @param namespace The namespace of the parameter.
   * @param value The value object.  This can be any valid Java object
   * -- it's up to the processor to provide the proper
   * coersion to the object, or simply pass it on for use
   * in extensions.
   */
  public void setParameter(String name, String namespace, Object value)
  {

    VariableStack varstack = getXPathContext().getVarStack();
    QName qname = new QName(namespace, name);
    XObject xobject = XObject.create(value, getXPathContext());
    
    StylesheetRoot sroot = m_stylesheetRoot;
    Vector vars = sroot.getVariablesAndParamsComposed();
    int i = vars.size();
    while (--i >= 0)
    {
      ElemVariable variable = (ElemVariable)vars.elementAt(i);
      if(variable.getXSLToken() == Constants.ELEMNAME_PARAMVARIABLE && 
         variable.getName().equals(qname))
      {
          varstack.setGlobalVariable(i, xobject);
      }
    }
  }

  /** NEEDSDOC Field m_userParams          */
  Vector m_userParams;

  /**
   * Set a parameter for the transformation.
   *
   * @param name The name of the parameter,
   *             which may have a namespace URI.
   * @param value The value object.  This can be any valid Java object
   * -- it's up to the processor to provide the proper
   * coersion to the object, or simply pass it on for use
   * in extensions.
   */
  public void setParameter(String name, Object value)
  {
    
    if (value == null) {
      throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_SET_PARAM_VALUE, new Object[]{name}));
    }    

    StringTokenizer tokenizer = new StringTokenizer(name, "{}", false);

    try
    {

      // The first string might be the namespace, or it might be 
      // the local name, if the namespace is null.
      String s1 = tokenizer.nextToken();
      String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;

      if (null == m_userParams)
        m_userParams = new Vector();

      if (null == s2)
      {
        replaceOrPushUserParam(new QName(s1), XObject.create(value, getXPathContext()));
        setParameter(s1, null, value);
      }
      else
      {
        replaceOrPushUserParam(new QName(s1, s2), XObject.create(value, getXPathContext()));
        setParameter(s2, s1, value);
      }
    }
    catch (java.util.NoSuchElementException nsee)
    {

      // Should throw some sort of an error.
    }
  }

  /**
   * NEEDSDOC Method replaceOrPushUserParam 
   *
   *
   * NEEDSDOC @param qname
   * NEEDSDOC @param xval
   */
  private void replaceOrPushUserParam(QName qname, XObject xval)
  {

    int n = m_userParams.size();

    for (int i = n - 1; i >= 0; i--)
    {
      Arg arg = (Arg) m_userParams.elementAt(i);

      if (arg.getQName().equals(qname))
      {
        m_userParams.setElementAt(new Arg(qname, xval, true), i);

        return;
      }
    }

    m_userParams.addElement(new Arg(qname, xval, true));
  }

  /**
   * Get a parameter that was explicitly set with setParameter
   * or setParameters.
   *
   *
   * NEEDSDOC @param name
   * @return A parameter that has been set with setParameter
   * or setParameters,
   * *not* all the xsl:params on the stylesheet (which require
   * a transformation Source to be evaluated).
   */
  public Object getParameter(String name)
  {

    try
    {

      // VariableStack varstack = getXPathContext().getVarStack();
      // The first string might be the namespace, or it might be 
      // the local name, if the namespace is null.
      QName qname = QName.getQNameFromString(name);

      if (null == m_userParams)
        return null;

      int n = m_userParams.size();

      for (int i = n - 1; i >= 0; i--)
      {
        Arg arg = (Arg) m_userParams.elementAt(i);

        if (arg.getQName().equals(qname))
        {
          return arg.getVal().object();
        }
      }

      return null;
    }
    catch (java.util.NoSuchElementException nsee)
    {

      // Should throw some sort of an error.
      return null;
    }
  }
  
  /**
   * Reset parameters that the user specified for the transformation.
   * Called during transformer.reset() after we have cleared the 
   * variable stack. We need to make sure that user params are
   * reset so that the transformer object can be reused. 
   */
  private void resetUserParameters()
  {

    try
    {
      
      if (null == m_userParams)
        return;

      int n = m_userParams.size();
      for (int i = n - 1; i >= 0; i--)
      {
        Arg arg = (Arg) m_userParams.elementAt(i);
        QName name = arg.getQName();
        // The first string might be the namespace, or it might be 
        // the local name, if the namespace is null.
        String s1 = name.getNamespace();
        String s2 = name.getLocalPart();

        setParameter(s2, s1, arg.getVal().object());
        
      }
      
    }
    catch (java.util.NoSuchElementException nsee)
    {
      // Should throw some sort of an error.
      
    }
  }

  /**
   * Set a bag of parameters for the transformation. Note that
   * these will not be additive, they will replace the existing
   * set of parameters.
   *
   * NEEDSDOC @param params
   */
  public void setParameters(Properties params)
  {

    clearParameters();

    Enumeration names = params.propertyNames();

    while (names.hasMoreElements())
    {
      String name = params.getProperty((String) names.nextElement());
      StringTokenizer tokenizer = new StringTokenizer(name, "{}", false);

      try
      {

        // The first string might be the namespace, or it might be 
        // the local name, if the namespace is null.
        String s1 = tokenizer.nextToken();
        String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;

        if (null == s2)
          setParameter(s1, null, params.getProperty(name));
        else
          setParameter(s2, s1, params.getProperty(name));
      }
      catch (java.util.NoSuchElementException nsee)
      {

        // Should throw some sort of an error.
      }
    }
  }

  /**
   * Reset the parameters to a null list.
   */
  public void clearParameters()
  {

    synchronized (m_reentryGuard)
    {
      VariableStack varstack = new VariableStack();

      m_xcontext.setVarStack(varstack);

      m_userParams = null;
    }
  }


  /**
   * Internal -- push the global variables from the Stylesheet onto
   * the context's runtime variable stack.
   * <p>If we encounter a variable
   * that is already defined in the variable stack, we ignore it.  This
   * is because the second variable definition will be at a lower import
   * precedence.  Presumably, global"variables at the same import precedence
   * with the same name will have been caught during the recompose process.
   * <p>However, if we encounter a parameter that is already defined in the
   * variable stack, we need to see if this is a parameter whose value was
   * supplied by a setParameter call.  If so, we need to "receive" the one
   * already in the stack, ignoring this one.  If it is just an earlier
   * xsl:param or xsl:variable definition, we ignore it using the same
   * reasoning as explained above for the variable.
   *
   * @param contextNode The root of the source tree, can't be null.
   *
   * @throws TransformerException
   */
  protected void pushGlobalVars(int contextNode) throws TransformerException
  {

    XPathContext xctxt = m_xcontext;
    VariableStack vs = xctxt.getVarStack();
    StylesheetRoot sr = getStylesheet();
    Vector vars = sr.getVariablesAndParamsComposed();
    
    int i = vars.size();
    vs.link(i);

    while (--i >= 0)
    {
      ElemVariable v = (ElemVariable) vars.elementAt(i);

      // XObject xobj = v.getValue(this, contextNode);
      XObject xobj = new XUnresolvedVariable(v, contextNode, this,
                                     vs.getStackFrame(), 0, true);
      
      if(null == vs.elementAt(i))                               
        vs.setGlobalVariable(i, xobj);
    }

  }

  /**
   * Set an object that will be used to resolve URIs used in
   * document(), etc.
   * @param resolver An object that implements the URIResolver interface,
   * or null.
   */
  public void setURIResolver(URIResolver resolver)
  {

    synchronized (m_reentryGuard)
    {
      m_xcontext.getSourceTreeManager().setURIResolver(resolver);
    }
  }

  /**
   * Get an object that will be used to resolve URIs used in
   * document(), etc.
   *
   * @return An object that implements the URIResolver interface,
   * or null.
   */
  public URIResolver getURIResolver()
  {
    return m_xcontext.getSourceTreeManager().getURIResolver();
  }

  // ======== End Transformer Implementation ========  

  /**
   * Set the content event handler.
   *
   * NEEDSDOC @param handler
   * @throws java.lang.NullPointerException If the handler
   *            is null.
   * @see org.xml.sax.XMLReader#setContentHandler
   */
  public void setContentHandler(ContentHandler handler)
  {

    if (handler == null)
    {
      throw new NullPointerException(XSLMessages.createMessage(XSLTErrorResources.ER_NULL_CONTENT_HANDLER, null)); //"Null content handler");
    }
    else
    {
      m_outputContentHandler = handler;

      if (null == m_serializationHandler)
      {
        ToXMLSAXHandler h = new ToXMLSAXHandler();
        h.setContentHandler(handler);
        h.setTransformer(this);
        
        m_serializationHandler = h;
      }
      else
        m_serializationHandler.setContentHandler(handler);
    }
  }

  /**
   * Get the content event handler.
   *
   * @return The current content handler, or null if none was set.
   * @see org.xml.sax.XMLReader#getContentHandler
   */
  public ContentHandler getContentHandler()
  {
    return m_outputContentHandler;
  }

  /**
   * Given a stylesheet element, create a result tree fragment from it's
   * contents. The fragment will be built within the shared RTF DTM system
   * used as a variable stack.
   * @param templateParent The template element that holds the fragment.
   * @return the NodeHandle for the root node of the resulting RTF.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public int transformToRTF(ElemTemplateElement templateParent)
          throws TransformerException
  {
    // Retrieve a DTM to contain the RTF. At this writing, this may be a
    // multi-document DTM (SAX2RTFDTM).
    DTM dtmFrag = m_xcontext.getRTFDTM();
    return transformToRTF(templateParent,dtmFrag);
  }
  
  /**
   * Given a stylesheet element, create a result tree fragment from it's
   * contents. The fragment will also use the shared DTM system, but will
   * obtain its space from the global variable pool rather than the dynamic
   * variable stack. This allows late binding of XUnresolvedVariables without
   * the risk that their content will be discarded when the variable stack
   * is popped.
   * 
   * @param templateParent The template element that holds the fragment.
   * @return the NodeHandle for the root node of the resulting RTF.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public int transformToGlobalRTF(ElemTemplateElement templateParent)
          throws TransformerException
  {
    // Retrieve a DTM to contain the RTF. At this writing, this may be a
    // multi-document DTM (SAX2RTFDTM).
    DTM dtmFrag = m_xcontext.getGlobalRTFDTM();
    return transformToRTF(templateParent,dtmFrag);
  }
  
  /**
   * Given a stylesheet element, create a result tree fragment from it's
   * contents.
   * @param templateParent The template element that holds the fragment.
   * @param dtmFrag The DTM to write the RTF into
   * @return the NodeHandle for the root node of the resulting RTF.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  private int transformToRTF(ElemTemplateElement templateParent,DTM dtmFrag)
          throws TransformerException
  {

    XPathContext xctxt = m_xcontext;
    
    ContentHandler rtfHandler = dtmFrag.getContentHandler();

    // Obtain the ResultTreeFrag's root node.
    // NOTE: In SAX2RTFDTM, this value isn't available until after
    // the startDocument has been issued, so assignment has been moved
    // down a bit in the code.
    int resultFragment; // not yet reliably = dtmFrag.getDocument();

    // Save the current result tree handler.
    SerializationHandler savedRTreeHandler = this.m_serializationHandler;
 

    // And make a new handler for the RTF.
    ToSAXHandler h = new ToXMLSAXHandler();
    h.setContentHandler(rtfHandler);
    h.setTransformer(this);
    
    // Replace the old handler (which was already saved)
    m_serializationHandler = h;
 
    // use local variable for the current handler
    SerializationHandler rth = m_serializationHandler;

    try
    {
      rth.startDocument();
      
      // startDocument is "bottlenecked" in RTH. We need it acted upon immediately,
      // to set the DTM's state as in-progress, so that if the xsl:variable's body causes
      // further RTF activity we can keep that from bashing this DTM.
      rth.flushPending(); 
 
      try
      {

        // Do the transformation of the child elements.
        executeChildTemplates(templateParent, true);

        // Make sure everything is flushed!
        rth.flushPending();
        
        // Get the document ID. May not exist until the RTH has not only
        // received, but flushed, the startDocument, and may be invalid
        // again after the document has been closed (still debating that)
        // ... so waiting until just before the end seems simplest/safest. 
	resultFragment = dtmFrag.getDocument();      
      }
      finally
      {
        rth.endDocument();
      }
    }
    catch (org.xml.sax.SAXException se)
    {
      throw new TransformerException(se);
    }
    finally
    {

      // Restore the previous result tree handler.
      this.m_serializationHandler = savedRTreeHandler;
    }

    return resultFragment;
  }

  /**
   * Take the contents of a template element, process it, and
   * convert it to a string.
   *
   * @param elem The parent element whose children will be output
   * as a string.
   *
   * @return The stringized result of executing the elements children.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public String transformToString(ElemTemplateElement elem)
          throws TransformerException
  {
    ElemTemplateElement firstChild = elem.getFirstChildElem();
    if(null == firstChild)
      return "";
    if(elem.hasTextLitOnly() && m_optimizer)
    {
      return ((ElemTextLiteral)firstChild).getNodeValue();
    }

    // Save the current result tree handler.
    SerializationHandler savedRTreeHandler = this.m_serializationHandler;

    // Create a Serializer object that will handle the SAX events 
    // and build the ResultTreeFrag nodes.
    StringWriter sw = (StringWriter) m_stringWriterObjectPool.getInstance();

    m_serializationHandler =
        (ToTextStream) m_textResultHandlerObjectPool.getInstance();

      if (null == m_serializationHandler)
      {
        // if we didn't get one from the pool, go make a new one

        
        Serializer serializer = org.apache.xml.serializer.SerializerFactory.getSerializer(
            m_textformat.getProperties());
        m_serializationHandler = (SerializationHandler) serializer;
      } 

        m_serializationHandler.setTransformer(this);
        m_serializationHandler.setWriter(sw);
 
 
    String result;

    try
    {
        /* Don't call startDocument, the SerializationHandler  will
         * generate its own internal startDocument call anyways
         */
      // this.m_serializationHandler.startDocument();

      // Do the transformation of the child elements.
      executeChildTemplates(elem, true);
        this.m_serializationHandler.endDocument();

      result = sw.toString();
    }
    catch (org.xml.sax.SAXException se)
    {
      throw new TransformerException(se);
    }
    finally
    {
      sw.getBuffer().setLength(0);

      try
      {
        sw.close();
      }
      catch (Exception ioe){}

      m_stringWriterObjectPool.freeInstance(sw);
      m_serializationHandler.reset();
      m_textResultHandlerObjectPool.freeInstance(m_serializationHandler);

      // Restore the previous result tree handler.
      m_serializationHandler = savedRTreeHandler;
    }

    return result;
  }

  /**
   * Given an element and mode, find the corresponding
   * template and process the contents.
   *
   * @param xslInstruction The calling element.
   * @param template The template to use if xsl:for-each, current template for apply-imports, or null.
   * @param child The source context node.
   * @throws TransformerException
   * @return true if applied a template, false if not.
   * @xsl.usage advanced
   */
  public boolean applyTemplateToNode(ElemTemplateElement xslInstruction,  // xsl:apply-templates or xsl:for-each
                                     ElemTemplate template, int child)
                                             throws TransformerException
  {

    DTM dtm = m_xcontext.getDTM(child);
    short nodeType = dtm.getNodeType(child);
    boolean isDefaultTextRule = false;
    boolean isApplyImports = false;
    
    isApplyImports = ((xslInstruction == null)
                                ? false
                                : xslInstruction.getXSLToken()
                                  == Constants.ELEMNAME_APPLY_IMPORTS);        

    if (null == template || isApplyImports)
    {
      int maxImportLevel, endImportLevel=0;

      if (isApplyImports)
      {
        maxImportLevel =
          template.getStylesheetComposed().getImportCountComposed() - 1;
        endImportLevel =
          template.getStylesheetComposed().getEndImportCountComposed();
      }
      else
      {
        maxImportLevel = -1;
      }

      // If we're trying an xsl:apply-imports at the top level (ie there are no
      // imported stylesheets), we need to indicate that there is no matching template.
      // The above logic will calculate a maxImportLevel of -1 which indicates
      // that we should find any template.  This is because a value of -1 for
      // maxImportLevel has a special meaning.  But we don't want that.
      // We want to match -no- templates. See bugzilla bug 1170.
      if (isApplyImports && (maxImportLevel == -1))
      {
        template = null;
      }
      else
      {

        // Find the XSL template that is the best match for the 
        // element.        
        XPathContext xctxt = m_xcontext;

        try
        {
          xctxt.pushNamespaceContext(xslInstruction);

          QName mode = this.getMode();
          
          if (isApplyImports)
            template = m_stylesheetRoot.getTemplateComposed(xctxt, child, mode,
                  maxImportLevel, endImportLevel, m_quietConflictWarnings, dtm);
          else
            template = m_stylesheetRoot.getTemplateComposed(xctxt, child, mode,
                  m_quietConflictWarnings, dtm);
          
        }
        finally
        {
          xctxt.popNamespaceContext();
        }
      }

      // If that didn't locate a node, fall back to a default template rule.
      // See http://www.w3.org/TR/xslt#built-in-rule.
      if (null == template)
      {
        switch (nodeType)
        {
        case DTM.DOCUMENT_FRAGMENT_NODE :
        case DTM.ELEMENT_NODE :
          template = m_stylesheetRoot.getDefaultRule();
          break;
        case DTM.CDATA_SECTION_NODE :
        case DTM.TEXT_NODE :
        case DTM.ATTRIBUTE_NODE :
          template = m_stylesheetRoot.getDefaultTextRule();
          isDefaultTextRule = true;
          break;
        case DTM.DOCUMENT_NODE :
          template = m_stylesheetRoot.getDefaultRootRule();
          break;
        default :

          // No default rules for processing instructions and the like.
          return false;
        }
      }
    }

    // If we are processing the default text rule, then just clone 
    // the value directly to the result tree.
    try
    {
      pushElemTemplateElement(template);
      m_xcontext.pushCurrentNode(child);
      pushPairCurrentMatched(template, child);
      
      // Fix copy copy29 test.
      if (!isApplyImports) {
          DTMIterator cnl = new org.apache.xpath.NodeSetDTM(child, m_xcontext.getDTMManager());
          m_xcontext.pushContextNodeList(cnl);
      }

      if (isDefaultTextRule)
      {
        switch (nodeType)
        {
        case DTM.CDATA_SECTION_NODE :
        case DTM.TEXT_NODE :
          ClonerToResultTree.cloneToResultTree(child, nodeType, 
                                        dtm, getResultTreeHandler(), false);
          break;
        case DTM.ATTRIBUTE_NODE :
          dtm.dispatchCharactersEvents(child, getResultTreeHandler(), false);
          break;
        }
      }
      else
      {

        // And execute the child templates.
        // 9/11/00: If template has been compiled, hand off to it
        // since much (most? all?) of the processing has been inlined.
        // (It would be nice if there was a single entry point that
        // worked for both... but the interpretive system works by
        // having the Tranformer execute the children, while the
        // compiled obviously has to run its own code. It's
        // also unclear that "execute" is really the right name for
        // that entry point.)
        m_xcontext.setSAXLocator(template);
        // m_xcontext.getVarStack().link();
        m_xcontext.getVarStack().link(template.m_frameSize);
        executeChildTemplates(template, true);
      }
    }
    catch (org.xml.sax.SAXException se)
    {
      throw new TransformerException(se);
    }
    finally
    {
      if (!isDefaultTextRule)
        m_xcontext.getVarStack().unlink();
      m_xcontext.popCurrentNode();
      if (!isApplyImports) {
          m_xcontext.popContextNodeList();
      }
      popCurrentMatched();
      
      popElemTemplateElement();
    }

    return true;
  }
  
  
  /**
   * Execute each of the children of a template element.  This method
   * is only for extension use.
   *
   * @param elem The ElemTemplateElement that contains the children
   * that should execute.
   * NEEDSDOC @param context
   * @param mode The current mode.
   * @param handler The ContentHandler to where the result events
   * should be fed.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public void executeChildTemplates(
          ElemTemplateElement elem, org.w3c.dom.Node context, QName mode, ContentHandler handler)
            throws TransformerException
  {

    XPathContext xctxt = m_xcontext;

    try
    {
      if(null != mode)
        pushMode(mode);
      xctxt.pushCurrentNode(xctxt.getDTMHandleFromNode(context));
      executeChildTemplates(elem, handler);
    }
    finally
    {
      xctxt.popCurrentNode();
      
      // I'm not sure where or why this was here.  It is clearly in 
      // error though, without a corresponding pushMode().
      if (null != mode)
        popMode();
    }
  }

  /**
   * Execute each of the children of a template element.
   *
   * @param elem The ElemTemplateElement that contains the children
   * that should execute.
   * @param shouldAddAttrs true if xsl:attributes should be executed.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public void executeChildTemplates(
          ElemTemplateElement elem, boolean shouldAddAttrs)
            throws TransformerException
  {

    // Does this element have any children?
    ElemTemplateElement t = elem.getFirstChildElem();

    if (null == t)
      return;      
    
    if(elem.hasTextLitOnly() && m_optimizer)
    {      
      char[] chars = ((ElemTextLiteral)t).getChars();
      try
      {
        // Have to push stuff on for tooling...
        this.pushElemTemplateElement(t);
        m_serializationHandler.characters(chars, 0, chars.length);
      }
      catch(SAXException se)
      {
        throw new TransformerException(se);
      }
      finally
      {
        this.popElemTemplateElement();
      }
      return;
    }

//    // Check for infinite loops if we have to.
//    boolean check = (m_stackGuard.m_recursionLimit > -1);
//
//    if (check)
//      getStackGuard().push(elem, xctxt.getCurrentNode());

    XPathContext xctxt = m_xcontext;
    xctxt.pushSAXLocatorNull();
    int currentTemplateElementsTop = m_currentTemplateElements.size();
    m_currentTemplateElements.push(null);

    try
    {
      // Loop through the children of the template, calling execute on 
      // each of them.
      for (; t != null; t = t.getNextSiblingElem())
      {
        if (!shouldAddAttrs
                && t.getXSLToken() == Constants.ELEMNAME_ATTRIBUTE)
          continue;

        xctxt.setSAXLocator(t);
        m_currentTemplateElements.setElementAt(t,currentTemplateElementsTop);
        t.execute(this);
      }
    }
    catch(RuntimeException re)
    {
    	TransformerException te = new TransformerException(re);
    	te.setLocator(t);
    	throw te;
    }
    finally
    {
      m_currentTemplateElements.pop();
      xctxt.popSAXLocator();
    }

    // Check for infinite loops if we have to
//    if (check)
//      getStackGuard().pop();
  }
    /**
      * Execute each of the children of a template element.
      *
      * @param elem The ElemTemplateElement that contains the children
      * that should execute.
      * @param handler The ContentHandler to where the result events
      * should be fed.
      *
      * @throws TransformerException
      * @xsl.usage advanced
      */
     public void executeChildTemplates(
             ElemTemplateElement elem, ContentHandler handler)
               throws TransformerException
     {

       SerializationHandler xoh = this.getSerializationHandler();

       // These may well not be the same!  In this case when calling
       // the Redirect extension, it has already set the ContentHandler
       // in the Transformer.
       SerializationHandler savedHandler = xoh;

       try
       {
         xoh.flushPending();

         // %REVIEW% Make sure current node is being pushed.
         LexicalHandler lex = null;
         if (handler instanceof LexicalHandler) {
            lex = (LexicalHandler) handler;
         }
         m_serializationHandler = new ToXMLSAXHandler(handler, lex, savedHandler.getEncoding());
         m_serializationHandler.setTransformer(this);
         executeChildTemplates(elem, true);
       }
       catch (TransformerException e)
       {
         throw e;
       }
       catch (SAXException se) {
       	 throw new TransformerException(se);
       }
       finally
       {
         m_serializationHandler = savedHandler;
    }
  }

  /**
   * Get the keys for the xsl:sort elements.
   * Note: Should this go into ElemForEach?
   *
   * @param foreach Valid ElemForEach element, not null.
   * @param sourceNodeContext The current node context in the source tree,
   * needed to evaluate the Attribute Value Templates.
   *
   * @return A Vector of NodeSortKeys, or null.
   *
   * @throws TransformerException
   * @xsl.usage advanced
   */
  public Vector processSortKeys(ElemForEach foreach, int sourceNodeContext)
          throws TransformerException
  {

    Vector keys = null;
    XPathContext xctxt = m_xcontext;
    int nElems = foreach.getSortElemCount();

    if (nElems > 0)
      keys = new Vector();

    // March backwards, collecting the sort keys.
    for (int i = 0; i < nElems; i++)
    {
      ElemSort sort = foreach.getSortElem(i);
      
      String langString =
        (null != sort.getLang())
        ? sort.getLang().evaluate(xctxt, sourceNodeContext, foreach) : null;
      String dataTypeString = sort.getDataType().evaluate(xctxt,
                                sourceNodeContext, foreach);

      if (dataTypeString.indexOf(":") >= 0)
        System.out.println(
          "TODO: Need to write the hooks for QNAME sort data type");
      else if (!(dataTypeString.equalsIgnoreCase(Constants.ATTRVAL_DATATYPE_TEXT))
               &&!(dataTypeString.equalsIgnoreCase(
                 Constants.ATTRVAL_DATATYPE_NUMBER)))
        foreach.error(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE,
                      new Object[]{ Constants.ATTRNAME_DATATYPE,
                                    dataTypeString });

      boolean treatAsNumbers =
        ((null != dataTypeString) && dataTypeString.equals(
        Constants.ATTRVAL_DATATYPE_NUMBER)) ? true : false;
      String orderString = sort.getOrder().evaluate(xctxt, sourceNodeContext,
                             foreach);

      if (!(orderString.equalsIgnoreCase(Constants.ATTRVAL_ORDER_ASCENDING))
              &&!(orderString.equalsIgnoreCase(
                Constants.ATTRVAL_ORDER_DESCENDING)))
        foreach.error(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE,
                      new Object[]{ Constants.ATTRNAME_ORDER,
                                    orderString });

      boolean descending =
        ((null != orderString) && orderString.equals(
        Constants.ATTRVAL_ORDER_DESCENDING)) ? true : false;
      AVT caseOrder = sort.getCaseOrder();
      boolean caseOrderUpper;

      if (null != caseOrder)
      {
        String caseOrderString = caseOrder.evaluate(xctxt, sourceNodeContext,
                                                    foreach);

        if (!(caseOrderString.equalsIgnoreCase(Constants.ATTRVAL_CASEORDER_UPPER))
                &&!(caseOrderString.equalsIgnoreCase(
                  Constants.ATTRVAL_CASEORDER_LOWER)))
          foreach.error(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE,
                        new Object[]{ Constants.ATTRNAME_CASEORDER,
                                      caseOrderString });

        caseOrderUpper =
          ((null != caseOrderString) && caseOrderString.equals(
          Constants.ATTRVAL_CASEORDER_UPPER)) ? true : false;
      }
      else
      {
        caseOrderUpper = false;
      }

      keys.addElement(new NodeSortKey(this, sort.getSelect(), treatAsNumbers,
                                      descending, langString, caseOrderUpper,
                                      foreach));
     }

    return keys;
  }

  //==========================================================
  // SECTION: TransformState implementation
  //==========================================================
  
  /**
   * Get the count of how many elements are 
   * active.
   * @return The number of active elements on 
   * the currentTemplateElements stack.
   */
  public int getCurrentTemplateElementsCount()
  {
  	return m_currentTemplateElements.size();
  }
  
  
  /**
   * Get the count of how many elements are 
   * active.
   * @return The number of active elements on 
   * the currentTemplateElements stack.
   */
  public ObjectStack getCurrentTemplateElements()
  {
  	return m_currentTemplateElements;
  }

  /**
   * Push the current template element.
   *
   * @param elem The current ElemTemplateElement (may be null, and then
   * set via setCurrentElement).
   */
  public void pushElemTemplateElement(ElemTemplateElement elem)
  {
    m_currentTemplateElements.push(elem);
  }

  /**
   * Pop the current template element.
   */
  public void popElemTemplateElement()
  {
    m_currentTemplateElements.pop();
  }

  /**
   * Set the top of the current template elements
   * stack.
   *
   * @param e The current ElemTemplateElement about to
   * be executed.
   */
  public void setCurrentElement(ElemTemplateElement e)
  {
    m_currentTemplateElements.setTop(e);
  }

  /**
   * Retrieves the current ElemTemplateElement that is
   * being executed.
   *
   * @return The current ElemTemplateElement that is executing,
   * should not normally be null.
   */
  public ElemTemplateElement getCurrentElement()
  {
    return (m_currentTemplateElements.size() > 0) ? 
        (ElemTemplateElement) m_currentTemplateElements.peek() : null;
  }

  /**
   * This method retrieves the current context node
   * in the source tree.
   *
   * @return The current context node (should never be null?).
   */
  public int getCurrentNode()
  {
    return m_xcontext.getCurrentNode();
  }
  
  /**
   * This method retrieves the xsl:template
   * that is in effect, which may be a matched template
   * or a named template.
   *
   * <p>Please note that the ElemTemplate returned may
   * be a default template, and thus may not have a template
   * defined in the stylesheet.</p>
   *
   * @return The current xsl:template, should not be null.
   */
  public ElemTemplate getCurrentTemplate()
  {

    ElemTemplateElement elem = getCurrentElement();

    while ((null != elem)
           && (elem.getXSLToken() != Constants.ELEMNAME_TEMPLATE))
    {
      elem = elem.getParentElem();
    }

    return (ElemTemplate) elem;
  }

  /**
   * Push both the current xsl:template or xsl:for-each onto the
   * stack, along with the child node that was matched.
   * (Note: should this only be used for xsl:templates?? -sb)
   *
   * @param template xsl:template or xsl:for-each.
   * @param child The child that was matched.
   */
  public void pushPairCurrentMatched(ElemTemplateElement template, int child)
  {
    m_currentMatchTemplates.push(template);
    m_currentMatchedNodes.push(child);
  }

  /**
   * Pop the elements that were pushed via pushPairCurrentMatched.
   */
  public void popCurrentMatched()
  {
    m_currentMatchTemplates.pop();
    m_currentMatchedNodes.pop();
  }

  /**
   * This method retrieves the xsl:template
   * that was matched.  Note that this may not be
   * the same thing as the current template (which
   * may be from getCurrentElement()), since a named
   * template may be in effect.
   *
   * @return The pushed template that was pushed via pushPairCurrentMatched.
   */
  public ElemTemplate getMatchedTemplate()
  {
    return (ElemTemplate) m_currentMatchTemplates.peek();
  }

  /**
   * Retrieves the node in the source tree that matched
   * the template obtained via getMatchedTemplate().
   *
   * @return The matched node that corresponds to the
   * match attribute of the current xsl:template.
   */
  public int getMatchedNode()
  {
    return m_currentMatchedNodes.peepTail();
  }

  /**
   * Get the current context node list.
   *
   * @return A reset clone of the context node list.
   */
  public DTMIterator getContextNodeList()
  {

    try
    {
      DTMIterator cnl = m_xcontext.getContextNodeList();

      return (cnl == null) ? null : (DTMIterator) cnl.cloneWithReset();
    }
    catch (CloneNotSupportedException cnse)
    {

      // should never happen.
      return null;
    }
  }

  /**
   * Get the TrAX Transformer object in effect.
   *
   * @return This object.
   */
  public Transformer getTransformer()
  {
    return this;
  }

  //==========================================================
  // SECTION: Accessor Functions
  //==========================================================

  /**
   * Set the stylesheet for this processor.  If this is set, then the
   * process calls that take only the input .xml will use
   * this instead of looking for a stylesheet PI.  Also,
   * setting the stylesheet is needed if you are going
   * to use the processor as a SAX ContentHandler.
   *
   * @param stylesheetRoot A non-null StylesheetRoot object,
   * or null if you wish to clear the stylesheet reference.
   */
  public void setStylesheet(StylesheetRoot stylesheetRoot)
  {
    m_stylesheetRoot = stylesheetRoot;
  }

  /**
   * Get the current stylesheet for this processor.
   *
   * @return The stylesheet that is associated with this
   * transformer.
   */
  public final StylesheetRoot getStylesheet()
  {
    return m_stylesheetRoot;
  }

  /**
   * Get quietConflictWarnings property. If the quietConflictWarnings
   * property is set to true, warnings about pattern conflicts won't be
   * printed to the diagnostics stream.
   *
   * @return True if this transformer should not report
   * template match conflicts.
   */
  public boolean getQuietConflictWarnings()
  {
    return m_quietConflictWarnings;
  }

  /**
   * Set the execution context for XPath.
   *
   * @param xcontext A non-null reference to the XPathContext
   * associated with this transformer.
   * @xsl.usage internal
   */
  public void setXPathContext(XPathContext xcontext)
  {
    m_xcontext = xcontext;
  }

  /**
   * Get the XPath context associated with this transformer.
   *
   * @return The XPathContext reference, never null.
   */
  public final XPathContext getXPathContext()
  {
    return m_xcontext;
  }

  /**
   * Get the SerializationHandler object.
   *
   * @return The current SerializationHandler, which may not
   * be the main result tree manager.
   */
  public SerializationHandler getResultTreeHandler()
  {
    return m_serializationHandler;
  }

  /**
   * Get the SerializationHandler object.
   *
   * @return The current SerializationHandler, which may not
   * be the main result tree manager.
   */
  public SerializationHandler getSerializationHandler()
  {
    return m_serializationHandler;
  }
  
  /**
   * Get the KeyManager object.
   *
   * @return A reference to the KeyManager object, which should
   * never be null.
   */
  public KeyManager getKeyManager()
  {
    return m_keyManager;
  }

  /**
   * Check to see if this is a recursive attribute definition.
   *
   * @param attrSet A non-null ElemAttributeSet reference.
   *
   * @return true if the attribute set is recursive.
   */
  public boolean isRecursiveAttrSet(ElemAttributeSet attrSet)
  {

    if (null == m_attrSetStack)
    {
      m_attrSetStack = new Stack();
    }

    if (!m_attrSetStack.empty())
    {
      int loc = m_attrSetStack.search(attrSet);

      if (loc > -1)
      {
        return true;
      }
    }

    return false;
  }

  /**
   * Push an executing attribute set, so we can check for
   * recursive attribute definitions.
   *
   * @param attrSet A non-null ElemAttributeSet reference.
   */
  public void pushElemAttributeSet(ElemAttributeSet attrSet)
  {
    m_attrSetStack.push(attrSet);
  }

  /**
   * Pop the current executing attribute set.
   */
  public void popElemAttributeSet()
  {
    m_attrSetStack.pop();
  }

  /**
   * Get the table of counters, for optimized xsl:number support.
   *
   * @return The CountersTable, never null.
   */
  public CountersTable getCountersTable()
  {

    if (null == m_countersTable)
      m_countersTable = new CountersTable();

    return m_countersTable;
  }

  /**
   * Tell if the current template rule is null, i.e. if we are
   * directly within an apply-templates.  Used for xsl:apply-imports.
   *
   * @return True if the current template rule is null.
   */
  public boolean currentTemplateRuleIsNull()
  {
    return ((!m_currentTemplateRuleIsNull.isEmpty())
            && (m_currentTemplateRuleIsNull.peek() == true));
  }

  /**
   * Push true if the current template rule is null, false
   * otherwise.
   *
   * @param b True if the we are executing an xsl:for-each
   * (or xsl:call-template?).
   */
  public void pushCurrentTemplateRuleIsNull(boolean b)
  {
    m_currentTemplateRuleIsNull.push(b);
  }

  /**
   * Push true if the current template rule is null, false
   * otherwise.
   */
  public void popCurrentTemplateRuleIsNull()
  {
    m_currentTemplateRuleIsNull.pop();
  }

  /**
   * Push a funcion result for the currently active EXSLT
   * <code>func:function</code>.
   * 
   * @param val the result of executing an EXSLT
   * <code>func:result</code> instruction for the current
   * <code>func:function</code>.
   */
  public void pushCurrentFuncResult(Object val) {
    m_currentFuncResult.push(val);
  }

  /**
   * Pops the result of the currently active EXSLT <code>func:function</code>.
   * 
   * @return the value of the <code>func:function</code>
   */
  public Object popCurrentFuncResult() {
    return m_currentFuncResult.pop();
  }

  /**
   * Determines whether an EXSLT <code>func:result</code> instruction has been
   * executed for the currently active EXSLT <code>func:function</code>.
   * 
   * @return <code>true</code> if and only if a <code>func:result</code>
   * instruction has been executed
   */
  public boolean currentFuncResultSeen() {
    return !m_currentFuncResult.empty()
               && m_currentFuncResult.peek() != null;
  }

  /**
   * Return the message manager.
   *
   * @return The message manager, never null.
   */
  public MsgMgr getMsgMgr()
  {

    if (null == m_msgMgr)
      m_msgMgr = new MsgMgr(this);

    return m_msgMgr;
  }

  /**
   * Set the error event listener.
   *
   * @param listener The new error listener.
   * @throws IllegalArgumentException if
   */
  public void setErrorListener(ErrorListener listener)
          throws IllegalArgumentException
  {

    synchronized (m_reentryGuard)
    {
      if (listener == null)
        throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_NULL_ERROR_HANDLER, null)); //"Null error handler");

      m_errorHandler = listener;
    }
  }

  /**
   * Get the current error event handler.
   *
   * @return The current error handler, which should never be null.
   */
  public ErrorListener getErrorListener()
  {
    return m_errorHandler;
  }

  /**
   * Look up the value of a feature.
   *
   * <p>The feature name is any fully-qualified URI.  It is
   * possible for an TransformerFactory to recognize a feature name but
   * to be unable to return its value; this is especially true
   * in the case of an adapter for a SAX1 Parser, which has
   * no way of knowing whether the underlying parser is
   * validating, for example.</p>
   *
   * <h3>Open issues:</h3>
   * <dl>
   *    <dt><h4>Should getFeature be changed to hasFeature?</h4></dt>
   *    <dd>Keith Visco writes: Should getFeature be changed to hasFeature?
   *        It returns a boolean which indicated whether the "state"
   *        of feature is "true or false". I assume this means whether
   *        or not a feature is supported? I know SAX is using "getFeature",
   *        but to me "hasFeature" is cleaner.</dd>
   * </dl>
   *
   * @param name The feature name, which is a fully-qualified
   *        URI.
   * @return The current state of the feature (true or false).
   * @throws org.xml.sax.SAXNotRecognizedException When the
   *            TransformerFactory does not recognize the feature name.
   * @throws org.xml.sax.SAXNotSupportedException When the
   *            TransformerFactory recognizes the feature name but
   *            cannot determine its value at this time.
   *
   * @throws SAXNotRecognizedException
   * @throws SAXNotSupportedException
   */
  public boolean getFeature(String name)
          throws SAXNotRecognizedException, SAXNotSupportedException
  {

    if ("http://xml.org/trax/features/sax/input".equals(name))
      return true;
    else if ("http://xml.org/trax/features/dom/input".equals(name))
      return true;

    throw new SAXNotRecognizedException(name);
  }

  // %TODO% Doc

  /**
   * NEEDSDOC Method getMode 
   *
   *
   * NEEDSDOC (getMode) @return
   */
  public QName getMode()
  {
    return m_modes.isEmpty() ? null : (QName) m_modes.peek();
  }

  // %TODO% Doc

  /**
   * NEEDSDOC Method pushMode 
   *
   *
   * NEEDSDOC @param mode
   */
  public void pushMode(QName mode)
  {
    m_modes.push(mode);
  }

  // %TODO% Doc

  /**
   * NEEDSDOC Method popMode 
   *
   */
  public void popMode()
  {
    m_modes.pop();
  }

  /**
   * Called by SourceTreeHandler to start the transformation
   *  in a separate thread
   *
   * NEEDSDOC @param priority
   */
  public void runTransformThread(int priority)
  {

    // used in SourceTreeHandler
    Thread t = ThreadControllerWrapper.runThread(this, priority);
    this.setTransformThread(t);
  }

  /**
   * Called by this.transform() if isParserEventsOnMain()==false.
   *  Similar with runTransformThread(), but no priority is set
   *  and setTransformThread is not set.
   */
  public void runTransformThread()
  {
    ThreadControllerWrapper.runThread(this, -1);
  }
  
  /**
   * Called by CoRoutineSAXParser. Launches the CoroutineSAXParser
   * in a thread, and prepares it to invoke the parser from that thread
   * upon request. 
   *  
   */
  public static void runTransformThread(Runnable runnable)
  {
    ThreadControllerWrapper.runThread(runnable, -1);
  }

  /**
   * Used by SourceTreeHandler to wait until the transform
   *   completes
   *
   * @throws SAXException
   */
  public void waitTransformThread() throws SAXException
  {

    // This is called to make sure the task is done.
    // It is possible that the thread has been reused -
    // but for a different transformation. ( what if we 
    // recycle the transformer ? Not a problem since this is
    // still in use. )
    Thread transformThread = this.getTransformThread();

    if (null != transformThread)
    {
      try
      {
        ThreadControllerWrapper.waitThread(transformThread, this);

        if (!this.hasTransformThreadErrorCatcher())
        {
          Exception e = this.getExceptionThrown();

          if (null != e)
          {
            e.printStackTrace();
            throw new org.xml.sax.SAXException(e);
          }
        }

        this.setTransformThread(null);
      }
      catch (InterruptedException ie){}
    }
  }

  /**
   * Get the exception thrown by the secondary thread (normally
   * the transform thread).
   *
   * @return The thrown exception, or null if no exception was
   * thrown.
   */
  public Exception getExceptionThrown()
  {
    return m_exceptionThrown;
  }

  /**
   * Set the exception thrown by the secondary thread (normally
   * the transform thread).
   *
   * @param e The thrown exception, or null if no exception was
   * thrown.
   */
  public void setExceptionThrown(Exception e)
  {
    m_exceptionThrown = e;
  }

  /**
   * This is just a way to set the document for run().
   *
   * @param doc A non-null reference to the root of the
   * tree to be transformed.
   */
  public void setSourceTreeDocForThread(int doc)
  {
    m_doc = doc;
  }

  /**
   * From a secondary thread, post the exception, so that
   * it can be picked up from the main thread.
   *
   * @param e The exception that was thrown.
   */
  void postExceptionFromThread(Exception e)
  {

    // Commented out in response to problem reported by Nicola Brown <Nicola.Brown@jacobsrimell.com>
    //    if(m_reportInPostExceptionFromThread)
    //    {
    //      // Consider re-throwing the exception if this flag is set.
    //      e.printStackTrace();
    //    }
    // %REVIEW Need DTM equivelent?    
    //    if (m_inputContentHandler instanceof SourceTreeHandler)
    //    {
    //      SourceTreeHandler sth = (SourceTreeHandler) m_inputContentHandler;
    //
    //      sth.setExceptionThrown(e);
    //    }
 //   ContentHandler ch = getContentHandler();

    //    if(ch instanceof SourceTreeHandler)
    //    {
    //      SourceTreeHandler sth = (SourceTreeHandler) ch;
    //      ((TransformerImpl)(sth.getTransformer())).postExceptionFromThread(e);
    //    }
    // m_isTransformDone = true; // android-removed
    m_exceptionThrown = e;
    ;  // should have already been reported via the error handler?

    synchronized (this)
    {

      // See message from me on 3/27/2001 to Patrick Moore.
      //      String msg = e.getMessage();
      // System.out.println(e.getMessage());
      // Is this really needed?  -sb
      notifyAll();

      //      if (null == msg)
      //      {
      //
      //        // m_throwNewError = false;
      //        e.printStackTrace();
      //      }
      // throw new org.apache.xml.utils.WrappedRuntimeException(e);
    }
  }

  /**
   * Run the transform thread.
   */
  public void run()
  {

    m_hasBeenReset = false;

    try
    {

      // int n = ((SourceTreeHandler)getInputContentHandler()).getDTMRoot();
      // transformNode(n);
      try
      {
        // m_isTransformDone = false; // android-removed
        
        // Should no longer be needed...
//          if(m_inputContentHandler instanceof TransformerHandlerImpl)
//          {
//            TransformerHandlerImpl thi = (TransformerHandlerImpl)m_inputContentHandler;
//            thi.waitForInitialEvents();
//          }

        transformNode(m_doc);
        
      }
      catch (Exception e)
      {
        // e.printStackTrace();

        // Strange that the other catch won't catch this...
        if (null != m_transformThread)
          postExceptionFromThread(e);   // Assume we're on the main thread
        else 
          throw new RuntimeException(e.getMessage());
      }
      finally
      {
        // m_isTransformDone = true; // android-removed

        if (m_inputContentHandler instanceof TransformerHandlerImpl)
        {
          ((TransformerHandlerImpl) m_inputContentHandler).clearCoRoutine();
        }

        //        synchronized (this)
        //        {
        //          notifyAll();
        //        }
      }
    }
    catch (Exception e)
    {

      // e.printStackTrace();
      if (null != m_transformThread)
        postExceptionFromThread(e);
      else 
        throw new RuntimeException(e.getMessage());         // Assume we're on the main thread.
    }
  }

  // Fragment re-execution interfaces for a tool.

  /**
   * Test whether whitespace-only text nodes are visible in the logical
   * view of <code>DTM</code>. Normally, this function
   * will be called by the implementation of <code>DTM</code>;
   * it is not normally called directly from
   * user code.
   *
   * @param elementHandle int Handle of the element.
   * @return one of NOTSTRIP, STRIP, or INHERIT.
   */
  public short getShouldStripSpace(int elementHandle, DTM dtm)
  {

    try
    {
      org.apache.xalan.templates.WhiteSpaceInfo info =
        m_stylesheetRoot.getWhiteSpaceInfo(m_xcontext, elementHandle, dtm);

      if (null == info)
      {
        return DTMWSFilter.INHERIT;
      }
      else
      {

        // System.out.println("getShouldStripSpace: "+info.getShouldStripSpace());
        return info.getShouldStripSpace()
               ? DTMWSFilter.STRIP : DTMWSFilter.NOTSTRIP;
      }
    }
    catch (TransformerException se)
    {
      return DTMWSFilter.INHERIT;
    }
  }
  /**
   * Initializer method.
   *
   * @param transformer non-null transformer instance
   * @param realHandler Content Handler instance
   */
   public void init(ToXMLSAXHandler h,Transformer transformer, ContentHandler realHandler)
   {
      h.setTransformer(transformer);
      h.setContentHandler(realHandler);
   }
      
   public void setSerializationHandler(SerializationHandler xoh)
   {
      m_serializationHandler = xoh;
   }
   
   
     
	/**
	 * Fire off characters, cdate events.
	 * @see org.apache.xml.serializer.SerializerTrace#fireGenerateEvent(int, char[], int, int)
	 */
	public void fireGenerateEvent(
		int eventType,
		char[] ch,
		int start,
		int length) {
	}

	/**
	 * Fire off startElement, endElement events.
	 * @see org.apache.xml.serializer.SerializerTrace#fireGenerateEvent(int, String, Attributes)
	 */
	public void fireGenerateEvent(
		int eventType,
		String name,
		Attributes atts) {
	}

	/**
	 * Fire off processingInstruction events.
	 */
	public void fireGenerateEvent(int eventType, String name, String data) {
	}

	/**
	 * Fire off comment and entity ref events.
	 * @see org.apache.xml.serializer.SerializerTrace#fireGenerateEvent(int, String)
	 */
	public void fireGenerateEvent(int eventType, String data) {
	}

	/**
	 * Fire off startDocument, endDocument events.
	 * @see org.apache.xml.serializer.SerializerTrace#fireGenerateEvent(int)
	 */
	public void fireGenerateEvent(int eventType) {
	}

    /**
     * @see org.apache.xml.serializer.SerializerTrace#hasTraceListeners()
     */
    public boolean hasTraceListeners() {
        return false;
    }

    /**
     * @return Incremental flag
     */
    public boolean getIncremental() {
        return m_incremental;
    }

    /**
     * @return Optimization flag
     */
    public boolean getOptimize() {
        return m_optimizer;
    }

    /**
     * @return Source location flag
     */
    public boolean getSource_location() {
        return m_source_location;
    }

}  // end TransformerImpl class

