// HTMLParser Library $Name: v1_6 $ - A java-based parser for HTML
// http://sourceforge.org/projects/htmlparser
// Copyright (C) 2004 Somik Raha
//
// Revision Control Information
//
// $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/util/NodeList.java,v $
// $Author: derrickoswald $
// $Date: 2006/05/31 02:10:15 $
// $Revision: 1.62 $
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package org.htmlparser.util;

import java.io.Serializable;
import java.util.NoSuchElementException;

import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.visitors.NodeVisitor;

public class NodeList implements Serializable
{
    private static final int INITIAL_CAPACITY=10;
    //private static final int CAPACITY_INCREMENT=20;
    private Node nodeData[];
    private int size;
    private int capacity;
    private int capacityIncrement;

    public NodeList ()
    {
        removeAll ();
    }
    
    /**
     * Create a one element node list.
     * @param node The initial node to add.
     */
    public NodeList (Node node)
    {
        this ();
        add (node);
    }
    
    public void add (Node node)
    {
        if (size == capacity)
            adjustVectorCapacity ();
        nodeData[size++] = node;
    }
    
    /**
     * Add another node list to this one.
     * @param list The list to add.
     */
    public void add (NodeList list)
    {
        for (int i = 0; i < list.size; i++)
            add (list.nodeData[i]);
    }
    
    /**
     * Insert the given node at the head of the list.
     * @param node The new first element.
     */
    public void prepend (Node node)
    {
        if (size == capacity)
            adjustVectorCapacity ();
        System.arraycopy (nodeData, 0, nodeData, 1, size);
        size++;
        nodeData[0]=node;
    }
    
    private void adjustVectorCapacity ()
    {
        capacity += capacityIncrement;
        capacityIncrement *= 2;
        Node oldData [] = nodeData;
        nodeData = newNodeArrayFor (capacity);
        System.arraycopy (oldData, 0, nodeData, 0, size);
    }
    
    private Node[] newNodeArrayFor (int capacity)
    {
        return new Node[capacity];
    }
    
    public int size ()
    {
        return size;
    }
    
    public Node elementAt (int i)
    {
        return nodeData[i];
    }
    
    public SimpleNodeIterator elements ()
    {
        return new SimpleNodeIterator ()
        {
            int count = 0;
            
            public boolean hasMoreNodes ()
            {
                return count < size;
            }
            
            public Node nextNode ()
            {
                synchronized (NodeList.this)
                {
                    if (count < size)
                    {
                        return nodeData[count++];
                    }
                }
                throw new NoSuchElementException ("Vector Enumeration");
            }
        };
    }
    
    public Node [] toNodeArray ()
    {
        Node [] nodeArray = newNodeArrayFor (size);
        System.arraycopy (nodeData, 0, nodeArray, 0, size);
        return nodeArray;
    }
    
    public void copyToNodeArray (Node[] array)
    {
        System.arraycopy (nodeData, 0, array, 0, size);
    }
    
    public String asString ()
    {
        StringBuffer buff = new StringBuffer ();
        for (int i=0;i<size;i++)
            buff.append (nodeData[i].toPlainTextString ());
        return buff.toString ();
    }
    
    /**
     * Convert this nodelist into the equivalent HTML.
     * @param verbatim If <code>true</code> return as close to the original
     * page text as possible.
     * @return The contents of the list as HTML text.
     */
    public String toHtml (boolean verbatim)
    {
        StringBuffer ret;
        
        ret = new StringBuffer ();
        for (int i = 0; i < size; i++)
            ret.append (nodeData[i].toHtml (verbatim));

        return (ret.toString ());
    }

    /**
     * Convert this nodelist into the equivalent HTML.
     * @return The contents of the list as HTML text.
     */
    public String toHtml ()
    {
        return (toHtml (false));
    }

    /**
     * Remove the node at index.
     * @param index The index of the node to remove.
     * @return The node that was removed.
     */
    public Node remove (int index)
    {
        Node ret;

        ret = nodeData[index];
        System.arraycopy (nodeData, index+1, nodeData, index, size - index - 1);
        nodeData[size-1] = null;
        size--;

        return (ret);
    }
    
    public void removeAll ()
    {
        size = 0;
        capacity = INITIAL_CAPACITY;
        nodeData = newNodeArrayFor (capacity);
        capacityIncrement = capacity * 2;
    }
    
    /**
     * Check to see if the NodeList contains the supplied Node.
     * @param node The node to look for.
     * @return True is the Node is in this NodeList.
     */
    public boolean contains (Node node)
    {
        return (-1 != indexOf (node));
    }
    
    /**
     * Finds the index of the supplied Node.
     * @param node The node to look for.
     * @return The index of the node in the list or -1 if it isn't found.
     */
    public int indexOf (Node node)
    {
        int ret;

        ret = -1;
        for (int i = 0; (i < size) && (-1 == ret); i++)
            if (nodeData[i].equals (node))
                ret = i;

        return (ret);
    }
    
    /**
     * Remove the supplied Node from the list.
     * @param node The node to remove.
     * @return True if the node was found and removed from the list.
     */
    public boolean remove (Node node)
    {
        int index;
        boolean ret;

        ret = false;
        if (-1 != (index = indexOf (node)))
        {
            remove (index);
            ret = true;
        }

        return (ret);
    }

    /**
     * Return the contents of the list as a string.
     * Suitable for debugging.
     * @return A string representation of the list. 
     */
    public String toString()
    {
        StringBuffer ret;
        
        ret = new StringBuffer ();
        for (int i = 0; i < size; i++)
            ret.append (nodeData[i]);

        return (ret.toString ());
    }

    /**
     * Filter the list with the given filter non-recursively.
     * @param filter The filter to use.
     * @return A new node array containing the nodes accepted by the filter.
     * This is a linear list and preserves the nested structure of the returned
     * nodes only.
     */
    public NodeList extractAllNodesThatMatch (NodeFilter filter)
    {
        return (extractAllNodesThatMatch (filter, false));
    }

    /**
     * Filter the list with the given filter.
     * @param filter The filter to use.
     * @param recursive If <code>true<code> digs into the children recursively.
     * @return A new node array containing the nodes accepted by the filter.
     * This is a linear list and preserves the nested structure of the returned
     * nodes only.
     */
    public NodeList extractAllNodesThatMatch (NodeFilter filter, boolean recursive)
    {
        Node node;
        NodeList children;
        NodeList ret;

        ret = new NodeList ();
        for (int i = 0; i < size; i++)
        {
            node = nodeData[i];
            if (filter.accept (node))
                ret.add (node);
            if (recursive)
            {
                children = node.getChildren ();
                if (null != children)
                    ret.add (children.extractAllNodesThatMatch (filter, recursive));
            }
        }

        return (ret);
    }

    /**
     * Remove nodes not matching the given filter non-recursively.
     * @param filter The filter to use.
     */
    public void keepAllNodesThatMatch (NodeFilter filter)
    {
        keepAllNodesThatMatch (filter, false);
    }

    /**
     * Remove nodes not matching the given filter.
     * @param filter The filter to use.
     * @param recursive If <code>true<code> digs into the children recursively.
     */
    public void keepAllNodesThatMatch (NodeFilter filter, boolean recursive)
    {
        Node node;
        NodeList children;

        for (int i = 0; i < size; )
        {
            node = nodeData[i];
            if (!filter.accept (node))
                remove (i);
            else
            {
                if (recursive)
                {
                    children = node.getChildren ();
                    if (null != children)
                        children.keepAllNodesThatMatch (filter, recursive);
                }
                i++;
            }
        }
    }

    /**
     * Utility to apply a visitor to a node list.
     * Provides for a visitor to modify the contents of a page and get the
     * modified HTML as a string with code like this:
     * <pre>
     * Parser parser = new Parser ("http://whatever");
     * NodeList list = parser.parse (null); // no filter
     * list.visitAllNodesWith (visitor);
     * System.out.println (list.toHtml ());
     * </pre>
     */
    public void visitAllNodesWith (NodeVisitor visitor)
        throws
            ParserException
    {
        Node node;

        visitor.beginParsing ();
        for (int i = 0; i < size; i++)
            nodeData[i].accept (visitor);
        visitor.finishedParsing ();
    }
}
