/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.xalan.xslt;

import java.util.*;
import java.io.*;
import java.net.*;

import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xalan.xpath.xml.QName;

import org.apache.xalan.xpath.ExtensionFunctionHandler;
import org.apache.xalan.xpath.XPathProcessorException;

/**
 * <meta name="usage" content="advanced"/>
 * Represents an extension namespace. Provides functions
 * to call into the extension via both element syntax and function
 * syntax. Extends XPath's extension function capability to a full
 * namespace extension model.
 *
 * @author Sanjiva Weerawarana (sanjiva@watson.ibm.com)
 */
public class ExtensionNSHandler extends ExtensionFunctionHandler
{
  XSLTEngineImpl xslp;        // xsl processor for whom I'm working
  Hashtable elements = new Hashtable (); // ext. elements of this namespace
  boolean componentDescLoaded; // true when info from the component desc
  // has been loaded. This gets set as soon
  // as any of the info has been specified.
  // If this is false, when processElement or
  // processFunction is called it will use the
  // namespace URI as a URL and try to load
  // that location as the component desc.

  /////////////////////////////////////////////////////////////////////////
  // Constructors
  /////////////////////////////////////////////////////////////////////////

  /**
   * Construct a new extension namespace handler for a given extension NS.
   * This doesn't do anything - just hang on to the namespace URI.
   *
   * @param xslp         handle to the XSL processor that I'm working for
   * @param namespaceUri the extension namespace URI that I'm implementing
   */
  public ExtensionNSHandler (XSLTEngineImpl xslp, String namespaceUri)
  {
    super (namespaceUri);
    this.xslp = xslp;
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Construct a new extension namespace handler given all the information
   * needed.
   *
   * @param xslp         handle to the XSL processor that I'm working for
   * @param namespaceUri the extension namespace URI that I'm implementing
   * @param elemNames    string containing list of elements of extension NS
   * @param funcNames    string containing list of functions of extension NS
   * @param lang         language of code implementing the extension
   * @param srcURL       value of src attribute (if any) - treated as a URL
   *                     or a classname depending on the value of lang. If
   *                     srcURL is not null, then scriptSrc is ignored.
   * @param scriptSrc    the actual script code (if any)
   */
  public ExtensionNSHandler (XSLTEngineImpl xslp, String namespaceUri,
                             String elemNames, String funcNames,
                             String lang, String srcURL, String src)
  {
    super (namespaceUri, funcNames, lang, srcURL, src);
    this.xslp = xslp;
    setElements (elemNames);
    componentDescLoaded = true;
  }

  /////////////////////////////////////////////////////////////////////////
  // Main API
  /////////////////////////////////////////////////////////////////////////

  /*
  * Set function local parts of extension NS. Super does the work; I
  * just record that a component desc has been loaded.
  *
  * @param functions whitespace separated list of function names defined
  *        by this extension namespace.
  */
  public void setFunctions (String funcNames)
  {
    super.setFunctions (funcNames);
    componentDescLoaded = true;
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Set the script data for this extension NS. Deferred to super for
   * actual work - I only record that a component desc has been loaded.
   *
   * @param lang      language of the script.
   * @param srcURL    value of src attribute (if any) - treated as a URL
   *                  or a classname depending on the value of lang. If
   *                  srcURL is not null, then scriptSrc is ignored.
   * @param scriptSrc the actual script code (if any)
   */
  public void setScript (String lang, String srcURL, String scriptSrc)
  {
    super.setScript (lang, srcURL, scriptSrc);
    componentDescLoaded = true;
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Set element local parts of extension NS.
   *
   * @param elemNames whitespace separated list of element names defined
   *        by this extension namespace.
   */
  public void setElements (String elemNames)
  {
    if (elemNames == null)
    {
      return;
    }
    StringTokenizer st = new StringTokenizer (elemNames, " \t\n\r", false);
    Object junk = new Object ();
    while (st.hasMoreTokens ())
    {
      String tok = st.nextToken ();
      elements.put (tok, junk); // just stick it in there basically
    }
    componentDescLoaded = true;
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Tests whether a certain element name is known within this namespace.
   *
   * @param element name of the element being tested
   *
   * @return true if its known, false if not.
   */
  public boolean isElementAvailable (String element)
  {
    return (elements.get (element) != null);
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Process a call to this extension namespace via an element. As a side
   * effect, the results are sent to the XSLTEngineImpl's result tree.
   *
   * @param localPart      Element name's local part.
   * @param element        The extension element being processed.
   * @param processor      Handle to XSLTEngineImpl.
   * @param stylesheetTree The compiled stylesheet tree.
   * @param mode           The current mode.
   * @param sourceTree     The root of the source tree (but don't assume
   *                       it's a Document).
   * @param sourceNode     The current context node.
   *
   * @exception XSLProcessorException thrown if something goes wrong
   *            while running the extension handler.
   * @exception MalformedURLException if loading trouble
   * @exception FileNotFoundException if loading trouble
   * @exception IOException           if loading trouble
   * @exception SAXException          if parsing trouble
   */
  public void processElement (String localPart, Element element,
                              XSLTEngineImpl processor,
                              Stylesheet stylesheetTree,
                              Node sourceTree, Node sourceNode, QName mode,
                              Class classObj, Object methodKey)
    throws XSLProcessorException,
           MalformedURLException,
           FileNotFoundException,
           IOException,
           SAXException
  {
    if (!componentStarted)
    {
      try
      {
        startupComponent (classObj);
      }
      catch (XPathProcessorException e)
      {
        // e.printStackTrace ();
        throw new XSLProcessorException (e.getMessage (), e);
      }
    }

    Object result = null;
    try
    {
      XSLProcessorContext xpc =
                               new XSLProcessorContext (processor, stylesheetTree,
                                                        sourceTree, sourceNode, mode);
      Vector argv = new Vector (2);
      argv.addElement (xpc);
      argv.addElement (element);
      result = super.callFunction (localPart, argv, methodKey, classObj);
    }
    catch (XPathProcessorException e)
    {
      // e.printStackTrace ();
      throw new XSLProcessorException (e.getMessage (), e);
    }

    if (result != null)
    {
      processor.outputToResultTree (stylesheetTree, result);
    }
  }

  /////////////////////////////////////////////////////////////////////////
  // Private/Protected Functions
  /////////////////////////////////////////////////////////////////////////

  /**
   * Start the component up by executing any script that needs to run
   * at startup time. This needs to happen before any functions can be
   * called on the component.
   *
   * @exception XPathProcessorException if something bad happens.
   */
  protected void startupComponent (Class classObj) throws  XPathProcessorException
  {
		/**
	   * Patch from Costin Manolache
	   *
    if(!bsfInitialized)
    {
      synchronized(m_bsfInitSynch)
      {
        bsfInitialized = true;
        com.ibm.bsf.BSFManager.registerScriptingEngine ("xslt-javaclass",
                                                        "org.apache.xalan.xpath.XSLTJavaClassEngine",
                                                        new String[0]);
      }
    }*/
		extensionLiaison.checkInit();

    if (!componentDescLoaded)
    {
      try
      {
        loadComponentDescription ();
      }
      catch (Exception e)
      {
        throw new XPathProcessorException (e.getMessage (), e);
      }
    }
    super.startupComponent (classObj);
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * Load the component spec for this extension namespace taking the URI
   * of this namespace as the URL to read from.
   *
   * @exception XSLProcessorException if something bad happens.
   * @exception MalformedURLException if loading trouble
   * @exception FileNotFoundException if loading trouble
   * @exception IOException           if loading trouble
   * @exception SAXException          if parsing trouble
   */
  private void loadComponentDescription ()
    throws  XSLProcessorException,
    MalformedURLException,
    FileNotFoundException,
    IOException,
    SAXException
  {
    // first try treaing the URI of the extension as a fully qualified
    // class name; if it works then go with treating this an extension
    // implemented in "javaclass" for with that class being the srcURL.
    // forget about setting elements and functions in that case - so if
    // u do extension-{element,function}-available then u get false,
    // but that's ok.
    try {
      String cname = namespaceUri.startsWith ("class:") ?
                       namespaceUri.substring (6) : namespaceUri;
      Class.forName (cname); // does it load?
      setScript ("javaclass", namespaceUri, null);
      componentDescLoaded = true;
      return;
    } catch (Exception e) {
      // oops, it failed .. ok, so this path ain't gonna pan out. shucks.
    }

    // parse the document at the URI of the extension, if any
    URL url = xslp.getURLFromString(namespaceUri,
                                    xslp.m_stylesheetRoot.m_baseIdent);
    // System.out.println("Extension URI: "+url.toString());
    org.apache.xalan.xpath.xml.XMLParserLiaison liaison = xslp.m_parserLiaison;
    // Patch from Costin Manolache
    if("org.apache.xalan.xpath.dtm.DTMLiaison".equals( liaison.getClass().getName()))
    {
      // There ought to be a better way.  At least if we know we
      // have the DTM, we ought to have Xerces.
      liaison = xslp.createXercesLiaison(); 
      liaison.setErrorHandler(new ExtErrorHandler(url.toString()));
    }
    Element componentElement = null;
    /*
    try
    {
    */
    liaison.parse(new InputSource(url.toString()));
    Document compSpec = liaison.getDocument();
    componentElement = compSpec.getDocumentElement ();

    // determine the functions and elements of this component
    setElements (componentElement.getAttribute ("elements"));
    setFunctions (componentElement.getAttribute ("functions"));

    // is there an lxslt:script element child? [NOTE THAT THIS IS NOT
    // PROPER NAMESPACE-WISE .. I'll FIX IT LATER. .. Sanjiva 8/20/99.]
    NodeList nl = componentElement.getElementsByTagName ("lxslt:script");
    switch (nl.getLength ())
    {
    case 0:
      break;
    case 1:
      Element scriptElem = (Element) nl.item (0);
      String lang = scriptElem.getAttribute ("lang");
      Attr srcURLAttr = scriptElem.getAttributeNode ("src");
      String srcURL = (srcURLAttr == null) ? null : srcURLAttr.getValue ();
      String src = getScriptString (scriptElem);
      setScript (lang, srcURL, src);
      break;
    default:
      throw new XSLProcessorException ("too many <script>s in component");
    }
    componentDescLoaded = true;
    /*
    }
    catch(org.xml.sax.SAXException se)
    {

    }
    catch(java.net.UnknownHostException uhe)
    {

    }
    */
  }

  /////////////////////////////////////////////////////////////////////////

  /**
   * extract the text nodes and CDATA content children of the given
   * elem and return as a string. Any other types of node children
   * are ignored
   *
   * @param elem element whose text and cdata children are to be
   *        concatenated together.
   *
   * @return string resulting from concatanating the text/cdata child
   *         nodes' values.
   */
  private String getScriptString (Element elem)
  {
    StringBuffer strBuf = new StringBuffer();
    for (Node n = elem.getFirstChild (); n != null; n = n.getNextSibling ())
    {
      switch (n.getNodeType())
      {
      case Node.TEXT_NODE:
      case Node.CDATA_SECTION_NODE:
        strBuf.append(((CharacterData) n).getData());
        break;
      default:
        break;
      }
    }
    return strBuf.toString();
  }


  /**
   * Implement SAX error handler for default reporting.
   */
  class ExtErrorHandler implements ErrorHandler
  {
    String m_xmlID = null;

    public ExtErrorHandler(String identifier)
    {
      m_xmlID = identifier;
    }

    /**
     * Receive notification of a warning.
     *
     * <p>SAX parsers will use this method to report conditions that
     * are not errors or fatal errors as defined by the XML 1.0
     * recommendation.  The default behaviour is to take no action.</p>
     *
     * <p>The SAX parser must continue to provide normal parsing events
     * after invoking this method: it should still be possible for the
     * application to process the document through to the end.</p>
     *
     * @param exception The warning information encapsulated in a
     *                  SAX parse exception.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.SAXParseException
     */
    public void warning (SAXParseException exception)
      throws SAXException
    {
      System.out.println("Parser warning: "+exception.getMessage());
    }


    /**
     * Receive notification of a recoverable error.
     *
     * <p>This corresponds to the definition of "error" in section 1.2
     * of the W3C XML 1.0 Recommendation.  For example, a validating
     * parser would use this callback to report the violation of a
     * validity constraint.  The default behaviour is to take no
     * action.</p>
     *
     * <p>The SAX parser must continue to provide normal parsing events
     * after invoking this method: it should still be possible for the
     * application to process the document through to the end.  If the
     * application cannot do so, then the parser should report a fatal
     * error even if the XML 1.0 recommendation does not require it to
     * do so.</p>
     *
     * @param exception The error information encapsulated in a
     *                  SAX parse exception.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.SAXParseException
     */
    public void error (SAXParseException exception)
      throws SAXException
    {
      System.out.println("Parser error: "+exception.getMessage());
    }

    /**
     * Receive notification of a non-recoverable error.
     *
     * <p>This corresponds to the definition of "fatal error" in
     * section 1.2 of the W3C XML 1.0 Recommendation.  For example, a
     * parser would use this callback to report the violation of a
     * well-formedness constraint.</p>
     *
     * <p>The application must assume that the document is unusable
     * after the parser has invoked this method, and should continue
     * (if at all) only for the sake of collecting addition error
     * messages: in fact, SAX parsers are free to stop reporting any
     * other events once this method has been invoked.</p>
     *
     * @param exception The error information encapsulated in a
     *                  SAX parse exception.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.SAXParseException
     */
    public void fatalError (SAXParseException exception)
      throws SAXException
    {
    }

  }

}
