//*****************************************************************************
/*
** FILE:   xg_Node.java
**
** (c) 1997, 1998 Steve Withall.
**
** HISTORY:
**    13Oct97  stevew  Created.
**    23Mar98  stevew  Added inheritance from DefaultMutableTreeNode - to
**                      support display within a Swing tree. Also renamed
**                      from xg_BaseEntity to xg_Node, for clarity.
**    11Apr98  stevew  Merged in xg_CompositeEntity.
*/
package xg;

import eh.eh_Debug;

//import org.w3c.dom.Node;

import com.sun.java.swing.text.AttributeSet;
import com.sun.java.swing.text.Document;
import com.sun.java.swing.text.Element;
import com.sun.java.swing.text.Position;
import com.sun.java.swing.text.SimpleAttributeSet;

import com.sun.java.swing.tree.DefaultMutableTreeNode;

import java.io.IOException;
import java.io.Writer;

import java.util.Vector;

//*****************************************************************************
/** Base class for all types of XML entity.
*/
abstract public class xg_Node extends    DefaultMutableTreeNode
                              implements Element
{
    /** The name of this node. */
    protected  String    NodeName            = null;

    /** If this node was preceded by whitespace in the source, this is it. */
    protected  String    PrecedingWhitespace = null;

    /** This node's parent. Note that attribute 'parent' is inherited from
     *  DefaultMutableTreeNode, so ParentNode is not really necessary and is
     *  probably doomed to removal in due course. */
    protected  xg_Node   ParentNode          = null;

    /** The position of the start of this node relative to the start of the
     *  document it is in. */
    protected  Position  StartPosition       = null;

    /** The position of the end of this node relative to the start of the
     *  document it is in. */
    protected  Position  EndPosition         = null;

    //*****************************************************************************
    /** Default constructor.
     */
    public xg_Node()
    {
    }

    //*****************************************************************************
    /** Construct a node with a name.
     *
     *  @param  InputNodeName  The name of the node
     */
    public xg_Node(String InputNodeName)
    {
        NodeName = InputNodeName;
    }

    //*****************************************************************************
    /** Clear out the contents of this node.
     */
    public void reset()
    {
        PrecedingWhitespace = null;
        ParentNode          = null;
        setParent(null);
        removeAllChildren();  // Clear tree (implemented in DefaultMutableTreeNode)
    }

    //*****************************************************************************
    /** Save this node (and its children) as an XML source in InputWriter.
     *
     *  @param  InputWriter   The writer to which the XML will be written
     */
    public void save(Writer  InputWriter) throws IOException
    {
        eh_Debug.add(7, "XE_Node.save:");
        saveChildren(InputWriter);
    }

    //*****************************************************************************
    /** Save this node's children in XML source form in InputWriter.
     *
     *  @param  InputWriter   The writer to which the XML will be written
     */
    public void saveChildren(Writer  InputWriter) throws IOException
    {
        eh_Debug.add(7, "xg_Node.saveChildren:");
        xg_Node  CurrentChild = null;
        for (int ChildIndex = 0; ChildIndex < getChildCount(); ChildIndex++)
        {
            CurrentChild = (xg_Node)getChildAt(ChildIndex);
            CurrentChild.save(InputWriter);
        }
    }

    //*****************************************************************************
    /** Write this node's content (and its children) to InputWriter. This
     *  implementation writes nothing, and derived classes which wish to write
     *  content must override it.
     *
     *  @param  InputWriter   The writer to which the content will be written
     */
    public void writeContent(Writer  InputWriter) throws IOException
    {
    }

    //*****************************************************************************
    /** Write this node's content (and its children) to InputWriter. This
     *  implementation writes nothing, and derived classes which wish to write
     *  content must override it.
     *
     *  @param  InputWriter   The writer to which the content will be written
     */
    public void writePrecedingWhitespace(Writer  InputWriter) throws IOException
    {
        if (PrecedingWhitespace != null)
            InputWriter.write(PrecedingWhitespace);
    }

    //*****************************************************************************
    /** Add a child.
     *
     *  @param  InputChildEntity  The child to be added
     */
    public void addChild(xg_Node  InputChildNode)
    {
        eh_Debug.add(8, "xg_Node.addChild: Add " + InputChildNode.toString()
                                 + " to parent " + toString());
        add(InputChildNode);    // Add to TreeNode
        InputChildNode.setParentNode(this);
    }

    //*****************************************************************************
    /** Insert a child at a specified position.
     *
     *  @param  InputChildEntity    The child to be added
     *  @param  InputChildPosition  The position in the list of children at which
     *                               to add the child
     */
    public void insertChild(xg_Node  InputChildNode, int  InputChildPosition)
    {
        eh_Debug.add(8, "xg_Node.insertChild: Insert " + InputChildNode.toString()
                               + " in parent "  + toString()
                               + " at position " + InputChildPosition);
        insert(InputChildNode, InputChildPosition);    // Insert in TreeNode
        InputChildNode.setParentNode(this);
    }

    //*****************************************************************************
    /** Verify that this node is correct (ie. internally correct and/or consistent
     *  with other nodes - such as its parent). This constitutes semantic checking
     *  on this node. Verification of this sort is meaningful only in special
     *  derived classes, since the classes for handling standard XML have no
     *  semantic knowledge (and therefore never return an error).
     *
     *  <p>This method also provides derived classes with an opportunity to perform
     *  processing once they have been fully parsed (say, to compute the values of
     *  derivative attribute).</p>
     *
     *  @exception  xg_VerificationException  Error in verification
     */
    public void verify() throws xg_VerificationException
    {
    }

    //*****************************************************************************
    /** Validate this node.
     *
     *  @exception  xg_ValidationException  Error in validation
     */
    public void validate() throws xg_ValidationException
    {
    }

    //*****************************************************************************
    /** Set the name of this node.
     *
     *  @param  NodeName   The name of this node
     */
    public void setName(String  InputNodeName)
    {
        NodeName = InputNodeName;
    }

    //*****************************************************************************
    /** Set the parent of this node.
     *
     *  @param  ParentNode   The parent of this node
     */
    public void setParentNode(xg_Node  InputParentNode)
    {
        ParentNode = InputParentNode;
        setParent(InputParentNode);
    }

    //*****************************************************************************
    /** Set the value of the preceding whitespace.
     *
     *  @param  InputPrecedingWhitespace   Value of the preceding whitespace
     */
    public void setPrecedingWhitespace(String  InputPrecedingWhitespace)
    {
        PrecedingWhitespace = InputPrecedingWhitespace;
    }

    //*****************************************************************************
    /** Set the position of the start of this node relative to the start of the
     *  document it is in.
     *
     * @param  InputStartPosition   Offset of start of node
     */
    public void setStartPosition(Position  InputStartPosition)
    {
        StartPosition = InputStartPosition;
    }

    //*****************************************************************************
    /** Set the position of the end of this node relative to the start of the
     *  document it is in.
     *
     *  @param  InputEndPosition   Offset of end of node
     */
    public void setEndPosition(Position  InputEndPosition)
    {
        EndPosition = InputEndPosition;
    }

    //*****************************************************************************
    /** Get the value of the preceding whitespace.
     *
     *  @return  String value or null
     */
    public String getPrecedingWhitespace()
    {
        return PrecedingWhitespace;
    }

    //*****************************************************************************
    /** Get the length of the preceding whitespace.
     *
     *  @return  The length of the preceding whitespace, or 0 if there is none
     */
    public int getPrecedingWhitespaceLength()
    {
        int  PrecedingWhitespaceLength = 0;
        if (PrecedingWhitespace != null)
            PrecedingWhitespaceLength = PrecedingWhitespace.length();
        return PrecedingWhitespaceLength;
    }

    //*****************************************************************************
    /** Get all the child nodes of a particular named type.
     *
     *  @param   InputNodeType  The name of the type required
     *  @return  A vector of all the child nodes of the given type; it is empty if
     *            there are no such children.
     */
    public Vector getChildrenOfType(String  InputNodeType)
    {
        Vector   RequestedChildren = new Vector();
        xg_Node  CurrentChild      = null;
        for (int ChildIndex = 0; ChildIndex < getChildCount(); ChildIndex++)
        {
            CurrentChild = (xg_Node)getChildAt(ChildIndex);
            if (CurrentChild.getName().equals(InputNodeType) )
                RequestedChildren.addElement(CurrentChild);
        }
        return RequestedChildren;
    }

    //*****************************************************************************
    /** Get all the child nodes of a particular type code.
     *
     *  @param   InputNodeType  The ID of the type required
     *  @return  A vector of all the child nodes of the given type; it is empty if
     *            there are no such children.
     */
    public Vector getChildrenOfType(int  InputNodeType)
    {
        Vector   RequestedChildren = new Vector();
        xg_Node  CurrentChild      = null;
        for (int ChildIndex = 0; ChildIndex < getChildCount(); ChildIndex++)
        {
            CurrentChild = (xg_Node)getChildAt(ChildIndex);
            if (CurrentChild.getType() == InputNodeType)
                RequestedChildren.addElement(CurrentChild);
        }
        return RequestedChildren;
    }

    //*****************************************************************************
    /** Get all the child nodes which are an instance of a particular named class.
     *
     *  @param   InputClassName  The name of the class required
     *  @return  A vector of all the child nodes of the given class; it is empty if
     *            there are no such children.
     */
    public Vector getChildrenOfClass(String  InputClassName)
    {
        Vector   RequestedChildren = new Vector();
        xg_Node  CurrentChild      = null;

        try
        {
            Class    RequestedClass    = Class.forName(InputClassName);
            for (int ChildIndex = 0; ChildIndex < getChildCount(); ChildIndex++)
            {
                CurrentChild = (xg_Node)getChildAt(ChildIndex);
                if (RequestedClass.isInstance(CurrentChild) )
                    RequestedChildren.addElement(CurrentChild);
            }
        }
        catch(ClassNotFoundException  InputException)
        {
            eh_Debug.add(2, "xg_Node.getChildrenOfClass: Requested class "
                                          + InputClassName + " is not a valid class");
        }

        return RequestedChildren;
    }

    //*****************************************************************************
    /** Get the first child element of a particular named type.
     *
     *  @param   InputNodeType  The name of the type required
     *  @return  The first child node of the given type (or null if there isn't one).
     */
    public xg_Node getFirstChildOfType(String  InputNodeType)
    {
        xg_Node  RequestedNode = null;
        xg_Node  CurrentChild  = null;
        for (int ChildIndex = 0; ChildIndex < getChildCount(); ChildIndex++)
        {
            CurrentChild = (xg_Node)getChildAt(ChildIndex);
            if (CurrentChild.getName().equals(InputNodeType) )
            {
                RequestedNode = CurrentChild;
                break;
            }
        }
        return RequestedNode;
    }

    //*****************************************************************************
    /** Get the child element with the given index.
     *
     *  @param   InputChildIndex  The index of the child required
     *  @return  The child of the given number
     */
    public xg_Node getChild(int  InputChildIndex)
    {
        return (xg_Node)getChildAt(InputChildIndex);
    }

    //*****************************************************************************
    /** Get this node's parent.
     *
     *  @return  The parent of the node
     */
    public xg_Node getParentNode()
    {
        return ParentNode;
    }

    //*****************************************************************************
    /** Get the top-level node of the tree to which this node belongs.
     *
     *  @return  The root node of this tree
     */
    public xg_Node getRootNode()
    {
        xg_Node  RootNode = null;
        if (ParentNode == null)
            RootNode = this;
        else
            RootNode = ParentNode.getRootNode();
        return RootNode;
    }

    //*****************************************************************************
    /** Get the first ancestor of this node whose NodeName is InputTypeName (or null
     *  if it doesn't have an ancestor of this type).
     *
     *  @return  The first ancestor of this node which is of the requested type
     */
    public xg_Node getAncestorOfType(String  InputNodeName)
    {
        eh_Debug.add(6, "xg_Node.getAncestorOfType('" + InputNodeName + "')");
        xg_Node  AncestorNode = null;
        if (    NodeName != null
             && NodeName.equals(InputNodeName) )
            AncestorNode = this;
        else if (ParentNode != null)
            AncestorNode = ParentNode.getAncestorOfType(InputNodeName);
        return AncestorNode;
    }

    //*****************************************************************************
    /** Get a summary descriptive string suitable for display in the tree view or
     *  elsewhere. To be overridden in each sub-class.
     *
     *  @return  A description suitable for display in the tree view
     */
    public String getSummaryString()
    {
        return (toString() );
    }

    //*****************************************************************************
    /** Return a string representation of the node (intended for use as debug
     *  output).
     *
     *  @return  String representing the node type
     */
    public String toString()
    {
        return ("Node '" + NodeName + "'");
    }

    //*****************************************************************************
    //*Methods*implementing*com.sun.java.swing.text.Element************************
    //*****************************************************************************
    /** Note that the method 'public boolean isLeaf()' is not mentioned here because
     *  a method of identical signature is inherited from DefaultMutableTreeNode. */
    //*****************************************************************************
    /** Get the swing.text.Document associated with this node.
     *
     *  @return  The swing.text.Document to which this node belongs
     */
    public Document getDocument()
    {
//        eh_Debug.add(6, "xg_Node.getDocument");
        Document  OwningDocument = null;
        if (ParentNode != null)
            OwningDocument = ParentNode.getDocument();
        return OwningDocument;
    }

    //*****************************************************************************
    /** Get this node's parent (as an Element). If the element is a root level
     *  element, null is returned.
     *
     *  @return the parent element
     */
    public Element getParentElement()
    {
        return ParentNode;
    }

    //*****************************************************************************
    /** Get the name of this node. If the node is used to represent some type
     *  of structure, this is its type name.
     *
     *  @return  The name of this node
     */
    public String getName()
    {
        return NodeName;
    }

    //*****************************************************************************
    /** Get the type of this node.
     *
     *  @return  The type of this node
     */
    abstract public int getType();
//    {
//        return -1;
//    }

    //*****************************************************************************
    /** Get the type of this node, using only type values defined for the DOM.
     *  (Implements org.w3c.dom.Node.getNodeType.)
     *
     *  @return  The DOM type of this node
     */
    public int getNodeType()
    {
        return (getType() );
    }

    //*****************************************************************************
    /** Get a collection of attributes this node contains. This method may be
     *  overridden by derived classes, but a raw node itself has no attributes of
     *  its own and thus the AttributeSet returned contains no attributes itself
     *  - although attributes may be obtained via resolve parent.
     *
     *  @return  The attributes for the node
     */
    public AttributeSet getAttributes()
    {
//        SimpleAttributeSet  NodeAttributeSet   = new SimpleAttributeSet();  //TEMP
        xg_AttList    NodeAttributeSet   = new xg_AttList();  //TEMP
        AttributeSet  ParentAttributeSet = null;
        if (ParentNode != null)
            ParentAttributeSet = ParentNode.getAttributes();
        NodeAttributeSet.setResolveParent(ParentAttributeSet);
        return NodeAttributeSet;
    }

    //*****************************************************************************
    /** Get the offset from the beginning of the document at which this element
     *  begins. If this element has children, this will be the offset of the first
     *  child.
     *
     * @return  The starting offset
     */
    public int getStartOffset()
    {
        int  StartOffset = -1;
        if (StartPosition != null)
            StartOffset = StartPosition.getOffset();
        eh_Debug.add(8, "xg_Node.getStartOffset: Start offset = " + StartOffset);
        return (StartOffset);
    }

    //*****************************************************************************
    /** Get the offset from the beginning of the document at which this element
     *  ends. If this element has children, this will be the end offset of the last
     *  child.
     *
     *  @return  The ending offset
     */
    public int getEndOffset()
    {
        int  EndOffset = -1;
        if (EndPosition != null)
            EndOffset = EndPosition.getOffset();
        eh_Debug.add(8, "xg_Node.getEndOffset: End offset = " + EndOffset);
        return (EndOffset);
    }

    //*****************************************************************************
    /** Get the child element index closest to the given offset. The offset is
     *  specified relative to the beginning of the document.
     *
     *  @param   InputOffset  The offset from the start of the document
     *  @return               The index of the element which resides at this offset
     */
    public int getElementIndex(int  InputOffset)
    {
        eh_Debug.add(8, "xg_Node.getElementIndex: Find child of " + NodeName
                                       + " at offset " + InputOffset);
	    if (getChildCount() == 0)
		    return 0;                      // No children
	    if (InputOffset >= getEndOffset() )
		    return (getChildCount() - 1);  // Offset is too big: point to last child

	    int      FoundIndex;
	    int      LowerIndex       = 0;
	    int      UpperIndex       = getChildCount() - 1;
	    int      MidIndex         = 0;
    	xg_Node  CurrentChild     = null;
	    int      ChildStartOffset = 0;
	    int      ChildEndOffset;

        // Home in by repeatedly performing a binary chop.
	    while (LowerIndex <= UpperIndex)
        {
    		MidIndex         = (UpperIndex + LowerIndex)/2;
    		CurrentChild     = getChild(MidIndex);
    		ChildStartOffset = CurrentChild.getStartOffset();
    		ChildEndOffset   = CurrentChild.getEndOffset();
            if (ChildStartOffset > InputOffset)      // This child is too high
		        UpperIndex = MidIndex - 1;
            else  if (ChildEndOffset < InputOffset)  // This child is too low
    		    LowerIndex = MidIndex + 1;
            else
            {
    		    // We have found the location.
    		    FoundIndex = MidIndex;
    		    return FoundIndex;
    		}
	    }

	    // We didn't find it, but indicate the index of where it would belong
	    if (InputOffset < ChildStartOffset)
    		FoundIndex = MidIndex;
        else
    		FoundIndex = MidIndex + 1;
        eh_Debug.add(6, "xg_Node.getElementIndex: Child " + FoundIndex
                                       + " of " + NodeName
                                       + " is at offset " + InputOffset);
	    return FoundIndex;
    }

    //*****************************************************************************
    /** Get the number of child elements contained by this element. If this element
     *  is a leaf, a count of zero is returned.
     *
     *  @return  The number of child elements
     */
    public int getElementCount()
    {
        return (getChildCount() );
    }

    //*****************************************************************************
    /** Get the child element at the given index.
     *
     *  @param   InputChildIndex  The index of the required child
     *  @return                   The child element
     */
    public Element getElement(int  InputChildIndex)
    {
        eh_Debug.add(6, "xg_Node.getElement: Get child number " + InputChildIndex);
        Element  ChildElement = null;
        if (InputChildIndex <= (getChildCount() - 1) )
            ChildElement = getChild(InputChildIndex);
        return ChildElement;
    }

    //*****************************************************************************
    //*org.w3c.dom.Node*methods****************************************************
    //*****************************************************************************
    /** Get the name of this node. If the node is used to represent some type
     *  of structure, this is its type name.
     *
     *  @return  The name of this node
     */
    public String getNodeName()
    {
        return NodeName;
    }

    //*****************************************************************************
//   public String             getNodeValue();
//   public void               setNodeValue(String arg);

//   public short              getNodeType();

//   public Node               getParentNode();

//   public NodeList           getChildNodes();

//   public Node               getFirstChild();

//   public Node               getLastChild();

//   public Node               getPreviousSibling();

//   public Node               getNextSibling();

//   public NamedNodeMap       getAttributes();

//   public Node               insertBefore(Node newChild,
//                                          Node refChild)
//                                          throws DOMException;
//   public Node               replaceChild(Node newChild,
//                                          Node oldChild)
//                                          throws DOMException;
//   public Node               removeChild(Node oldChild)
//                                         throws DOMException;
//   public Node               appendChild(Node newChild);

    //*****************************************************************************
    /** Indicate whether this node has children.
     *
     *  @return  true if this node has children; false if not
     */
    public boolean hasChildNodes()
    {
        return(getChildCount() > 0);
    }

//   public Node               cloneNode(boolean deep);
//   public boolean            equals(Node arg,
//                                    boolean deep);
}

//*****************************************************************************
