/*
 * SAXNullTransformerFactory.java
 * Copyright (C) 2001 The Free Software Foundation
 * 
 * This file is part of GNU JAXP, a library.
 *
 * GNU JAXP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * GNU JAXP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Linking this library statically or dynamically with other modules is
 * making a combined work based on this library.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole
 * combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on this library.  If you modify this library, you may extend
 * this exception to your version of the library, but you are not
 * obliged to do so.  If you do not wish to do so, delete this
 * exception statement from your version. 
 */

package gnu.xml.util;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
import java.util.Properties;

import gnu.xml.dom.Consumer;
import gnu.xml.dom.DomDocument;
import gnu.xml.pipeline.DomConsumer;
import gnu.xml.pipeline.EventFilter;

import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;

import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.LocatorImpl;


/**
 * Implements null transforms. XSLT stylesheets are not supported.
 * This class provides a way to translate three representations of
 * XML data (SAX event stream, DOM tree, and XML text) into each other.
 * In essence it's a thinnish wrapper around basic SAX event
 * <a href="../pipeline/package-summary.html">pipeline</a> facilities, which
 * exposes only limited functionality.  The <em>javax.xml.transform</em>
 * functionality is implemented as follows: <ul>
 *
 * <li>The {@link javax.xml.transform.sax.SAXSource SAXSource} class
 * just wraps an {@link XMLReader} and {@link InputSource}, while the
 * {@link javax.xml.transform.sax.SAXResult SAXResult} class is less
 * functional than a {@link gnu.xml.pipeline.EventConsumer EventConsumer}.
 * (Notably, it drops all but one declaration from any DTD.)</li>
 *
 * <li>The {@link javax.xml.transform.dom.DOMSource DOMSource} class
 * corresponds to special SAX parsers like {@link DomParser}, and the
 * {@link javax.xml.transform.dom.DOMResult DOMResult} class corresponds
 * to a {@link gnu.xml.pipeline.DomConsumer DomConsumer}.</li>
 *
 * <li>The {@link javax.xml.transform.stream.StreamSource StreamSource}
 * class corresponds to a SAX {@link InputSource}, and the
 * {@link javax.xml.transform.stream.StreamResult StreamResult} class
 * corresponds to a {@link gnu.xml.pipeline.TextConsumer TextConsumer}.</li>
 *
 * </ul>
 *
 * <p><em>This implementation is preliminary.</em>
 *
 * @see gnu.xml.pipeline.XsltFilter
 *
 * @author David Brownell
 */
public class SAXNullTransformerFactory extends SAXTransformerFactory
{
  
  private ErrorListener	errListener;
  private URIResolver		uriResolver;
  
  /** Default constructor */
  public SAXNullTransformerFactory () { }
  
  //
  // only has stuff that makes sense with null transforms
  //
  
  /**
   * Returns true if the requested feature is supported.
   * All three kinds of input and output are accepted:
   * XML text, SAX events, and DOM nodes.
   */
  public boolean getFeature (String feature)
  {
    return SAXTransformerFactory.FEATURE.equals (feature)
      || SAXResult.FEATURE.equals (feature)
      || SAXSource.FEATURE.equals (feature)
      || DOMResult.FEATURE.equals (feature)
      || DOMSource.FEATURE.equals (feature)
      || StreamResult.FEATURE.equals (feature)
      || StreamSource.FEATURE.equals (feature)
      ;
  }

  public void setFeature(String name, boolean value)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException(name);
  }


  /** Throws an exception (no implementation attributes are supported) */
  public void setAttribute (String key, Object value)
  {
    throw new IllegalArgumentException ();
  }

  /** Throws an exception (no implementation attributes are supported) */
  public Object getAttribute (String key)
  {
    throw new IllegalArgumentException ();
  }

  /** (not yet implemented) */
  public Source getAssociatedStylesheet (Source source,
                                         String media,
                                         String title,
                                         String charset)
    throws TransformerConfigurationException
  {
    // parse, and find the appropriate xsl-stylesheet PI contents
    throw new IllegalArgumentException ();
  }

  public Transformer newTransformer ()
    throws TransformerConfigurationException
  {
    return new NullTransformer ();
  }

  /**
   * Returns a TransformerHandler that knows how to generate output
   * in all three standard formats.  Output text is generated using
   * {@link XMLWriter}, and the GNU implementation of
   * {@link DomDocument DOM} is used.
   *
   * @see SAXResult
   * @see StreamResult
   * @see DOMResult
   */
  public TransformerHandler newTransformerHandler ()
    throws TransformerConfigurationException
  {
    NullTransformer	transformer = new NullTransformer ();
    return transformer.handler;
  }

  //
  // Stuff that depends on XSLT support, which we don't provide
  //
  private static final String		noXSLT = "No XSLT support";

  /** Throws an exception (XSLT is not supported). */
  public Transformer newTransformer (Source stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public Templates newTemplates (Source stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public TemplatesHandler newTemplatesHandler ()
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public TransformerHandler newTransformerHandler (Source stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public TransformerHandler newTransformerHandler (Templates stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public XMLFilter newXMLFilter (Source stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Throws an exception (XSLT is not supported). */
  public XMLFilter newXMLFilter (Templates stylesheet)
    throws TransformerConfigurationException
  {
    throw new TransformerConfigurationException (noXSLT);
  }

  /** Returns the value assigned by {@link #setErrorListener}.  */
  public ErrorListener getErrorListener ()
  {
    return errListener;
  }

  /** Assigns a value that would be used when parsing stylesheets */
  public void setErrorListener (ErrorListener e)
  {
    errListener = e;
  }

  /** Returns the value assigned by {@link #setURIResolver}.  */
  public URIResolver getURIResolver ()
  {
    return uriResolver;
  }

  /** Assigns a value that would be used when parsing stylesheets */
  public void setURIResolver (URIResolver u)
  {
    uriResolver = u;
  }


  //
  // Helper classes.  These might in theory be subclassed
  // by an XSLT implementation, if they were exported.
  //

  static class DomTerminus
    extends DomConsumer
  {

    DomTerminus (DOMResult result)
      throws SAXException
    {
      // won't really throw SAXException
      super (DomDocument.class);
      setHandler (new DomHandler (this, result));
    }
    
  }

  static class DomHandler
    extends Consumer.Backdoor
  {
  
    private DOMResult	result;
    
    DomHandler (DomConsumer c, DOMResult r)
      throws SAXException
    {
      // won't really throw SAXException
      super (c);
      result = r;
    }

    public void endDocument ()
      throws SAXException
    {
      super.endDocument ();
      result.setNode (getDocument ());
    }
    
  }

  private static OutputStream getOutputStream (String uri)
    throws IOException
  {
    // JDK stupidity:  file "protocol does not support output" ... 
    if (uri.startsWith ("file:"))
      return new FileOutputStream (uri.substring (5));
    
    // Otherwise ...
    URL		url = new URL (uri);
    URLConnection	conn = url.openConnection ();
    
    conn.setDoOutput (true);
    return conn.getOutputStream ();
  }
  

  static class NullHandler
    extends EventFilter
    implements TransformerHandler
  {
   
    private String		systemId;
    private Transformer	transformer;
    
    NullHandler (Transformer t)
    {
      transformer = t;
    }

    public Transformer getTransformer ()
    {
      return transformer;
    }

    public String getSystemId ()
    {
      return systemId;
    }

    public void setSystemId (String id)
    {
      systemId = id;
    }

    public void setResult (Result result)
    {
      if (result.getSystemId () != null)
        systemId = result.getSystemId ();
      
      try
        {
          
          // output to partial SAX event stream?
          if (result instanceof SAXResult)
            {
              SAXResult 	r = (SAXResult) result;
              
              setContentHandler (r.getHandler ());
              setProperty (LEXICAL_HANDLER, r.getLexicalHandler ());
              // DTD info is filtered out by javax.transform
              
              // output to DOM tree?
            }
          else if (result instanceof DOMResult)
            {
              DomTerminus	out = new DomTerminus ((DOMResult) result);

              setContentHandler (out.getContentHandler ());
              setProperty (LEXICAL_HANDLER,
                           out.getProperty (LEXICAL_HANDLER));
              // save DTD-derived info, if any.
              setDTDHandler (out.getDTDHandler ());
              setProperty (DECL_HANDLER,
                           out.getProperty (DECL_HANDLER));

              // node is saved into result on endDocument()

              // output to (XML) text?
            }
          else if (result instanceof StreamResult)
            {
              StreamResult	r = (StreamResult) result;
              XMLWriter		out;

              // FIXME:  when do output properties take effect?
              // encoding, standalone decl, xml/xhtml/... ...

              // FIXME:  maybe put nsfix filter up front

              try
                {
                  if (r.getWriter () != null)
                    out = new XMLWriter (r.getWriter ());
                  else if (r.getOutputStream () != null)
                    out = new XMLWriter (r.getOutputStream ());
                  else if (r.getSystemId () != null)
                    out = new XMLWriter (
                                         getOutputStream (r.getSystemId ()));
                  else
                    throw new IllegalArgumentException (
                                                        "bad StreamResult");
                }
              catch (IOException e)
                {
                  e.printStackTrace ();
                  // on jdk 1.4, pass the root cause ...
                  throw new IllegalArgumentException (e.getMessage ());
                }

              // out.setExpandingEntities (true);
              // out.setPrettyPrinting (true);
              // out.setXhtml (true);

              setContentHandler (out);
              setProperty (LEXICAL_HANDLER, out);
              // save DTD info, if any; why not?
              setDTDHandler (out);
              setProperty (DECL_HANDLER, out);
            }
          
        }
      catch (SAXException e)
        {
          // SAXNotSupportedException or SAXNotRecognizedException:
          // "can't happen" ... but SAXException for DOM build probs
          // could happen, so ...
          // on jdk 1.4, pass the root cause ...
          throw new IllegalArgumentException (e.getMessage ());
        }
    }
  }

  // an interface that adds no value
  static class LocatorAdapter
    extends LocatorImpl
    implements SourceLocator
  {
  
    LocatorAdapter (SAXParseException e)
    {
      setSystemId (e.getSystemId ());
      setPublicId (e.getPublicId ());
      setLineNumber (e.getLineNumber ());
      setColumnNumber (e.getColumnNumber ());
    }
    
  }

  // another interface that adds no value
  static class ListenerAdapter
    implements ErrorHandler
  {
    
    NullTransformer	transformer;
    
    ListenerAdapter (NullTransformer t)
    {
      transformer = t;
    }
    
    private TransformerException map (SAXParseException e)
    {
      return new TransformerException (
                                       e.getMessage (),
                                       new LocatorAdapter (e),
                                       e);
    }

    public void error (SAXParseException e)
      throws SAXParseException
    {
      try
        {
          if (transformer.errListener != null)
            transformer.errListener.error (map (e));
        }
      catch (TransformerException ex)
        {
          transformer.ex = ex;
          throw e;
        }
    }

    public void fatalError (SAXParseException e)
      throws SAXParseException
    {
      try
        {
          if (transformer.errListener != null)
            transformer.errListener.fatalError (map (e));
          else
            throw map (e);
        } catch (TransformerException ex) {
          transformer.ex = ex;
          throw e;
        }
    }

    public void warning (SAXParseException e)
      throws SAXParseException
    {
      try
        {
          if (transformer.errListener != null)
            transformer.errListener.warning (map (e));
        }
      catch (TransformerException ex)
        {
          transformer.ex = ex;
          throw e;
        }
    }
  }

  static class NullTransformer
    extends Transformer
  {
  
    private URIResolver		uriResolver;
    private Properties		props = new Properties ();
    private Hashtable		params = new Hashtable (7);
    
    ErrorListener			errListener = null;
    TransformerException		ex = null;
    NullHandler			handler;
    
    NullTransformer ()
    {
      super ();
      handler = new NullHandler (this);
    }
    
    public ErrorListener getErrorListener ()
    {
      return errListener;
    }
    
    public void setErrorListener (ErrorListener e)
    {
      errListener = e;
    }
    
    public URIResolver getURIResolver ()
    {
      return uriResolver;
    }
    
    public void setURIResolver (URIResolver u)
    {
      uriResolver = u;
    }

    public void setOutputProperties (Properties p)
    {
      props = (Properties) p.clone ();
    }
    
    public Properties getOutputProperties ()
    {
      return (Properties) props.clone ();
    }

    public void setOutputProperty (String name, String value)
    {
      props.setProperty (name, value);
    }
    
    public String getOutputProperty (String name)
    {
      return props.getProperty (name);
    }

    public void clearParameters ()
    {
      params.clear ();
    }
    
    public void setParameter (String name, Object value)
    {
      props.put (name, value);
    }
    
    public Object getParameter (String name)
    {
      return props.get (name);
    }

    public void transform (Source in, Result out)
      throws TransformerException
    {
      try
        {
          XMLReader		producer;
          InputSource		input;
          
          // Input from DOM?
          if (in instanceof DOMSource)
            {
              DOMSource	source = (DOMSource) in;
              
              if (source.getNode () == null)
                throw new IllegalArgumentException ("no DOM node");
              producer = new DomParser (source.getNode ());
              input = null;
              
              // Input from SAX?
            }
          else if (in instanceof SAXSource)
            {
              SAXSource	source = (SAXSource) in;
              
              producer = source.getXMLReader ();
              if (producer == null)
                producer = XMLReaderFactory.createXMLReader ();
              
              input = source.getInputSource ();
              if (input == null)
                {
                  if (source.getSystemId () != null)
                    input = new InputSource (source.getSystemId ());
                  else
                    throw new IllegalArgumentException (
                                                        "missing SAX input");
                }
              
              // Input from a stream or something?
            }
          else
            {
              producer = XMLReaderFactory.createXMLReader ();
              input = SAXSource.sourceToInputSource (in);
              if (input == null)
                throw new IllegalArgumentException ("missing input");
            }
          
          // preserve original namespace prefixes
          try
            {
              producer.setFeature(handler.FEATURE_URI + "namespace-prefixes",
                                  true);
            }
          catch (Exception e)
            {
              /* ignore */
              // FIXME if we couldn't, "NsFix" stage before the output ..
            }
          
          // arrange the output
          handler.setResult (out);
          handler.bind (producer, handler);
          
          // then parse ... single element pipeline
          producer.parse (input);
          
        }
      catch (IOException e)
        {
          throw new TransformerException ("transform failed", e);
          
        }
      catch (SAXException e)
        {
          if (ex == null && ex.getCause () == e)
            throw ex;
          else
            throw new TransformerException ("transform failed", e);
          
        }
      finally
        {
          ex = null;
        }
    }
  }

}
