/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import org.w3c.dom.*;

/**
 * The TXDocument class implements the Document interface as defined by the Document Object Model (DOM).
 * <p>The Document object represents the entire XML document. Conceptually, it is the root 
 * of the document tree, and provides the primary access to the document's data.
 * <P>It should be noted that factory methods (<CODE>create*()</code>) are not used by the parser.
 *
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 * @see org.w3c.dom.Document
 * @see com.ibm.xml.parser.Parent
 * @see com.ibm.xml.parser.Child
 */
public class TXDocument extends Parent implements Document {
    
            DocumentContext documentContext     = null;
            TXElement       rootElement         = null;
            DTD             doctype             = null;
            String          standalone          = null;
            String          xmlVersion          = null;
            String          xmlEncoding         = null;
    java.util.Hashtable     nsHash              = new java.util.Hashtable();

    /**
     * Constructor.  Adds the <CODE>xml</CODE> namespace to this document.
     */
    public TXDocument() {
        addNamespace(new NamespacePI("", "xml"));
    }

    /**
     * Clone this Document Node and its children using the appropriate factories.
     * <p>This method is defined by Child.
     * @return          Cloned Document Node and all children.
     * @see com.ibm.xml.parser.Child#clone
     */
    public synchronized Object clone() {
        checkFactory();
        TXDocument d = factory.createDocument();
        d.setFactory(getFactory());
        d.standalone        = this.standalone;
        d.xmlVersion        = this.xmlVersion;
        d.xmlEncoding       = this.xmlEncoding;
        d.documentContext   = this.documentContext;
        d.children.ensureCapacity((int)children.getLength());
        for (int i = 0;  i < children.getLength();  i ++)
            d.addElement((Child)((Child)children.item(i)).clone());
        return d;
    }
    
    /**
     * Returns that this object is a Document Node.
     * <p>This method is defined by DOM.
     * @return          Document Node indicator.
     */
    public int getNodeType() {
        return Node.DOCUMENT;
    }

    /**
     * Returns this Document's document context.
     * @return          Document context, or <var>null</var> if no type.
     * @see #setContextInfo
     * @see com.ibm.xml.parser.DocumentContext
     */
    public DocumentContext getContextInfo() {
        return this.documentContext;
    }
    
    /**
     * Sets this Document's document context.
     * @param   documentContext Document context.
     * @see #setContextInfo
     * @see com.ibm.xml.parser.DocumentContext
     */
    public void setContextInfo(DocumentContext documentContext) {
        this.documentContext = documentContext;
    }
    
    /**
     * Returns this Document's root Element.
     * @return          Document root Element, or <var>null</var> if no root Element.
     * @see #setDocumentElement
     * @see #getRootName
     */
    public Element getDocumentElement() {
        return this.rootElement;
    }

    /**
     * Sets this Document's root Element.
     * @return          Document root Element.
     * @see #getDocumentElement
     */
    public void setDocumentElement(Element rootElement) {
        this.rootElement = (TXElement)rootElement;
    }

    /**
     * Returns the name of this Document's root Element (not DTD).
     * @return          Name of Document root Element, or <var>null</var> if no root element.
     * @see getDocumentElement
     */
    public String getRootName() {
        return null == this.rootElement ? null : this.rootElement.getTagName();
    }

    /**
     * Returns a <var>NodeIterator</var> of matches through all child Element Nodes.
     * Searching is done recursively, not just for immediate Child Nodes.
     * The returned iterator is not "live" (refer to VectorNodeIterator for details).
     * The specified qualified name refers to the Element tag name (see Namespace for details).
     * <p>This method is defined by DOM.
     * @param   qName   Qualified name to match against in all subordinate Elements.
     * @return          A <var>NodeIterator</var> of matched <var>Element</var> Nodes (<var>TXElement</var>)
     * @see com.ibm.xml.parser.TXElement#getElementsByTagName
     * @see com.ibm.xml.parser.VectorNodeIterator
     * @see com.ibm.xml.parser.Namespace
     */
    public NodeIterator getElementsByTagName(String qName) {
        return null != rootElement ? rootElement.getElementsByTagName(qName)
            : VectorNodeIterator.emptyIterator;
    }
    
    /**
     * Returns this Document's Type Definition (DTD) as a Node.
     * @return          Document Type Definition (DTD), or <var>null</var> if no type.
     * @see #getDTD
     * @see #setDocumentType
     */
    public Node getDocumentType() {
        return this.doctype;
    }

    /**
     * Returns this Document's Type Definition (DTD) as a DTD.
     * @return          Document Type Definition (DTD), or <var>null</var> if no type.
     * @see #getDocumentType
     * @see #isApplySyntax
     * @see #setDocumentType
     */
    public DTD getDTD() {
        return this.doctype;
    }

    /**
     * Sets this Document's Type Definition (DTD).
     * @param documentType  Document Type Definition (DTD) in the form of a Node.
     * @see #getDocumentType
     * @see #getDTD
     * @see #isApplySyntax
     */
    public void setDocumentType(Node documentType) {
        this.doctype = (DTD)documentType;
    }

    /**
     * Returns whether a syntax is being applied to this doument.  In other words,
     * return whether a Document Type Definition (DTD) has been defined.
     * @return          =true if a Document Type Definition (DTD) has been defined; otherwise =false.
     * @see #getDocumentType
     * @see #getDTD
     * @see #setDocumentType
     */
    public boolean isApplySyntax() {
        return null != this.doctype;
    }

    /**
     * Returns if this Document is standalone. A document that contains a standalone value
     * of <var>yes</var> indicates that there are no markup declarations external to the document 
     * entity either in the DTD external subset, or in an external parameter entity 
     * referenced from the internal subset. The value <var>no</var> indicates that there 
     * are or may be such external markup declarations. 
     * @return          =<var>yes</var> if standalone; =<var>no</var> if not standalone; 
     *                    =<var>null</var> if not known.
     * @see #setStandalone
     * @see #isStandalone
     */
    public String getStandalone() {
        return this.standalone;
    }

    /**
     * Returns if this Document is standalone. A document that contains a standalone value
     * of <var>true</var> indicates that there are no markup declarations external to the document 
     * entity either in the DTD external subset, or in an external parameter entity 
     * referenced from the internal subset. The value <var>false</var> indicates that there 
     * are or may be such external markup declarations. 
     * @return          =<var>true</var> if standalone; otherwise, =<var>false</var>. 
     * @see #getStandalone
     * @see #setStandalone
     */
    public boolean isStandalone() {
        return null == this.standalone ? false : "yes".equals(this.standalone);
    }

    /**
     * Sets if this Document is standalone. A document that contains a standalone value
     * of <var>yes</var> indicates that there are no markup declarations external to the document 
     * entity either in the DTD external subset, or in an external parameter entity 
     * referenced from the internal subset. The value <var>no</var> indicates that there 
     * are or may be such external markup declarations. 
     * @return          =<var>yes</var> if standalone; =<var>no</var> if not standalone; 
     *                    =<var>null</var> if not known.
     * @see #isStandalone
     * @see #getStandalone
     */
    public void setStandalone(String standalone) {
        this.standalone = standalone;
        if (null == this.xmlVersion)
            this.xmlVersion = "1.0";
    }

    /**
     * Returns the XML version of this Document from the XML prolog declaration (e.g. <code>&lt;?xml ...></code>).
     * @return          XML version of this Document (e.g. "1.0"), or <var>null</var> if version not specified.
     * @see #setVersion
     */
    public String getVersion() {
        return this.xmlVersion;
    }

    /**
     * Sets the XML version of this Document from the XML prolog declaration (e.g. <code>&lt;?xml ...></code>).
     * @param   xmlVersion  XML version of this Document (e.g. "1.0").
     * @see #getVersion
     */
    public void setVersion(String xmlVersion) {
        this.xmlVersion = xmlVersion;
    }

    /**
     * Returns the value of the XML encoding parameter from the XML prolog declaration (e.g. <code>&lt;?xml encoding="..."&gt;</code>).
     * @return          Value of the XML encoding parameter, or <var>null</var> if encoding not specified.
     * @see #setEncoding
     */
    public String getEncoding() {
        return this.xmlEncoding;
    }

    /**
     * Sets the value of the XML encoding parameter from the XML prolog declaration (e.g. <code>&lt;?xml encoding="..."&gt;</code>).
     * <p>The supported XML encodings are the intersection of XML-supported code sets and 
     * those supported in JDK 1.1:
     * <ul>
     * <li>UTF-16
     * <li>ISO-10646-UCS-2
     * <li>ISO-10646-UCS-4
     * <li>UTF-8
     * <li>US-ASCII
     * <li>ISO-8859-1 ... ISO-8859-9
     * <li>ISO-2022-JP
     * <li>Shift_JIS
     * <li>EUC-JP
     * <li>GB2312
     * <li>Big5
     * </ul>
     * @param   xmlEncoding Value of the XML encoding parameter. 
     * @see com.ibm.xml.parser.MIME2Java#convert
     * @see #getEncoding
     * @see com.ibm.xml.parser.DTD#setEncoding
     */
    public void setEncoding(String xmlEncoding) {
        this.xmlEncoding = xmlEncoding;
        if (null == this.xmlVersion)
            this.xmlVersion = "1.0";
    }

    /**
     * Returns the <var>NamespacePI</var> instance which matches the specified namespace prefix name.
     * @param   prefixName  The prefix value to match from <code>&lt;?xml:namespace ns="..." prefix="..."&gt;</code>.
     * @return              Matching <var>NamespacePI</var>, or <var>null</var> if no match.
     * @see #getNamespaces
     * @see #addNamespace
     * @see #setNamespaceParameters
     * @see com.ibm.xml.parser.NamespacePI
     */
    public NamespacePI getNamespaceFor(String nsPrefixName) {
        return (NamespacePI)this.nsHash.get(nsPrefixName);
    }

    /**
     * Returns an enumeration of <var>NamespacePI</var> instances defined for this Document.
     * @return              All defined <var>NamespacePI</var> instances, or <var>null</var> none defined.
     * @see #getNamespaceFor
     * @see #addNamespace
     * @see #setNamespaceParameters
     * @see com.ibm.xml.parser.NamespacePI
     */
    public java.util.Enumeration getNamespaces() {
        return this.nsHash.elements();
    }
    
    /**
     * Records the presence of a <var>NamespacePI</var> instance for this Document.  Note
     * that this method does not add a Node for the <var>NamespacePI</var>.  This method
     * facilitates a quick hash table query of defined namespaces.
     * <p>This method is automatically called by <CODE>insert</code>
     * and <code>addElement</CODE> methods.
     * @param   namespacePI The <var>namespacePI</var> instance to record.
     * @see #getNamespaces
     * @see #getNamespaceFor
     * @see #setNamespaceParameters
     * @see com.ibm.xml.parser.NamespacePI
     */
    public void addNamespace(NamespacePI namespacePI) {
        this.nsHash.put(namespacePI.getPrefix(), namespacePI);
    }

    /**
     * Returns the specified <var>namespace</var> with all aspects of its namespace set
     * according to NamespacePIs defined in this document, and the raw tag name.
     * @param namespace Namespace to be initialized. Note: <code>namespace.getName()</code>
     *                  must contain the tag's qualified name.
     * @return          Error message, or <code>null</code> if successful.
     * @see #getNamespaces
     * @see #getNamespaceFor
     * @see #addNamespace
     * @see com.ibm.xml.parser.Namespace
     * @see com.ibm.xml.parser.NamespacePI
     */
    public String setNamespaceParameters(Namespace namespace) {
        String ret = null;
        String name = namespace.getName();
        int nssep = name.indexOf(':');
        if (0 < nssep) {
            String prefix = name.substring(0, nssep);
            String local = name.substring(nssep+1);
            //obj.setNSPrefixName(prefix);
            namespace.setNSLocalName(local);
            NamespacePI npi = getNamespaceFor(prefix);
            if (null != npi)
                namespace.setNSName(npi.getNs());
            else
                ret = "E_TAGf";
            if (0 <= local.indexOf(':'))  ret = "E_TAGe";
        } else {
            //obj.setNSPrefixName(null);
            namespace.setNSLocalName(name);
            namespace.setNSName(null);
        }
        return ret;
    }

    /**
     * Insert a Child Node into the specified position.  Special consideration is given
     * to DTDs, the root document Element, and namspace PI children.
     * @param child     The Child Node being inserted.
     * @param index     0-based index into the list of children.
     * @exception LibraryException Thrown if the document's root element is set twice.
     */
    public void insert(Child child, int index) throws LibraryException {     
        super.insert(child, index);
        if (child instanceof DTD) {
            if (null != this.doctype)
                removeChild(this.doctype);
            this.doctype = (DTD)child;
        } else if (child instanceof TXElement) {
            if (null != this.rootElement)
                throw new LibraryException("com.ibm.xml.parser.TXDocument#insert(): Document root Element was set twice.");
            else
                setDocumentElement((Element)child);
        } else if (child instanceof NamespacePI) {
            addNamespace((NamespacePI)child);
        }
    }

    /**
     * Print this Node and all children in XML format using the default character encoding.
     * Entities embedded in the output stream will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @param pw        The character output stream to use.
     * @see com.ibm.xml.parser.Entity
     * @see com.ibm.xml.Format.print
     */
    public void printWithFormat(java.io.PrintWriter pw) {
        Format.print(this, pw);
    }
    
    /**
     * Print this Node and all children in XML format using the specified character encoding.
     * Entities embedded in the output stream will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @param pw        The character output stream to use.
     * @param enc       Java character encoding in use by <VAR>pw</VAR>.
     * @see com.ibm.xml.parser.Entity
     * @see com.ibm.xml.Format.print
     */
    public void printWithFormat(java.io.PrintWriter pw, String enc) {
        Format.print(this, pw, enc);
    }
    
    /**
     * Print this Node and all children in XML format, using the specified character encoding, and indented by <var>indent</var> spaces.
     * Entities embedded in the output stream will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @param pw        The character output stream to use.
     * @param enc       Java character encoding in use by <VAR>pw</VAR>.
     * @param indent    Number of spaces to indent for formatting.
     * @see com.ibm.xml.parser.Entity
     * @see com.ibm.xml.Format.print
     */
    public void printWithFormat(java.io.PrintWriter pw, String enc, int indent) {
        Format.print(this, pw, enc, indent);
    }

    /**
     * Sets whether the internal DTD is printed by the <code>print()</code> methods.
     * @param   printInternalDTD    =true to print the internal DTD; =false to not print.
     */
    public void setPrintInternalDTD(boolean printInternalDTD) {
        if (null != this.doctype)
            this.doctype.setPrintInternalDTD(printInternalDTD);
    }

    /**
     * Return all text associated with this Node and its children without considering entities.                                           
     * <p>This method is defined by Child.
     * @return          Text associated with all children, or <var>""</var> if no children.
     * @see com.ibm.xml.parser.Child#toString
     */
    public String getText() {                   
        return null != this.rootElement ? this.rootElement.getText() : "";
    }

    /**
     * Returns the Document Node asociated with this document fragment.
     * Note: XML4J has not yet implemented document fragments.
     * <p>This method is CURRENTLY NOT IMPLEMENTED.
     * @return              Document Node associated with this document fragment.
     * @see #setMasterDoc
     */
    public Document getMasterDoc(){
        return null;
    }
    
    /**
     * Sets the Document Node asociated with this document fragment.
     * Note: XML4J has not yet implemented document fragments.
     * <p>This method is CURRENTLY NOT IMPLEMENTED.
     * @param   document    Document Node associated with this document fragment.
     * @see #getMasterDoc
     */
    public void setMasterDoc(Document document) {
    }

    /**
     * Create and return a new DocumentContext.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @return         New <var>TXDocumentContext</var> Node.
     */
    public DocumentContext createDocumentContext() {
        return new TXDocumentContext();
    }
    
    /**
     * Create and return a new Element Node.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @param   tagName     The Element's tag name. 
     * @param   attributes  The attributes to set specified in the form of a TXAttributeList or AttributeList. 
     *                      Specify <var>null</var> if no attributes are to be set for the Element.
     * @return              New <var>TXElement</var> Node.
     */
    public Element createElement(String tagName, AttributeList attributes) {
        TXElement ele = new TXElement(tagName);
        ele.setAttributes(attributes);
        return ele;
    }
    
    /**
     * Create and return a new Text Node.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @param data      The actual content of the text Node.
     * @return          New <var>TXText</var> Node.
     */
    public Text createTextNode(String data) {
        return new TXText(data);
    }
    
    /**
     * Create and return a new Comment Node.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @param data      The actual content of the comment Node.
     * @return          New <var>TXComment</var> Node.
     */
    public Comment createComment(String data) {
        return new TXComment(data);
    }
    
    /**
     * Create and return a new PI Node.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @param name      The first token following the markup.
     * @param data      From the character immediately after the name to the character immediately preceding the <code>?&gt;</code>.
     * @return          New <var>TXPI</var> Node.
     */
    public PI createPI(String name, String data) {
        return new TXPI(name, data);
    }
    
    /**
     * Create and return a new Attribute.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @param name      The name of this attribute. 
     * @param value     This parameter IS NOT IMPLEMENTED. 
     * @return          New <var>TXAttribute</var>.
     */
    public Attribute createAttribute(String name, Node value) {
        TXAttribute attr = new TXAttribute(name, "");
        //attr.setValue(value);
        return attr;
    }
    
    /**
     * Create and return a new AttributeList.
     * <p>It should be noted this factory method is not used by the parser. 
     * <p>This method is defined by DOM.
     * @return         New <var>TXAttributeList</var>.
     */
    public AttributeList createAttributeList() {
        return new TXAttributeList();
    }

    /**
     * @deprecated Not implemented.
     */
    public TreeIterator createTreeIterator(Node node) {
        return null;
    }

    /**
     * @deprecated Use getDigest in TXElement, TXText, TXComment, and TXPI.
     */
    public byte[] getDigest() {
        return null;
    }
}
