/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.struts2.jasper.compiler;

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

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.IterationTag;
import javax.servlet.jsp.tagext.JspIdConsumer;
import javax.servlet.jsp.tagext.SimpleTag;
import javax.servlet.jsp.tagext.TagAttributeInfo;
import javax.servlet.jsp.tagext.TagData;
import javax.servlet.jsp.tagext.TagFileInfo;
import javax.servlet.jsp.tagext.TagInfo;
import javax.servlet.jsp.tagext.TagVariableInfo;
import javax.servlet.jsp.tagext.TryCatchFinally;
import javax.servlet.jsp.tagext.VariableInfo;

import org.apache.struts2.jasper.Constants;
import org.apache.struts2.jasper.JasperException;
import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
import org.xml.sax.Attributes;

/**
 * An internal data representation of a JSP page or a JSP docuement (XML). Also
 * included here is a visitor class for tranversing nodes.
 * 
 * @author Kin-man Chung
 * @author Jan Luehe
 * @author Shawn Bayern
 * @author Mark Roth
 */

abstract class Node implements TagConstants {

    private static final VariableInfo[] ZERO_VARIABLE_INFO = {};

    protected Attributes attrs;

    // xmlns attributes that represent tag libraries (only in XML syntax)
    protected Attributes taglibAttrs;

    /*
     * xmlns attributes that do not represent tag libraries (only in XML syntax)
     */
    protected Attributes nonTaglibXmlnsAttrs;

    protected Nodes body;

    protected String text;

    protected Mark startMark;

    protected int beginJavaLine;

    protected int endJavaLine;

    protected Node parent;

    protected Nodes namedAttributeNodes; // cached for performance

    protected String qName;

    protected String localName;

    /*
     * The name of the inner class to which the codes for this node and its body
     * are generated. For instance, for <jsp:body> in foo.jsp, this is
     * "foo_jspHelper". This is primarily used for communicating such info from
     * Generator to Smap generator.
     */
    protected String innerClassName;

    private boolean isDummy;

    /**
     * Zero-arg Constructor.
     */
    public Node() {
        this.isDummy = true;
    }

    /**
     * Constructor.
     * 
     * @param start
     *            The location of the jsp page
     * @param parent
     *            The enclosing node
     */
    public Node(Mark start, Node parent) {
        this.startMark = start;
        this.isDummy = (start == null);
        addToParent(parent);
    }

    /**
     * Constructor.
     * 
     * @param qName
     *            The action's qualified name
     * @param localName
     *            The action's local name
     * @param start
     *            The location of the jsp page
     * @param parent
     *            The enclosing node
     */
    public Node(String qName, String localName, Mark start, Node parent) {
        this.qName = qName;
        this.localName = localName;
        this.startMark = start;
        this.isDummy = (start == null);
        addToParent(parent);
    }

    /**
     * Constructor for Nodes parsed from standard syntax.
     * 
     * @param qName
     *            The action's qualified name
     * @param localName
     *            The action's local name
     * @param attrs
     *            The attributes for this node
     * @param start
     *            The location of the jsp page
     * @param parent
     *            The enclosing node
     */
    public Node(String qName, String localName, Attributes attrs, Mark start,
            Node parent) {
        this.qName = qName;
        this.localName = localName;
        this.attrs = attrs;
        this.startMark = start;
        this.isDummy = (start == null);
        addToParent(parent);
    }

    /**
     * Constructor for Nodes parsed from XML syntax.
     * 
     * @param qName
     *            The action's qualified name
     * @param localName
     *            The action's local name
     * @param attrs
     *            The action's attributes whose name does not start with xmlns
     * @param nonTaglibXmlnsAttrs
     *            The action's xmlns attributes that do not represent tag
     *            libraries
     * @param taglibAttrs
     *            The action's xmlns attributes that represent tag libraries
     * @param start
     *            The location of the jsp page
     * @param parent
     *            The enclosing node
     */
    public Node(String qName, String localName, Attributes attrs,
            Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start,
            Node parent) {
        this.qName = qName;
        this.localName = localName;
        this.attrs = attrs;
        this.nonTaglibXmlnsAttrs = nonTaglibXmlnsAttrs;
        this.taglibAttrs = taglibAttrs;
        this.startMark = start;
        this.isDummy = (start == null);
        addToParent(parent);
    }

    /*
     * Constructor.
     * 
     * @param qName The action's qualified name @param localName The action's
     * local name @param text The text associated with this node @param start
     * The location of the jsp page @param parent The enclosing node
     */
    public Node(String qName, String localName, String text, Mark start,
            Node parent) {
        this.qName = qName;
        this.localName = localName;
        this.text = text;
        this.startMark = start;
        this.isDummy = (start == null);
        addToParent(parent);
    }

    public String getQName() {
        return this.qName;
    }

    public String getLocalName() {
        return this.localName;
    }

    /*
     * Gets this Node's attributes.
     * 
     * In the case of a Node parsed from standard syntax, this method returns
     * all the Node's attributes.
     * 
     * In the case of a Node parsed from XML syntax, this method returns only
     * those attributes whose name does not start with xmlns.
     */
    public Attributes getAttributes() {
        return this.attrs;
    }

    /*
     * Gets this Node's xmlns attributes that represent tag libraries (only
     * meaningful for Nodes parsed from XML syntax)
     */
    public Attributes getTaglibAttributes() {
        return this.taglibAttrs;
    }

    /*
     * Gets this Node's xmlns attributes that do not represent tag libraries
     * (only meaningful for Nodes parsed from XML syntax)
     */
    public Attributes getNonTaglibXmlnsAttributes() {
        return this.nonTaglibXmlnsAttrs;
    }

    public void setAttributes(Attributes attrs) {
        this.attrs = attrs;
    }

    public String getAttributeValue(String name) {
        return (attrs == null) ? null : attrs.getValue(name);
    }

    /**
     * Get the attribute that is non request time expression, either from the
     * attribute of the node, or from a jsp:attrbute
     */
    public String getTextAttribute(String name) {

        String attr = getAttributeValue(name);
        if (attr != null) {
            return attr;
        }

        NamedAttribute namedAttribute = getNamedAttributeNode(name);
        if (namedAttribute == null) {
            return null;
        }

        return namedAttribute.getText();
    }

    /**
     * Searches all subnodes of this node for jsp:attribute standard actions
     * with the given name, and returns the NamedAttribute node of the matching
     * named attribute, nor null if no such node is found.
     * <p>
     * This should always be called and only be called for nodes that accept
     * dynamic runtime attribute expressions.
     */
    public NamedAttribute getNamedAttributeNode(String name) {
        NamedAttribute result = null;

        // Look for the attribute in NamedAttribute children
        Nodes nodes = getNamedAttributeNodes();
        int numChildNodes = nodes.size();
        for (int i = 0; i < numChildNodes; i++) {
            NamedAttribute na = (NamedAttribute) nodes.getNode(i);
            boolean found = false;
            int index = name.indexOf(':');
            if (index != -1) {
                // qualified name
                found = na.getName().equals(name);
            } else {
                found = na.getLocalName().equals(name);
            }
            if (found) {
                result = na;
                break;
            }
        }

        return result;
    }

    /**
     * Searches all subnodes of this node for jsp:attribute standard actions,
     * and returns that set of nodes as a Node.Nodes object.
     * 
     * @return Possibly empty Node.Nodes object containing any jsp:attribute
     *         subnodes of this Node
     */
    public Node.Nodes getNamedAttributeNodes() {

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

        Node.Nodes result = new Node.Nodes();

        // Look for the attribute in NamedAttribute children
        Nodes nodes = getBody();
        if (nodes != null) {
            int numChildNodes = nodes.size();
            for (int i = 0; i < numChildNodes; i++) {
                Node n = nodes.getNode(i);
                if (n instanceof NamedAttribute) {
                    result.add(n);
                } else if (!(n instanceof Comment)) {
                    // Nothing can come before jsp:attribute, and only
                    // jsp:body can come after it.
                    break;
                }
            }
        }

        namedAttributeNodes = result;
        return result;
    }

    public Nodes getBody() {
        return body;
    }

    public void setBody(Nodes body) {
        this.body = body;
    }

    public String getText() {
        return text;
    }

    public Mark getStart() {
        return startMark;
    }

    public Node getParent() {
        return parent;
    }

    public int getBeginJavaLine() {
        return beginJavaLine;
    }

    public void setBeginJavaLine(int begin) {
        beginJavaLine = begin;
    }

    public int getEndJavaLine() {
        return endJavaLine;
    }

    public void setEndJavaLine(int end) {
        endJavaLine = end;
    }

    public boolean isDummy() {
        return isDummy;
    }

    public Node.Root getRoot() {
        Node n = this;
        while (!(n instanceof Node.Root)) {
            n = n.getParent();
        }
        return (Node.Root) n;
    }

    public String getInnerClassName() {
        return innerClassName;
    }

    public void setInnerClassName(String icn) {
        innerClassName = icn;
    }

    /**
     * Selects and invokes a method in the visitor class based on the node type.
     * This is abstract and should be overrode by the extending classes.
     * 
     * @param v
     *            The visitor class
     */
    abstract void accept(Visitor v) throws JasperException;

    // *********************************************************************
    // Private utility methods

    /*
     * Adds this Node to the body of the given parent.
     */
    private void addToParent(Node parent) {
        if (parent != null) {
            this.parent = parent;
            Nodes parentBody = parent.getBody();
            if (parentBody == null) {
                parentBody = new Nodes();
                parent.setBody(parentBody);
            }
            parentBody.add(this);
        }
    }

    /***************************************************************************
     * Child classes
     */

    /**
     * Represents the root of a Jsp page or Jsp document
     */
    public static class Root extends Node {

        private Root parentRoot;

        private boolean isXmlSyntax;

        // Source encoding of the page containing this Root
        private String pageEnc;

        // Page encoding specified in JSP config element
        private String jspConfigPageEnc;

        /*
         * Flag indicating if the default page encoding is being used (only
         * applicable with standard syntax).
         * 
         * True if the page does not provide a page directive with a
         * 'contentType' attribute (or the 'contentType' attribute doesn't have
         * a CHARSET value), the page does not provide a page directive with a
         * 'pageEncoding' attribute, and there is no JSP configuration element
         * page-encoding whose URL pattern matches the page.
         */
        private boolean isDefaultPageEncoding;

        /*
         * Indicates whether an encoding has been explicitly specified in the
         * page's XML prolog (only used for pages in XML syntax). This
         * information is used to decide whether a translation error must be
         * reported for encoding conflicts.
         */
        private boolean isEncodingSpecifiedInProlog;

        /*
         * Indicates whether an encoding has been explicitly specified in the
         * page's bom.
         */
        private boolean isBomPresent;

        /*
         * Sequence number for temporary variables.
         */
        private int tempSequenceNumber = 0;

        /*
         * Constructor.
         */
        Root(Mark start, Node parent, boolean isXmlSyntax) {
            super(start, parent);
            this.isXmlSyntax = isXmlSyntax;
            this.qName = JSP_ROOT_ACTION;
            this.localName = ROOT_ACTION;

            // Figure out and set the parent root
            Node r = parent;
            while ((r != null) && !(r instanceof Node.Root))
                r = r.getParent();
            parentRoot = (Node.Root) r;
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public boolean isXmlSyntax() {
            return isXmlSyntax;
        }

        /*
         * Sets the encoding specified in the JSP config element whose URL
         * pattern matches the page containing this Root.
         */
        public void setJspConfigPageEncoding(String enc) {
            jspConfigPageEnc = enc;
        }

        /*
         * Gets the encoding specified in the JSP config element whose URL
         * pattern matches the page containing this Root.
         */
        public String getJspConfigPageEncoding() {
            return jspConfigPageEnc;
        }

        public void setPageEncoding(String enc) {
            pageEnc = enc;
        }

        public String getPageEncoding() {
            return pageEnc;
        }

        public void setIsDefaultPageEncoding(boolean isDefault) {
            isDefaultPageEncoding = isDefault;
        }

        public boolean isDefaultPageEncoding() {
            return isDefaultPageEncoding;
        }

        public void setIsEncodingSpecifiedInProlog(boolean isSpecified) {
            isEncodingSpecifiedInProlog = isSpecified;
        }

        public boolean isEncodingSpecifiedInProlog() {
            return isEncodingSpecifiedInProlog;
        }

        public void setIsBomPresent(boolean isBom) {
            isBomPresent = isBom;
        }

        public boolean isBomPresent() {
            return isBomPresent;
        }

        /**
         * @return The enclosing root to this Root. Usually represents the page
         *         that includes this one.
         */
        public Root getParentRoot() {
            return parentRoot;
        }
        
        /**
         * Generates a new temporary variable name.
         */
        public String nextTemporaryVariableName() {
            if (parentRoot == null) {
                return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
            } else {
                return parentRoot.nextTemporaryVariableName();
            }
            
        }
    }

    /**
     * Represents the root of a Jsp document (XML syntax)
     */
    public static class JspRoot extends Node {

        public JspRoot(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, ROOT_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a page directive
     */
    public static class PageDirective extends Node {

        private Vector imports;

        public PageDirective(Attributes attrs, Mark start, Node parent) {
            this(JSP_PAGE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
        }

        public PageDirective(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, PAGE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
            imports = new Vector();
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        /**
         * Parses the comma-separated list of class or package names in the
         * given attribute value and adds each component to this PageDirective's
         * vector of imported classes and packages.
         * 
         * @param value
         *            A comma-separated string of imports.
         */
        public void addImport(String value) {
            int start = 0;
            int index;
            while ((index = value.indexOf(',', start)) != -1) {
                imports.add(value.substring(start, index).trim());
                start = index + 1;
            }
            if (start == 0) {
                // No comma found
                imports.add(value.trim());
            } else {
                imports.add(value.substring(start).trim());
            }
        }

        public List getImports() {
            return imports;
        }
    }

    /**
     * Represents an include directive
     */
    public static class IncludeDirective extends Node {

        public IncludeDirective(Attributes attrs, Mark start, Node parent) {
            this(JSP_INCLUDE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
        }

        public IncludeDirective(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, INCLUDE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a custom taglib directive
     */
    public static class TaglibDirective extends Node {

        public TaglibDirective(Attributes attrs, Mark start, Node parent) {
            super(JSP_TAGLIB_DIRECTIVE_ACTION, TAGLIB_DIRECTIVE_ACTION, attrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a tag directive
     */
    public static class TagDirective extends Node {
        private Vector imports;

        public TagDirective(Attributes attrs, Mark start, Node parent) {
            this(JSP_TAG_DIRECTIVE_ACTION, attrs, null, null, start, parent);
        }

        public TagDirective(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, TAG_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
            imports = new Vector();
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        /**
         * Parses the comma-separated list of class or package names in the
         * given attribute value and adds each component to this PageDirective's
         * vector of imported classes and packages.
         * 
         * @param value
         *            A comma-separated string of imports.
         */
        public void addImport(String value) {
            int start = 0;
            int index;
            while ((index = value.indexOf(',', start)) != -1) {
                imports.add(value.substring(start, index).trim());
                start = index + 1;
            }
            if (start == 0) {
                // No comma found
                imports.add(value.trim());
            } else {
                imports.add(value.substring(start).trim());
            }
        }

        public List getImports() {
            return imports;
        }
    }

    /**
     * Represents an attribute directive
     */
    public static class AttributeDirective extends Node {

        public AttributeDirective(Attributes attrs, Mark start, Node parent) {
            this(JSP_ATTRIBUTE_DIRECTIVE_ACTION, attrs, null, null, start,
                    parent);
        }

        public AttributeDirective(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, ATTRIBUTE_DIRECTIVE_ACTION, attrs,
                    nonTaglibXmlnsAttrs, taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a variable directive
     */
    public static class VariableDirective extends Node {

        public VariableDirective(Attributes attrs, Mark start, Node parent) {
            this(JSP_VARIABLE_DIRECTIVE_ACTION, attrs, null, null, start,
                    parent);
        }

        public VariableDirective(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, VARIABLE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a <jsp:invoke> tag file action
     */
    public static class InvokeAction extends Node {

        public InvokeAction(Attributes attrs, Mark start, Node parent) {
            this(JSP_INVOKE_ACTION, attrs, null, null, start, parent);
        }

        public InvokeAction(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, INVOKE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a <jsp:doBody> tag file action
     */
    public static class DoBodyAction extends Node {

        public DoBodyAction(Attributes attrs, Mark start, Node parent) {
            this(JSP_DOBODY_ACTION, attrs, null, null, start, parent);
        }

        public DoBodyAction(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, DOBODY_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a Jsp comment Comments are kept for completeness.
     */
    public static class Comment extends Node {

        public Comment(String text, Mark start, Node parent) {
            super(null, null, text, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents an expression, declaration, or scriptlet
     */
    public static abstract class ScriptingElement extends Node {

        public ScriptingElement(String qName, String localName, String text,
                Mark start, Node parent) {
            super(qName, localName, text, start, parent);
        }

        public ScriptingElement(String qName, String localName,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, localName, null, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        /**
         * When this node was created from a JSP page in JSP syntax, its text
         * was stored as a String in the "text" field, whereas when this node
         * was created from a JSP document, its text was stored as one or more
         * TemplateText nodes in its body. This method handles either case.
         * 
         * @return The text string
         */
        public String getText() {
            String ret = text;
            if (ret == null) {
                if (body != null) {
                    StringBuffer buf = new StringBuffer();
                    for (int i = 0; i < body.size(); i++) {
                        buf.append(body.getNode(i).getText());
                    }
                    ret = buf.toString();
                } else {
                    // Nulls cause NPEs further down the line
                    ret = "";
                }
            }
            return ret;
        }

        /**
         * For the same reason as above, the source line information in the
         * contained TemplateText node should be used.
         */
        public Mark getStart() {
            if (text == null && body != null && body.size() > 0) {
                return body.getNode(0).getStart();
            } else {
                return super.getStart();
            }
        }
    }

    /**
     * Represents a declaration
     */
    public static class Declaration extends ScriptingElement {

        public Declaration(String text, Mark start, Node parent) {
            super(JSP_DECLARATION_ACTION, DECLARATION_ACTION, text, start,
                    parent);
        }

        public Declaration(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, DECLARATION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents an expression. Expressions in attributes are embedded in the
     * attribute string and not here.
     */
    public static class Expression extends ScriptingElement {

        public Expression(String text, Mark start, Node parent) {
            super(JSP_EXPRESSION_ACTION, EXPRESSION_ACTION, text, start, parent);
        }

        public Expression(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, EXPRESSION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a scriptlet
     */
    public static class Scriptlet extends ScriptingElement {

        public Scriptlet(String text, Mark start, Node parent) {
            super(JSP_SCRIPTLET_ACTION, SCRIPTLET_ACTION, text, start, parent);
        }

        public Scriptlet(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, SCRIPTLET_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents an EL expression. Expressions in attributes are embedded in
     * the attribute string and not here.
     */
    public static class ELExpression extends Node {

        private ELNode.Nodes el;

        private final char type;

        public ELExpression(char type, String text, Mark start, Node parent) {
            super(null, null, text, start, parent);
            this.type = type;
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setEL(ELNode.Nodes el) {
            this.el = el;
        }

        public ELNode.Nodes getEL() {
            return el;
        }

        public char getType() {
            return this.type;
        }
    }

    /**
     * Represents a param action
     */
    public static class ParamAction extends Node {

        JspAttribute value;

        public ParamAction(Attributes attrs, Mark start, Node parent) {
            this(JSP_PARAM_ACTION, attrs, null, null, start, parent);
        }

        public ParamAction(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, PARAM_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setValue(JspAttribute value) {
            this.value = value;
        }

        public JspAttribute getValue() {
            return value;
        }
    }

    /**
     * Represents a params action
     */
    public static class ParamsAction extends Node {

        public ParamsAction(Mark start, Node parent) {
            this(JSP_PARAMS_ACTION, null, null, start, parent);
        }

        public ParamsAction(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, PARAMS_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a fallback action
     */
    public static class FallBackAction extends Node {

        public FallBackAction(Mark start, Node parent) {
            this(JSP_FALLBACK_ACTION, null, null, start, parent);
        }

        public FallBackAction(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, FALLBACK_ACTION, null, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents an include action
     */
    public static class IncludeAction extends Node {

        private JspAttribute page;

        public IncludeAction(Attributes attrs, Mark start, Node parent) {
            this(JSP_INCLUDE_ACTION, attrs, null, null, start, parent);
        }

        public IncludeAction(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, INCLUDE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setPage(JspAttribute page) {
            this.page = page;
        }

        public JspAttribute getPage() {
            return page;
        }
    }

    /**
     * Represents a forward action
     */
    public static class ForwardAction extends Node {

        private JspAttribute page;

        public ForwardAction(Attributes attrs, Mark start, Node parent) {
            this(JSP_FORWARD_ACTION, attrs, null, null, start, parent);
        }

        public ForwardAction(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, FORWARD_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setPage(JspAttribute page) {
            this.page = page;
        }

        public JspAttribute getPage() {
            return page;
        }
    }

    /**
     * Represents a getProperty action
     */
    public static class GetProperty extends Node {

        public GetProperty(Attributes attrs, Mark start, Node parent) {
            this(JSP_GET_PROPERTY_ACTION, attrs, null, null, start, parent);
        }

        public GetProperty(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, GET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a setProperty action
     */
    public static class SetProperty extends Node {

        private JspAttribute value;

        public SetProperty(Attributes attrs, Mark start, Node parent) {
            this(JSP_SET_PROPERTY_ACTION, attrs, null, null, start, parent);
        }

        public SetProperty(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, SET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setValue(JspAttribute value) {
            this.value = value;
        }

        public JspAttribute getValue() {
            return value;
        }
    }

    /**
     * Represents a useBean action
     */
    public static class UseBean extends Node {

        JspAttribute beanName;

        public UseBean(Attributes attrs, Mark start, Node parent) {
            this(JSP_USE_BEAN_ACTION, attrs, null, null, start, parent);
        }

        public UseBean(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, USE_BEAN_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setBeanName(JspAttribute beanName) {
            this.beanName = beanName;
        }

        public JspAttribute getBeanName() {
            return beanName;
        }
    }

    /**
     * Represents a plugin action
     */
    public static class PlugIn extends Node {

        private JspAttribute width;

        private JspAttribute height;

        public PlugIn(Attributes attrs, Mark start, Node parent) {
            this(JSP_PLUGIN_ACTION, attrs, null, null, start, parent);
        }

        public PlugIn(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, PLUGIN_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setHeight(JspAttribute height) {
            this.height = height;
        }

        public void setWidth(JspAttribute width) {
            this.width = width;
        }

        public JspAttribute getHeight() {
            return height;
        }

        public JspAttribute getWidth() {
            return width;
        }
    }

    /**
     * Represents an uninterpreted tag, from a Jsp document
     */
    public static class UninterpretedTag extends Node {

        private JspAttribute[] jspAttrs;

        public UninterpretedTag(String qName, String localName,
                Attributes attrs, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setJspAttributes(JspAttribute[] jspAttrs) {
            this.jspAttrs = jspAttrs;
        }

        public JspAttribute[] getJspAttributes() {
            return jspAttrs;
        }
    }

    /**
     * Represents a <jsp:element>.
     */
    public static class JspElement extends Node {

        private JspAttribute[] jspAttrs;

        private JspAttribute nameAttr;

        public JspElement(Attributes attrs, Mark start, Node parent) {
            this(JSP_ELEMENT_ACTION, attrs, null, null, start, parent);
        }

        public JspElement(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, ELEMENT_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public void setJspAttributes(JspAttribute[] jspAttrs) {
            this.jspAttrs = jspAttrs;
        }

        public JspAttribute[] getJspAttributes() {
            return jspAttrs;
        }

        /*
         * Sets the XML-style 'name' attribute
         */
        public void setNameAttribute(JspAttribute nameAttr) {
            this.nameAttr = nameAttr;
        }

        /*
         * Gets the XML-style 'name' attribute
         */
        public JspAttribute getNameAttribute() {
            return this.nameAttr;
        }
    }

    /**
     * Represents a <jsp:output>.
     */
    public static class JspOutput extends Node {

        public JspOutput(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {
            super(qName, OUTPUT_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Collected information about child elements. Used by nodes like CustomTag,
     * JspBody, and NamedAttribute. The information is set in the Collector.
     */
    public static class ChildInfo {
        private boolean scriptless; // true if the tag and its body

        // contain no scripting elements.
        private boolean hasUseBean;

        private boolean hasIncludeAction;

        private boolean hasParamAction;

        private boolean hasSetProperty;

        private boolean hasScriptingVars;

        public void setScriptless(boolean s) {
            scriptless = s;
        }

        public boolean isScriptless() {
            return scriptless;
        }

        public void setHasUseBean(boolean u) {
            hasUseBean = u;
        }

        public boolean hasUseBean() {
            return hasUseBean;
        }

        public void setHasIncludeAction(boolean i) {
            hasIncludeAction = i;
        }

        public boolean hasIncludeAction() {
            return hasIncludeAction;
        }

        public void setHasParamAction(boolean i) {
            hasParamAction = i;
        }

        public boolean hasParamAction() {
            return hasParamAction;
        }

        public void setHasSetProperty(boolean s) {
            hasSetProperty = s;
        }

        public boolean hasSetProperty() {
            return hasSetProperty;
        }

        public void setHasScriptingVars(boolean s) {
            hasScriptingVars = s;
        }

        public boolean hasScriptingVars() {
            return hasScriptingVars;
        }
    }

    /**
     * Represents a custom tag
     */
    public static class CustomTag extends Node {

        private String uri;

        private String prefix;

        private JspAttribute[] jspAttrs;

        private TagData tagData;

        private String tagHandlerPoolName;

        private TagInfo tagInfo;

        private TagFileInfo tagFileInfo;

        private Class tagHandlerClass;

        private VariableInfo[] varInfos;

        private int customNestingLevel;

        private ChildInfo childInfo;

        private boolean implementsIterationTag;

        private boolean implementsBodyTag;

        private boolean implementsTryCatchFinally;

        private boolean implementsJspIdConsumer;

        private boolean implementsSimpleTag;

        private boolean implementsDynamicAttributes;

        private Vector atBeginScriptingVars;

        private Vector atEndScriptingVars;

        private Vector nestedScriptingVars;

        private Node.CustomTag customTagParent;

        private Integer numCount;

        private boolean useTagPlugin;

        private TagPluginContext tagPluginContext;

        /**
         * The following two fields are used for holding the Java scriptlets
         * that the tag plugins may generate. Meaningful only if useTagPlugin is
         * true; Could move them into TagPluginContextImpl, but we'll need to
         * cast tagPluginContext to TagPluginContextImpl all the time...
         */
        private Nodes atSTag;

        private Nodes atETag;

        /*
         * Constructor for custom action implemented by tag handler.
         */
        public CustomTag(String qName, String prefix, String localName,
                String uri, Attributes attrs, Mark start, Node parent,
                TagInfo tagInfo, Class tagHandlerClass) {
            this(qName, prefix, localName, uri, attrs, null, null, start,
                    parent, tagInfo, tagHandlerClass);
        }

        /*
         * Constructor for custom action implemented by tag handler.
         */
        public CustomTag(String qName, String prefix, String localName,
                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent,
                TagInfo tagInfo, Class tagHandlerClass) {
            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);

            this.uri = uri;
            this.prefix = prefix;
            this.tagInfo = tagInfo;
            this.tagHandlerClass = tagHandlerClass;
            this.customNestingLevel = makeCustomNestingLevel();
            this.childInfo = new ChildInfo();

            this.implementsIterationTag = IterationTag.class
                    .isAssignableFrom(tagHandlerClass);
            this.implementsBodyTag = BodyTag.class
                    .isAssignableFrom(tagHandlerClass);
            this.implementsTryCatchFinally = TryCatchFinally.class
                    .isAssignableFrom(tagHandlerClass);
            this.implementsSimpleTag = SimpleTag.class
                    .isAssignableFrom(tagHandlerClass);
            this.implementsDynamicAttributes = DynamicAttributes.class
                    .isAssignableFrom(tagHandlerClass);
            this.implementsJspIdConsumer = JspIdConsumer.class
                    .isAssignableFrom(tagHandlerClass);
        }

        /*
         * Constructor for custom action implemented by tag file.
         */
        public CustomTag(String qName, String prefix, String localName,
                String uri, Attributes attrs, Mark start, Node parent,
                TagFileInfo tagFileInfo) {
            this(qName, prefix, localName, uri, attrs, null, null, start,
                    parent, tagFileInfo);
        }

        /*
         * Constructor for custom action implemented by tag file.
         */
        public CustomTag(String qName, String prefix, String localName,
                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent,
                TagFileInfo tagFileInfo) {

            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);

            this.uri = uri;
            this.prefix = prefix;
            this.tagFileInfo = tagFileInfo;
            this.tagInfo = tagFileInfo.getTagInfo();
            this.customNestingLevel = makeCustomNestingLevel();
            this.childInfo = new ChildInfo();

            this.implementsIterationTag = false;
            this.implementsBodyTag = false;
            this.implementsTryCatchFinally = false;
            this.implementsSimpleTag = true;
            this.implementsJspIdConsumer = false;
            this.implementsDynamicAttributes = tagInfo.hasDynamicAttributes();
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        /**
         * @return The URI namespace that this custom action belongs to
         */
        public String getURI() {
            return this.uri;
        }

        /**
         * @return The tag prefix
         */
        public String getPrefix() {
            return prefix;
        }

        public void setJspAttributes(JspAttribute[] jspAttrs) {
            this.jspAttrs = jspAttrs;
        }

        public TagAttributeInfo getTagAttributeInfo(String name) {
            TagInfo info = this.getTagInfo();
            if (info == null)
                return null;
            TagAttributeInfo[] tai = info.getAttributes();
            for (int i = 0; i < tai.length; i++) {
                if (tai[i].getName().equals(name)) {
                    return tai[i];
                }
            }
            return null;
        }

        public JspAttribute[] getJspAttributes() {
            return jspAttrs;
        }

        public ChildInfo getChildInfo() {
            return childInfo;
        }

        public void setTagData(TagData tagData) {
            this.tagData = tagData;
            this.varInfos = tagInfo.getVariableInfo(tagData);
            if (this.varInfos == null) {
                this.varInfos = ZERO_VARIABLE_INFO;
            }
        }

        public TagData getTagData() {
            return tagData;
        }

        public void setTagHandlerPoolName(String s) {
            tagHandlerPoolName = s;
        }

        public String getTagHandlerPoolName() {
            return tagHandlerPoolName;
        }

        public TagInfo getTagInfo() {
            return tagInfo;
        }

        public TagFileInfo getTagFileInfo() {
            return tagFileInfo;
        }

        /*
         * @return true if this custom action is supported by a tag file, false
         * otherwise
         */
        public boolean isTagFile() {
            return tagFileInfo != null;
        }

        public Class getTagHandlerClass() {
            return tagHandlerClass;
        }

        public void setTagHandlerClass(Class hc) {
            tagHandlerClass = hc;
        }

        public boolean implementsIterationTag() {
            return implementsIterationTag;
        }

        public boolean implementsBodyTag() {
            return implementsBodyTag;
        }

        public boolean implementsTryCatchFinally() {
            return implementsTryCatchFinally;
        }

        public boolean implementsJspIdConsumer() {
            return implementsJspIdConsumer;
        }

        public boolean implementsSimpleTag() {
            return implementsSimpleTag;
        }

        public boolean implementsDynamicAttributes() {
            return implementsDynamicAttributes;
        }

        public TagVariableInfo[] getTagVariableInfos() {
            return tagInfo.getTagVariableInfos();
        }

        public VariableInfo[] getVariableInfos() {
            return varInfos;
        }

        public void setCustomTagParent(Node.CustomTag n) {
            this.customTagParent = n;
        }

        public Node.CustomTag getCustomTagParent() {
            return this.customTagParent;
        }

        public void setNumCount(Integer count) {
            this.numCount = count;
        }

        public Integer getNumCount() {
            return this.numCount;
        }

        public void setScriptingVars(Vector vec, int scope) {
            switch (scope) {
            case VariableInfo.AT_BEGIN:
                this.atBeginScriptingVars = vec;
                break;
            case VariableInfo.AT_END:
                this.atEndScriptingVars = vec;
                break;
            case VariableInfo.NESTED:
                this.nestedScriptingVars = vec;
                break;
            }
        }

        /*
         * Gets the scripting variables for the given scope that need to be
         * declared.
         */
        public Vector getScriptingVars(int scope) {
            Vector vec = null;

            switch (scope) {
            case VariableInfo.AT_BEGIN:
                vec = this.atBeginScriptingVars;
                break;
            case VariableInfo.AT_END:
                vec = this.atEndScriptingVars;
                break;
            case VariableInfo.NESTED:
                vec = this.nestedScriptingVars;
                break;
            }

            return vec;
        }

        /*
         * Gets this custom tag's custom nesting level, which is given as the
         * number of times this custom tag is nested inside itself.
         */
        public int getCustomNestingLevel() {
            return customNestingLevel;
        }

        /**
         * Checks to see if the attribute of the given name is of type
         * JspFragment.
         */
        public boolean checkIfAttributeIsJspFragment(String name) {
            boolean result = false;

            TagAttributeInfo[] attributes = tagInfo.getAttributes();
            for (int i = 0; i < attributes.length; i++) {
                if (attributes[i].getName().equals(name)
                        && attributes[i].isFragment()) {
                    result = true;
                    break;
                }
            }

            return result;
        }

        public void setUseTagPlugin(boolean use) {
            useTagPlugin = use;
        }

        public boolean useTagPlugin() {
            return useTagPlugin;
        }

        public void setTagPluginContext(TagPluginContext tagPluginContext) {
            this.tagPluginContext = tagPluginContext;
        }

        public TagPluginContext getTagPluginContext() {
            return tagPluginContext;
        }

        public void setAtSTag(Nodes sTag) {
            atSTag = sTag;
        }

        public Nodes getAtSTag() {
            return atSTag;
        }

        public void setAtETag(Nodes eTag) {
            atETag = eTag;
        }

        public Nodes getAtETag() {
            return atETag;
        }

        /*
         * Computes this custom tag's custom nesting level, which corresponds to
         * the number of times this custom tag is nested inside itself.
         * 
         * Example:
         * 
         * <g:h> <a:b> -- nesting level 0 <c:d> <e:f> <a:b> -- nesting level 1
         * <a:b> -- nesting level 2 </a:b> </a:b> <a:b> -- nesting level 1
         * </a:b> </e:f> </c:d> </a:b> </g:h>
         * 
         * @return Custom tag's nesting level
         */
        private int makeCustomNestingLevel() {
            int n = 0;
            Node p = parent;
            while (p != null) {
                if ((p instanceof Node.CustomTag)
                        && qName.equals(((Node.CustomTag) p).qName)) {
                    n++;
                }
                p = p.parent;
            }
            return n;
        }

        /**
         * Returns true if this custom action has an empty body, and false
         * otherwise.
         * 
         * A custom action is considered to have an empty body if the following
         * holds true: - getBody() returns null, or - all immediate children are
         * jsp:attribute actions, or - the action's jsp:body is empty.
         */
        public boolean hasEmptyBody() {
            boolean hasEmptyBody = true;
            Nodes nodes = getBody();
            if (nodes != null) {
                int numChildNodes = nodes.size();
                for (int i = 0; i < numChildNodes; i++) {
                    Node n = nodes.getNode(i);
                    if (!(n instanceof NamedAttribute)) {
                        if (n instanceof JspBody) {
                            hasEmptyBody = (n.getBody() == null);
                        } else {
                            hasEmptyBody = false;
                        }
                        break;
                    }
                }
            }

            return hasEmptyBody;
        }
    }

    /**
     * Used as a placeholder for the evaluation code of a custom action
     * attribute (used by the tag plugin machinery only).
     */
    public static class AttributeGenerator extends Node {
        String name; // name of the attribute

        CustomTag tag; // The tag this attribute belongs to

        public AttributeGenerator(Mark start, String name, CustomTag tag) {
            super(start, null);
            this.name = name;
            this.tag = tag;
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public String getName() {
            return name;
        }

        public CustomTag getTag() {
            return tag;
        }
    }

    /**
     * Represents the body of a &lt;jsp:text&gt; element
     */
    public static class JspText extends Node {

        public JspText(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, TEXT_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }
    }

    /**
     * Represents a Named Attribute (&lt;jsp:attribute&gt;)
     */
    public static class NamedAttribute extends Node {

        // A unique temporary variable name suitable for code generation
        private String temporaryVariableName;

        // True if this node is to be trimmed, or false otherwise
        private boolean trim = true;

        private ChildInfo childInfo;

        private String name;

        private String localName;

        private String prefix;

        public NamedAttribute(Attributes attrs, Mark start, Node parent) {
            this(JSP_ATTRIBUTE_ACTION, attrs, null, null, start, parent);
        }

        public NamedAttribute(String qName, Attributes attrs,
                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
                Mark start, Node parent) {

            super(qName, ATTRIBUTE_ACTION, attrs, nonTaglibXmlnsAttrs,
                    taglibAttrs, start, parent);
            if ("false".equals(this.getAttributeValue("trim"))) {
                // (if null or true, leave default of true)
                trim = false;
            }
            childInfo = new ChildInfo();
            name = this.getAttributeValue("name");
            if (name != null) {
                // Mandatary attribute "name" will be checked in Validator
                localName = name;
                int index = name.indexOf(':');
                if (index != -1) {
                    prefix = name.substring(0, index);
                    localName = name.substring(index + 1);
                }
            }
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public String getName() {
            return this.name;
        }

        public String getLocalName() {
            return this.localName;
        }

        public String getPrefix() {
            return this.prefix;
        }

        public ChildInfo getChildInfo() {
            return this.childInfo;
        }

        public boolean isTrim() {
            return trim;
        }

        /**
         * @return A unique temporary variable name to store the result in.
         *         (this probably could go elsewhere, but it's convenient here)
         */
        public String getTemporaryVariableName() {
            if (temporaryVariableName == null) {
                temporaryVariableName = getRoot().nextTemporaryVariableName();
            }
            return temporaryVariableName;
        }

        /*
         * Get the attribute value from this named attribute (<jsp:attribute>).
         * Since this method is only for attributes that are not rtexpr, we can
         * assume the body of the jsp:attribute is a template text.
         */
        public String getText() {

            class AttributeVisitor extends Visitor {
                String attrValue = null;

                public void visit(TemplateText txt) {
                    attrValue = new String(txt.getText());
                }

                public String getAttrValue() {
                    return attrValue;
                }
            }

            // According to JSP 2.0, if the body of the <jsp:attribute>
            // action is empty, it is equivalent of specifying "" as the value
            // of the attribute.
            String text = "";
            if (getBody() != null) {
                AttributeVisitor attributeVisitor = new AttributeVisitor();
                try {
                    getBody().visit(attributeVisitor);
                } catch (JasperException e) {
                }
                text = attributeVisitor.getAttrValue();
            }

            return text;
        }
    }

    /**
     * Represents a JspBody node (&lt;jsp:body&gt;)
     */
    public static class JspBody extends Node {

        private ChildInfo childInfo;

        public JspBody(Mark start, Node parent) {
            this(JSP_BODY_ACTION, null, null, start, parent);
        }

        public JspBody(String qName, Attributes nonTaglibXmlnsAttrs,
                Attributes taglibAttrs, Mark start, Node parent) {
            super(qName, BODY_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
                    start, parent);
            this.childInfo = new ChildInfo();
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        public ChildInfo getChildInfo() {
            return childInfo;
        }
    }

    /**
     * Represents a template text string
     */
    public static class TemplateText extends Node {

        private ArrayList extraSmap = null;

        public TemplateText(String text, Mark start, Node parent) {
            super(null, null, text, start, parent);
        }

        public void accept(Visitor v) throws JasperException {
            v.visit(this);
        }

        /**
         * Trim all whitespace from the left of the template text
         */
        public void ltrim() {
            int index = 0;
            while ((index < text.length()) && (text.charAt(index) <= ' ')) {
                index++;
            }
            text = text.substring(index);
        }

        public void setText(String text) {
            this.text = text;
        }

        /**
         * Trim all whitespace from the right of the template text
         */
        public void rtrim() {
            int index = text.length();
            while ((index > 0) && (text.charAt(index - 1) <= ' ')) {
                index--;
            }
            text = text.substring(0, index);
        }

        /**
         * Returns true if this template text contains whitespace only.
         */
        public boolean isAllSpace() {
            boolean isAllSpace = true;
            for (int i = 0; i < text.length(); i++) {
                if (!Character.isWhitespace(text.charAt(i))) {
                    isAllSpace = false;
                    break;
                }
            }
            return isAllSpace;
        }

        /**
         * Add a source to Java line mapping
         * 
         * @param srcLine
         *            The postion of the source line, relative to the line at
         *            the start of this node. The corresponding java line is
         *            assumed to be consecutive, i.e. one more than the last.
         */
        public void addSmap(int srcLine) {
            if (extraSmap == null) {
                extraSmap = new ArrayList();
            }
            extraSmap.add(new Integer(srcLine));
        }

        public ArrayList getExtraSmap() {
            return extraSmap;
        }
    }

    /***************************************************************************
     * Auxillary classes used in Node
     */

    /**
     * Represents attributes that can be request time expressions.
     * 
     * Can either be a plain attribute, an attribute that represents a request
     * time expression value, or a named attribute (specified using the
     * jsp:attribute standard action).
     */

    public static class JspAttribute {

        private String qName;

        private String uri;

        private String localName;

        private String value;

        private boolean expression;

        private boolean dynamic;

        private final ELNode.Nodes el;

        private final TagAttributeInfo tai;

        // If true, this JspAttribute represents a <jsp:attribute>
        private boolean namedAttribute;

        // The node in the parse tree for the NamedAttribute
        private NamedAttribute namedAttributeNode;

        JspAttribute(TagAttributeInfo tai, String qName, String uri,
                String localName, String value, boolean expr, ELNode.Nodes el,
                boolean dyn) {
            this.qName = qName;
            this.uri = uri;
            this.localName = localName;
            this.value = value;
            this.namedAttributeNode = null;
            this.expression = expr;
            this.el = el;
            this.dynamic = dyn;
            this.namedAttribute = false;
            this.tai = tai;
        }

        /**
         * Allow node to validate itself
         * 
         * @param ef
         * @param ctx
         * @throws ELException
         */
        public void validateEL(ExpressionFactory ef, ELContext ctx)
                throws ELException {
            if (this.el != null) {
                // determine exact type
                ValueExpression ve = ef.createValueExpression(ctx, this.value,
                        String.class);
            }
        }

        /**
         * Use this constructor if the JspAttribute represents a named
         * attribute. In this case, we have to store the nodes of the body of
         * the attribute.
         */
        JspAttribute(NamedAttribute na, TagAttributeInfo tai, boolean dyn) {
            this.qName = na.getName();
            this.localName = na.getLocalName();
            this.value = null;
            this.namedAttributeNode = na;
            this.expression = false;
            this.el = null;
            this.dynamic = dyn;
            this.namedAttribute = true;
            this.tai = null;
        }

        /**
         * @return The name of the attribute
         */
        public String getName() {
            return qName;
        }

        /**
         * @return The local name of the attribute
         */
        public String getLocalName() {
            return localName;
        }

        /**
         * @return The namespace of the attribute, or null if in the default
         *         namespace
         */
        public String getURI() {
            return uri;
        }

        public TagAttributeInfo getTagAttributeInfo() {
            return this.tai;
        }

        /**
         * 
         * @return return true if there's TagAttributeInfo meaning we need to
         *         assign a ValueExpression
         */
        public boolean isDeferredInput() {
            return (this.tai != null) ? this.tai.isDeferredValue() : false;
        }

        /**
         * 
         * @return return true if there's TagAttributeInfo meaning we need to
         *         assign a MethodExpression
         */
        public boolean isDeferredMethodInput() {
            return (this.tai != null) ? this.tai.isDeferredMethod() : false;
        }

        public String getExpectedTypeName() {
            if (this.tai != null) {
                if (this.isDeferredInput()) {
                    return this.tai.getExpectedTypeName();
                } else if (this.isDeferredMethodInput()) {
                    String m = this.tai.getMethodSignature();
                    if (m != null) {
                        int rti = m.trim().indexOf(' ');
                        if (rti > 0) {
                            return m.substring(0, rti).trim();
                        }
                    }
                }
            }
            return "java.lang.Object";
        }
        
        public String[] getParameterTypeNames() {
            if (this.tai != null) {
                if (this.isDeferredMethodInput()) {
                    String m = this.tai.getMethodSignature();
                    if (m != null) {
                        m = m.trim();
                        m = m.substring(m.indexOf('(') + 1);
                        m = m.substring(0, m.length() - 1);
                        if (m.trim().length() > 0) {
                            String[] p = m.split(",");
                            for (int i = 0; i < p.length; i++) {
                                p[i] = p[i].trim();
                            }
                            return p;
                        }
                    }
                }
            }
            return new String[0];
        }

        /**
         * Only makes sense if namedAttribute is false.
         * 
         * @return the value for the attribute, or the expression string
         *         (stripped of "<%=", "%>", "%=", or "%" but containing "${"
         *         and "}" for EL expressions)
         */
        public String getValue() {
            return value;
        }

        /**
         * Only makes sense if namedAttribute is true.
         * 
         * @return the nodes that evaluate to the body of this attribute.
         */
        public NamedAttribute getNamedAttributeNode() {
            return namedAttributeNode;
        }

        /**
         * @return true if the value represents a traditional rtexprvalue
         */
        public boolean isExpression() {
            return expression;
        }

        /**
         * @return true if the value represents a NamedAttribute value.
         */
        public boolean isNamedAttribute() {
            return namedAttribute;
        }

        /**
         * @return true if the value represents an expression that should be fed
         *         to the expression interpreter
         * @return false for string literals or rtexprvalues that should not be
         *         interpreted or reevaluated
         */
        public boolean isELInterpreterInput() {
            return el != null || this.isDeferredInput()
                    || this.isDeferredMethodInput();
        }

        /**
         * @return true if the value is a string literal known at translation
         *         time.
         */
        public boolean isLiteral() {
            return !expression && (el != null) && !namedAttribute;
        }

        /**
         * XXX
         */
        public boolean isDynamic() {
            return dynamic;
        }

        public ELNode.Nodes getEL() {
            return el;
        }
    }

    /**
     * An ordered list of Node, used to represent the body of an element, or a
     * jsp page of jsp document.
     */
    public static class Nodes {

        private List list;

        private Node.Root root; // null if this is not a page

        private boolean generatedInBuffer;

        public Nodes() {
            list = new Vector();
        }

        public Nodes(Node.Root root) {
            this.root = root;
            list = new Vector();
            list.add(root);
        }

        /**
         * Appends a node to the list
         * 
         * @param n
         *            The node to add
         */
        public void add(Node n) {
            list.add(n);
            root = null;
        }

        /**
         * Removes the given node from the list.
         * 
         * @param n
         *            The node to be removed
         */
        public void remove(Node n) {
            list.remove(n);
        }

        /**
         * Visit the nodes in the list with the supplied visitor
         * 
         * @param v
         *            The visitor used
         */
        public void visit(Visitor v) throws JasperException {
            Iterator iter = list.iterator();
            while (iter.hasNext()) {
                Node n = (Node) iter.next();
                n.accept(v);
            }
        }

        public int size() {
            return list.size();
        }

        public Node getNode(int index) {
            Node n = null;
            try {
                n = (Node) list.get(index);
            } catch (ArrayIndexOutOfBoundsException e) {
            }
            return n;
        }

        public Node.Root getRoot() {
            return root;
        }

        public boolean isGeneratedInBuffer() {
            return generatedInBuffer;
        }

        public void setGeneratedInBuffer(boolean g) {
            generatedInBuffer = g;
        }
    }

    /**
     * A visitor class for visiting the node. This class also provides the
     * default action (i.e. nop) for each of the child class of the Node. An
     * actual visitor should extend this class and supply the visit method for
     * the nodes that it cares.
     */
    public static class Visitor {

        /**
         * This method provides a place to put actions that are common to all
         * nodes. Override this in the child visitor class if need to.
         */
        protected void doVisit(Node n) throws JasperException {
        }

        /**
         * Visit the body of a node, using the current visitor
         */
        protected void visitBody(Node n) throws JasperException {
            if (n.getBody() != null) {
                n.getBody().visit(this);
            }
        }

        public void visit(Root n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(JspRoot n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(PageDirective n) throws JasperException {
            doVisit(n);
        }

        public void visit(TagDirective n) throws JasperException {
            doVisit(n);
        }

        public void visit(IncludeDirective n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(TaglibDirective n) throws JasperException {
            doVisit(n);
        }

        public void visit(AttributeDirective n) throws JasperException {
            doVisit(n);
        }

        public void visit(VariableDirective n) throws JasperException {
            doVisit(n);
        }

        public void visit(Comment n) throws JasperException {
            doVisit(n);
        }

        public void visit(Declaration n) throws JasperException {
            doVisit(n);
        }

        public void visit(Expression n) throws JasperException {
            doVisit(n);
        }

        public void visit(Scriptlet n) throws JasperException {
            doVisit(n);
        }

        public void visit(ELExpression n) throws JasperException {
            doVisit(n);
        }

        public void visit(IncludeAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(ForwardAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(GetProperty n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(SetProperty n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(ParamAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(ParamsAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(FallBackAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(UseBean n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(PlugIn n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(CustomTag n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(UninterpretedTag n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(JspElement n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(JspText n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(NamedAttribute n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(JspBody n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(InvokeAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(DoBodyAction n) throws JasperException {
            doVisit(n);
            visitBody(n);
        }

        public void visit(TemplateText n) throws JasperException {
            doVisit(n);
        }

        public void visit(JspOutput n) throws JasperException {
            doVisit(n);
        }

        public void visit(AttributeGenerator n) throws JasperException {
            doVisit(n);
        }
    }
}
