/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.bebop.jsp;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;

/**
 * Base class for JSP tag library to manipulate and display XML
 * generated from Bebop. This allows JSP authors to show Bebop pages
 * or components from within them within relatively "normal" JSP pages.
 * <p>
 * show:component may also be used directly in a JSP page:
 * <pre>
 * &lt;show:component name="bebopComponent"/>
 * </pre>
 * This will have the effect of finding an element in the XML
 * document whose name attribute is "bebopComponent" and copying
 * the subtree rooted at this element into the result document.
 */
public class ShowComponent extends BodyTagSupport implements JSPConstants {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/bebop/jsp/ShowComponent.java#9 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private String m_name;

    /**
     * Gets the result Document created by the show:page tag that the
     * current tag is contained within.
     */
    Document getResultDocument() {
        ShowPage pageTag = getPageTag();
        return pageTag.getResultDocument();
    }

    /**
     * Creates a TreeWalker from the input page object, given a NodeFilter
     * and a node to start the traversal from.
     *
     * @param filter the node filter to use for accepting/skipping/rejecting
     * nodes in the traversal.
     * @return a TreeWalker.
     */
    TreeWalker createTreeWalker(Node startFrom, NodeFilter filter) {
        ShowPage pageTag = getPageTag();
        DocumentTraversal dt = (DocumentTraversal)pageTag.getInputDocument();
        TreeWalker tw =
            dt.createTreeWalker(startFrom, NodeFilter.SHOW_ALL,
                                filter, false);
        return tw;
    }

    /**
     * Creates a TreeWalker from the input page object, given a NodeFilter.
     * The traversal starts from the input context set up by whatever
     * tag we're contained within.
     *
     * @param filter the node filter to use for accepting/skipping/rejecting
     * nodes in the traversal.
     * @return a TreeWalker.
     */
    TreeWalker createTreeWalker(NodeFilter filter) {
        ShowContainer parent = getContainerTag();
        return createTreeWalker(parent.getInputContext(), filter);
    }

    ShowPage getPageTag() {
        return (ShowPage)findAncestorWithClass(this, ShowPage.class);
    }

    ShowContainer getContainerTag() {
        return
            (ShowContainer)findAncestorWithClass(this, ShowContainer.class);
    }

    public int doStartTag() throws JspException {
        ShowContainer parent = getContainerTag();
        // handle static JSP/HTML text
        parent.handleText();
        return EVAL_BODY_TAG;
    }

    public int doEndTag() throws JspException {
        Node n = findFirstMatch(getName());
        if (n != null) {
            // we have a match.  do a deep copy
            // this is equivalent to XSLT xsl:copy-of
            Node toAdd = getResultDocument().importNode(n, true);
            ShowContainer parent = getContainerTag();
            parent.getOutputContext().appendChild(toAdd);
        }
        return EVAL_PAGE;
    }

    Node findFirstMatch(final String name) {
        // traverse from the input context node down until we find
        // the first node such that @name=$name or @id=$name
        NodeFilter nf = new NodeFilter() {
                public short acceptNode(Node n) {
                    if (n instanceof Element) {
                        Element elt = (Element)n;
                        if (elt.getAttribute("name").equals(name)
                            || elt.getAttribute("id").equals(name)) {
                            return FILTER_ACCEPT;
                        }
                    }
                    return FILTER_SKIP;
                }
            };
        TreeWalker walker = createTreeWalker(nf);
        Node n = walker.nextNode();
        if (n == null) {
            // no node available.
            // try to look up same in slave document.
            Node slaveNode = (Node)pageContext.getRequest()
                .getAttribute(SLAVE_INPUT_DOC);
            if (slaveNode != null) {
                walker = createTreeWalker(slaveNode, nf);
                n = walker.nextNode();
            }
        }
        return n;
    }

    // ---------- attributes ------------

    public String getName() {
        return m_name;
    }

    public void setName(String s) {
        m_name = s;
    }

}
