/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package com.cherry.lib.doc.office.fc.dom4j.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import com.cherry.lib.doc.office.fc.dom4j.Branch;
import com.cherry.lib.doc.office.fc.dom4j.Comment;
import com.cherry.lib.doc.office.fc.dom4j.Element;
import com.cherry.lib.doc.office.fc.dom4j.IllegalAddException;
import com.cherry.lib.doc.office.fc.dom4j.Namespace;
import com.cherry.lib.doc.office.fc.dom4j.Node;
import com.cherry.lib.doc.office.fc.dom4j.ProcessingInstruction;
import com.cherry.lib.doc.office.fc.dom4j.QName;


/**
 * <p>
 * <code>AbstractBranch</code> is an abstract base class for tree implementors
 * to use for implementation inheritence.
 * </p>
 * 
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 * @version $Revision: 1.44 $
 */
public abstract class AbstractBranch extends AbstractNode implements Branch
{
    protected static final int DEFAULT_CONTENT_LIST_SIZE = 5;

    public AbstractBranch()
    {
    }

    public boolean isReadOnly()
    {
        return false;
    }

    public boolean hasContent()
    {
        return nodeCount() > 0;
    }

    public List content()
    {
        List backingList = contentList();

        return new ContentListFacade(this, backingList);
    }

    public String getText()
    {
        List content = contentList();

        if (content != null)
        {
            int size = content.size();

            if (size >= 1)
            {
                Object first = content.get(0);
                String firstText = getContentAsText(first);

                if (size == 1)
                {
                    // optimised to avoid StringBuffer creation
                    return firstText;
                }
                else
                {
                    StringBuffer buffer = new StringBuffer(firstText);

                    for (int i = 1; i < size; i++)
                    {
                        Object node = content.get(i);
                        buffer.append(getContentAsText(node));
                    }

                    return buffer.toString();
                }
            }
        }

        return "";
    }

    /**
     * DOCUMENT ME!
     * 
     * @param content
     *            DOCUMENT ME!
     * 
     * @return the text value of the given content object as text which returns
     *         the text value of CDATA, Entity or Text nodes
     */
    protected String getContentAsText(Object content)
    {
        if (content instanceof Node)
        {
            Node node = (Node)content;

            switch (node.getNodeType())
            {
                case CDATA_SECTION_NODE:

                    // case ENTITY_NODE:
                case ENTITY_REFERENCE_NODE:
                case TEXT_NODE:
                    return node.getText();

                default:
                    break;
            }
        }
        else if (content instanceof String)
        {
            return (String)content;
        }

        return "";
    }

    /**
     * DOCUMENT ME!
     * 
     * @param content
     *            DOCUMENT ME!
     * 
     * @return the XPath defined string-value of the given content object
     */
    protected String getContentAsStringValue(Object content)
    {
        if (content instanceof Node)
        {
            Node node = (Node)content;

            switch (node.getNodeType())
            {
                case CDATA_SECTION_NODE:

                    // case ENTITY_NODE:
                case ENTITY_REFERENCE_NODE:
                case TEXT_NODE:
                case ELEMENT_NODE:
                    return node.getStringValue();

                default:
                    break;
            }
        }
        else if (content instanceof String)
        {
            return (String)content;
        }

        return "";
    }

    public String getTextTrim()
    {
        String text = getText();

        StringBuffer textContent = new StringBuffer();
        StringTokenizer tokenizer = new StringTokenizer(text);

        while (tokenizer.hasMoreTokens())
        {
            String str = tokenizer.nextToken();
            textContent.append(str);

            if (tokenizer.hasMoreTokens())
            {
                textContent.append(" "); // separator
            }
        }

        return textContent.toString();
    }

    public void setProcessingInstructions(List listOfPIs)
    {
        for (Iterator iter = listOfPIs.iterator(); iter.hasNext();)
        {
            ProcessingInstruction pi = (ProcessingInstruction)iter.next();
            addNode(pi);
        }
    }

    public Element addElement(String name)
    {
        Element node = getDocumentFactory().createElement(name);
        add(node);

        return node;
    }

    public Element addElement(String qualifiedName, String namespaceURI)
    {
        Element node = getDocumentFactory().createElement(qualifiedName, namespaceURI);
        add(node);

        return node;
    }

    public Element addElement(QName qname)
    {
        Element node = getDocumentFactory().createElement(qname);
        add(node);

        return node;
    }

    public Element addElement(String name, String prefix, String uri)
    {
        Namespace namespace = Namespace.get(prefix, uri);
        QName qName = getDocumentFactory().createQName(name, namespace);

        return addElement(qName);
    }

    // polymorphic node methods
    public void add(Node node)
    {
        switch (node.getNodeType())
        {
            case ELEMENT_NODE:
                add((Element)node);

                break;

            case COMMENT_NODE:
                add((Comment)node);

                break;

            case PROCESSING_INSTRUCTION_NODE:
                add((ProcessingInstruction)node);

                break;

            default:
                invalidNodeTypeAddException(node);
        }
    }

    public boolean remove(Node node)
    {
        switch (node.getNodeType())
        {
            case ELEMENT_NODE:
                return remove((Element)node);

            case COMMENT_NODE:
                return remove((Comment)node);

            case PROCESSING_INSTRUCTION_NODE:
                return remove((ProcessingInstruction)node);

            default:
                invalidNodeTypeAddException(node);

                return false;
        }
    }

    // typesafe versions using node classes
    public void add(Comment comment)
    {
        addNode(comment);
    }

    public void add(Element element)
    {
        addNode(element);
    }

    public void add(ProcessingInstruction pi)
    {
        addNode(pi);
    }

    public boolean remove(Comment comment)
    {
        return removeNode(comment);
    }

    public boolean remove(Element element)
    {
        return removeNode(element);
    }

    public boolean remove(ProcessingInstruction pi)
    {
        return removeNode(pi);
    }

    public Element elementByID(String elementID)
    {
        for (int i = 0, size = nodeCount(); i < size; i++)
        {
            Node node = node(i);

            if (node instanceof Element)
            {
                Element element = (Element)node;
                String id = elementID(element);

                if ((id != null) && id.equals(elementID))
                {
                    return element;
                }
                else
                {
                    element = element.elementByID(elementID);

                    if (element != null)
                    {
                        return element;
                    }
                }
            }
        }

        return null;
    }

    public void appendContent(Branch branch)
    {
        for (int i = 0, size = branch.nodeCount(); i < size; i++)
        {
            Node node = branch.node(i);
            add((Node)node.clone());
        }
    }

    public Node node(int index)
    {
        Object object = contentList().get(index);

        if (object instanceof Node)
        {
            return (Node)object;
        }

        if (object instanceof String)
        {
            return getDocumentFactory().createText(object.toString());
        }

        return null;
    }

    public int nodeCount()
    {
        return contentList().size();
    }

    public int indexOf(Node node)
    {
        return contentList().indexOf(node);
    }

    public Iterator nodeIterator()
    {
        return contentList().iterator();
    }

    // Implementation methods

    /**
     * DOCUMENT ME!
     * 
     * @param element
     *            DOCUMENT ME!
     * 
     * @return the ID of the given <code>Element</code>
     */
    protected String elementID(Element element)
    {
        // XXX: there will be other ways of finding the ID
        // XXX: should probably have an IDResolver or something
        return element.attributeValue("ID");
    }

    /**
     * DOCUMENT ME!
     * 
     * @return the internal List used to manage the content
     */
    protected abstract List contentList();

    /**
     * A Factory Method pattern which creates a List implementation used to
     * store content
     * 
     * @return DOCUMENT ME!
     */
    protected List createContentList()
    {
        return new ArrayList(DEFAULT_CONTENT_LIST_SIZE);
    }

    /**
     * A Factory Method pattern which creates a List implementation used to
     * store content
     * 
     * @param size
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    protected List createContentList(int size)
    {
        return new ArrayList(size);
    }

    /**
     * A Factory Method pattern which creates a BackedList implementation used
     * to store results of a filtered content query.
     * 
     * @return DOCUMENT ME!
     */
    protected BackedList createResultList()
    {
        return new BackedList(this, contentList());
    }

    /**
     * A Factory Method pattern which creates a BackedList implementation which
     * contains a single result
     * 
     * @param result
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    protected List createSingleResultList(Object result)
    {
        BackedList list = new BackedList(this, contentList(), 1);
        list.addLocal(result);

        return list;
    }

    /**
     * A Factory Method pattern which creates an empty a BackedList
     * implementation
     * 
     * @return DOCUMENT ME!
     */
    protected List createEmptyList()
    {
        return new BackedList(this, contentList(), 0);
    }

    protected abstract void addNode(Node node);

    protected abstract void addNode(int index, Node node);

    protected abstract boolean removeNode(Node node);

    /**
     * Called when a new child node has been added to me to allow any parent
     * relationships to be created or events to be fired.
     * 
     * @param node
     *            DOCUMENT ME!
     */
    protected abstract void childAdded(Node node);

    /**
     * Called when a child node has been removed to allow any parent
     * relationships to be deleted or events to be fired.
     * 
     * @param node
     *            DOCUMENT ME!
     */
    protected abstract void childRemoved(Node node);

    /**
     * Called when the given List content has been removed so each node should
     * have its parent and document relationships cleared
     */
    protected void contentRemoved()
    {
        List content = contentList();

        for (int i = 0, size = content.size(); i < size; i++)
        {
            Object object = content.get(i);

            if (object instanceof Node)
            {
                childRemoved((Node)object);
            }
        }
    }

    /**
     * Called when an invalid node has been added. Throws an {@link
     * IllegalAddException}.
     * 
     * @param node
     *            DOCUMENT ME!
     * 
     * @throws IllegalAddException
     *             DOCUMENT ME!
     */
    protected void invalidNodeTypeAddException(Node node)
    {
        throw new IllegalAddException("Invalid node type. Cannot add node: " + node
            + " to this branch: " + this);
    }
}

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 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 name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``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 METASTUFF, LTD. 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.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
