/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed 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.jasper.compiler;

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

import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.IterationTag;
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.jasper.JasperException;
import org.apache.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;
        private boolean hasBom;

	// 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;

	/*
	 * 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 setHasBom(boolean hasBom) {
            this.hasBom = hasBom;
        }

        public boolean hasBom() {
            return hasBom;
        }

	/**
	 * @return The enclosing root to this Root. Usually represents the
	 * page that includes this one.
	 */
	public Root getParentRoot() {
	    return parentRoot;
	}
    }

    /**
     * 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 ArrayList<String> 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 ArrayList<String>();
	}

	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<String> 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 ArrayList<String> 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 ArrayList<String>();
	}

	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<String> 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) && (body != null)) {
		StringBuilder buf = new StringBuilder();
		for (int i=0; i<body.size(); i++) {
		    buf.append(body.getNode(i).getText());
		}
		ret = buf.toString();
	    }
	    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;

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

        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;
	}
    }

    /**
     * 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 double jspVersion;
	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 implementsSimpleTag;
	private boolean implementsDynamicAttributes;
	private ArrayList<Object> atBeginScriptingVars;
	private ArrayList<Object> atEndScriptingVars;
	private ArrayList<Object> nestedScriptingVars;
	private Node.CustomTag customTagParent;
	private Integer numCount;
	private boolean useTagPlugin;
	private TagPluginContext tagPluginContext;
        private int jspId;
        private HashMap<String, String> tempVars;
                // The temporary name for saving and restoring a scripting var

	/**
	 * 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 jspVersion,
                         String qName, String prefix, String localName,
			 String uri, Attributes attrs, Mark start, Node parent,
			 TagInfo tagInfo, Class tagHandlerClass) {
	    this(jspVersion, qName, prefix, localName, uri, attrs, null, null,
                 start, parent, tagInfo, tagHandlerClass);
	}

	/*
	 * Constructor for custom action implemented by tag handler.
	 */
	public CustomTag(String jspVersion, 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.jspVersion = Double.valueOf(jspVersion).doubleValue();
	    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);
	}

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

	/*
	 * Constructor for custom action implemented by tag file.
	 */
	public CustomTag(String jspVersion,
                         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.jspVersion = Double.valueOf(jspVersion).doubleValue();
	    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.implementsDynamicAttributes = tagInfo.hasDynamicAttributes();
	}

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

        /**
         * @return The jspVersion of the tag
         */
        public double getJspVersion() {
            return jspVersion;
        }

	/**
	 * @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 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 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(ArrayList<Object> 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 ArrayList<Object> getScriptingVars(int scope) {
	    switch (scope) {
	    case VariableInfo.AT_BEGIN:
		return this.atBeginScriptingVars;
	    case VariableInfo.AT_END:
		return this.atEndScriptingVars;
	    case VariableInfo.NESTED:
		return this.nestedScriptingVars;
	    }
	    return null;
	}

	/*
	 * 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;
	}

        public void setJspId(int jspId) {
            this.jspId = jspId;
        }

        public int getJspId() {
            return jspId;
        }

	/*
	 * 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;
	 }

         public String getTempScriptingVar(String scriptingVar) {
             if (tempVars == null) {
                 return null;
             }
             return tempVars.get(scriptingVar);
         }

         public void setTempScriptingVar(String scriptingVar,
                                         String tempScriptingVar) {
             if (tempVars == null) {
                 tempVars = new HashMap<String,String>();
             }
             tempVars.put(scriptingVar, tempScriptingVar);
         }
    }

    /**
     * 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 <jsp:text> 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 (<jsp:attribute>)
     */
    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;

        // omit can be expressions
        JspAttribute omit;

        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);
            temporaryVariableName = JspUtil.nextTemporaryVariableName();
            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;
        }

        public void setOmit(JspAttribute ja) {
            omit = ja;
        }

        public JspAttribute getOmit() {
            return omit;
        }

        /**
         * @return A unique temporary variable name to store the result in.
         *      (this probably could go elsewhere, but it's convenient here)
         */
        public String getTemporaryVariableName() {
            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 (<jsp:body>)
     */
    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<Integer> 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<Integer>();
            }
            extraSmap.add(Integer.valueOf(srcLine));
        }

        public ArrayList<Integer> 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 ELNode.Nodes el;
        private String expectedType;         // for deferred value
        private String expectedReturnType;   // for deferred method
        private String[] expectedParamTypes; // for deferred method

        // If true, this JspAttribute represents a <jsp:attribute>
        private boolean namedAttribute;
        // The node in the parse tree for the NamedAttribute
        private NamedAttribute namedAttributeNode;

        JspAttribute(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;
	}

        JspAttribute(String qName, String uri, String localName, String value,
		     ELNode.Nodes el,
                     String expectedType,
                     String expectedReturnType,
                     String[] expectedParamTypes) {
	    this.qName = qName;
	    this.uri = uri;
	    this.localName = localName;
	    this.value = value;
            this.namedAttributeNode = null;
	    this.expression = false;
            this.el = el;
	    this.dynamic = false;
            this.namedAttribute = false;
            this.expectedType = expectedType;
            this.expectedReturnType = expectedReturnType;
            this.expectedParamTypes = expectedParamTypes;
	}

        /**
         * 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, 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;
        }

	/**
 	 * @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;
	}

	/**
         * 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;
	}

        public void setValue(String val) {
            value = val;
        }

        /**
         * 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;
        }

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

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

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

        public String getExpectedType() {
            return expectedType;
        }

        public String getExpectedReturnType() {
            return expectedReturnType;
        }

        public String[] getExpectedParamTypes() {
            return expectedParamTypes;
        }
    }

    /**
     * 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<Node> list;
	private Node.Root root;		// null if this is not a page
	private boolean generatedInBuffer;

	public Nodes() {
	    list = new ArrayList<Node>();
	}

	public Nodes(Node.Root root) {
	    this.root = root;
	    list = new ArrayList<Node>();
	    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<Node> iter = list.iterator();
	    while (iter.hasNext()) {
		Node n = iter.next();
		n.accept(v);
	    }
	}

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

	public Node getNode(int index) {
	    Node n = null;
	    try {
		n = 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);
	}
    }
}


/*******************************************************************************
 * Copyright (c) 2016 IBH SYSTEMS GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.packagedrone.repo.trigger;

import static java.util.Objects.requireNonNull;
import static org.eclipse.packagedrone.utils.osgi.FactoryTracker.getString;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

import org.eclipse.packagedrone.utils.Holder;
import org.eclipse.packagedrone.utils.osgi.SimpleFactoryTracker;
import org.osgi.framework.BundleContext;

public class ProcessorFactoryTracker
{
    public static final String PROP_ID = "drone.trigger.processor.factory.id"; //$NON-NLS-1$

    private static class ProcessorFactoryInformationExt extends ProcessorFactoryInformation
    {
        private final ProcessorFactory factory;

        public ProcessorFactoryInformationExt ( final String id, final ProcessorFactory factory )
        {
            super ( id, factory.getLabel (), factory.getDescription (), factory.getConfigurationUrl () );
            this.factory = factory;
        }

        public ProcessorFactory getFactory ()
        {
            return this.factory;
        }
    }

    private final SimpleFactoryTracker<ProcessorFactory, ProcessorFactoryInformationExt> tracker;

    public ProcessorFactoryTracker ( final BundleContext context )
    {
        this.tracker = new SimpleFactoryTracker<> ( context, ProcessorFactory.class, ref -> getString ( ref, PROP_ID ), ( ref, service ) -> {
            return new ProcessorFactoryInformationExt ( getString ( ref, PROP_ID ), service );
        } );
        this.tracker.open ();
    }

    public void close ()
    {
        this.tracker.close ();
    }

    public void process ( final String factoryId, final Consumer<ProcessorFactory> factory )
    {
        this.tracker.consume ( factoryId, entry -> factory.accept ( entry.getFactory () ) );
    }

    public void processOptionally ( final String factoryId, final Consumer<Optional<ProcessorFactory>> factory )
    {
        this.tracker.consumeOptionally ( factoryId, entry -> factory.accept ( entry.map ( ProcessorFactoryInformationExt::getFactory ) ) );
    }

    /**
     * Get a list of matching factories
     * <p>
     * The returned list is a copy of the current state. The list may be
     * modified by the caller, but will not be updated by this tracker when the
     * state changes.
     * </p>
     *
     * @param contextClasses
     *            the provided context classes
     * @return a list of matching processor factors, never returns {@code null}
     */
    public List<ProcessorFactoryInformation> getFactoriesFor ( final Class<?>[] contextClasses )
    {
        requireNonNull ( contextClasses );

        final List<ProcessorFactoryInformation> result = new ArrayList<> ();
        this.tracker.consumeAll ( stream -> stream.filter ( service -> isMatch ( service, contextClasses ) ).forEach ( result::add ) );
        return result;
    }

    public Optional<ProcessorFactoryInformation> getFactoryInformation ( final String factoryId )
    {
        final Holder<Optional<ProcessorFactoryInformation>> result = new Holder<> ( Optional.empty () );
        this.tracker.consumeOptionally ( factoryId, factory -> result.value = Optional.ofNullable ( factory.orElse ( null ) ) );
        return result.value;
    }

    private boolean isMatch ( final ProcessorFactoryInformationExt service, final Class<?>[] contextClasses )
    {
        if ( service == null || contextClasses == null || contextClasses.length <= 0 )
        {
            return false;
        }

        final ProcessorFactory factory = service.getFactory ();

        for ( final Class<?> contextClass : contextClasses )
        {
            if ( contextClass == null )
            {
                continue;
            }

            if ( factory.supportsContext ( contextClass ) )
            {
                return true;
            }
        }

        return false;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed 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.jasper.xmlparser;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Simplified implementation of a Node from a Document Object Model (DOM)
 * parse of an XML document.  This class is used to represent a DOM tree
 * so that the XML parser's implementation of org.w3c.dom need
 * not be visible to the remainder of Jasper.
 *

 * WARNING - Construction of a new tree, or modifications
 * to an existing one, are not thread-safe and such accesses must be
 * synchronized.
 *
 * @author Craig R. McClanahan
 * @version $Revision: 1.2 $ $Date: 2005/12/08 01:29:00 $
 */

public class TreeNode {

    // ----------------------------------------------------------- Constructors

    /**
     * Construct a new node with no parent.
     *
     * @param name The name of this node
     */
    public TreeNode(String name) {

        this(name, null);

    }

    /**
     * Construct a new node with the specified parent.
     *
     * @param name The name of this node
     * @param parent The node that is the parent of this node
     */
    public TreeNode(String name, TreeNode parent) {

        super();
        this.name = name;
        this.parent = parent;
        if (this.parent != null)
            this.parent.addChild(this);

    }

    // ----------------------------------------------------- Instance Variables

    /**
     * The attributes of this node, keyed by attribute name,
     * Instantiated only if required.
     */
    protected HashMap<String, String> attributes = null;

    /**
     * The body text associated with this node (if any).
     */
    protected String body = null;

    /**
     * The children of this node, instantiated only if required.
     */
    protected ArrayList<TreeNode> children = null;

    /**
     * The name of this node.
     */
    protected String name = null;

    /**
     * The parent node of this node.
     */
    protected TreeNode parent = null;

    // --------------------------------------------------------- Public Methods

    /**
     * Add an attribute to this node, replacing any existing attribute
     * with the same name.
     *
     * @param name The attribute name to add
     * @param value The new attribute value
     */
    public void addAttribute(String name, String value) {

        if (attributes == null)
            attributes = new HashMap<String, String>();
        attributes.put(name, value);

    }

    /**
     * Add a new child node to this node.
     *
     * @param node The new child node
     */
    public void addChild(TreeNode node) {

        if (children == null)
            children = new ArrayList<TreeNode>();
        children.add(node);

    }

    /**
     * Return the value of the specified node attribute if it exists, or
     * null otherwise.
     *
     * @param name Name of the requested attribute
     */
    public String findAttribute(String name) {

        if (attributes == null)
            return (null);
        else
            return (attributes.get(name));

    }

    /**
     * Return an Iterator of the attribute names of this node.  If there are
     * no attributes, an empty Iterator is returned.
     */
    public Iterator<String> findAttributes() {

        Set<String> attrs;
        if (attributes == null)
            attrs = Collections.emptySet();
        else
            attrs = attributes.keySet();
        return attrs.iterator();

    }

    /**
     * Return the first child node of this node with the specified name,
     * if there is one; otherwise, return null.
     *
     * @param name Name of the desired child element
     */
    public TreeNode findChild(String name) {

        if (children == null)
            return (null);
        for (TreeNode item: children) {
            if (name.equals(item.getName()))
                return (item);
        }
        return (null);

    }

    /**
     * Return an Iterator of all children of this node.  If there are no
     * children, an empty Iterator is returned.
     */
    public Iterator<TreeNode> findChildren() {

        List<TreeNode> nodes;
        if (children == null)
            nodes = Collections.emptyList();
        else
            nodes = children;
        return nodes.iterator();
    }

    /**
     * Return an Iterator over all children of this node that have the
     * specified name.  If there are no such children, an empty Iterator
     * is returned.
     *
     * @param name Name used to select children
     */
    public Iterator<TreeNode> findChildren(String name) {

        List<TreeNode> results;
        if (children == null)
            results = Collections.emptyList();
        else {
            results = new ArrayList<TreeNode>();
            for (TreeNode item: children) {
                if (name.equals(item.getName()))
                    results.add(item);
            }
        }
        return results.iterator();
    }

    /**
     * Return the body text associated with this node (if any).
     */
    public String getBody() {

        return (this.body);

    }

    /**
     * Return the name of this node.
     */
    public String getName() {

        return (this.name);

    }

    /**
     * Remove any existing value for the specified attribute name.
     *
     * @param name The attribute name to remove
     */
    public void removeAttribute(String name) {

        if (attributes != null)
            attributes.remove(name);

    }

    /**
     * Remove a child node from this node, if it is one.
     *
     * @param node The child node to remove
     */
    public void removeNode(TreeNode node) {

        if (children != null)
            children.remove(node);

    }

    /**
     * Set the body text associated with this node (if any).
     *
     * @param body The body text (if any)
     */
    public void setBody(String body) {

        this.body = body;

    }

    /**
     * Return a String representation of this TreeNode.
     */
    public String toString() {

        StringBuilder sb = new StringBuilder();
        toString(sb, 0, this);
        return (sb.toString());

    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Append to the specified StringBuilder a character representation of
     * this node, with the specified amount of indentation.
     *
     * @param sb The StringBuilder to append to
     * @param indent Number of characters of indentation
     * @param node The TreeNode to be printed
     */
    protected void toString(StringBuilder sb, int indent,
                            TreeNode node) {

        int indent2 = indent + 2;

        // Reconstruct an opening node
        for (int i = 0; i < indent; i++)
            sb.append(' ');
        sb.append('<');
        sb.append(node.getName());
        Iterator names = node.findAttributes();
        while (names.hasNext()) {
            sb.append(' ');
            String name = (String) names.next();
            sb.append(name);
            sb.append("=\"");
            String value = node.findAttribute(name);
            sb.append(value);
            sb.append("\"");
        }
        sb.append(">\n");

        // Reconstruct the body text of this node (if any)
        String body = node.getBody();
        if ((body != null) && (body.length() > 0)) {
            for (int i = 0; i < indent2; i++)
                sb.append(' ');
            sb.append(body);
            sb.append("\n");
        }

        // Reconstruct child nodes with extra indentation
        Iterator<TreeNode> children = node.findChildren();
        while (children.hasNext()) {
            TreeNode child = children.next();
            toString(sb, indent2, child);
        }

        // Reconstruct a closing node marker
        for (int i = 0; i < indent; i++)
            sb.append(' ');
        sb.append("</");
        sb.append(node.getName());
        sb.append(">\n");

    }

}


/*******************************************************************************
 * Copyright (c) 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *     M-Ezzat - code cleanup - squid:S2162
 *******************************************************************************/
package org.eclipse.packagedrone.repo.importer.aether;

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.packagedrone.utils.converter.JSON;

@JSON
public class MavenCoordinates implements Comparable<MavenCoordinates>
{
    private String groupId;

    private String artifactId;

    private String version;

    private String classifier;

    private String extension;

    public MavenCoordinates ()
    {
    }

    public MavenCoordinates ( final MavenCoordinates other )
    {
        this.groupId = other.groupId;
        this.artifactId = other.artifactId;
        this.version = other.version;
        this.classifier = other.classifier;
        this.extension = other.extension;
    }

    public MavenCoordinates ( final String groupId, final String artifactId, final String version )
    {
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
    }

    public String getGroupId ()
    {
        return this.groupId;
    }

    public void setGroupId ( final String groupId )
    {
        this.groupId = groupId;
    }

    public String getArtifactId ()
    {
        return this.artifactId;
    }

    public void setArtifactId ( final String artifactId )
    {
        this.artifactId = artifactId;
    }

    public String getVersion ()
    {
        return this.version;
    }

    public void setVersion ( final String version )
    {
        this.version = version;
    }

    public String getClassifier ()
    {
        return this.classifier;
    }

    public void setClassifier ( final String classifier )
    {
        this.classifier = classifier;
    }

    public String getExtension ()
    {
        return this.extension;
    }

    public void setExtension ( final String extension )
    {
        this.extension = extension;
    }

    /**
     * Create a new instance without classifier and extension
     */
    public MavenCoordinates toBase ()
    {
        if ( this.classifier == null && this.extension == null )
        {
            return this;
        }

        return new MavenCoordinates ( this.groupId, this.artifactId, this.version );
    }

    public static MavenCoordinates fromString ( final String coords )
    {
        return fromArtifact ( new DefaultArtifact ( coords ) );
    }

    public static MavenCoordinates fromResult ( final ArtifactResult result )
    {
        if ( result == null )
        {
            return null;
        }

        return fromArtifact ( result.getRequest ().getArtifact () );
    }

    public static MavenCoordinates fromArtifact ( final Artifact art )
    {
        if ( art == null )
        {
            return null;
        }

        final MavenCoordinates coords = new MavenCoordinates ();

        coords.setGroupId ( art.getGroupId () );
        coords.setArtifactId ( art.getArtifactId () );
        coords.setVersion ( art.getVersion () );
        coords.setClassifier ( art.getClassifier () );
        coords.setExtension ( art.getExtension () );

        return coords;
    }

    @Override
    public String toString ()
    {
        final StringBuilder sb = new StringBuilder ();

        sb.append ( this.groupId );
        sb.append ( ':' ).append ( this.artifactId );

        if ( this.extension != null && !this.extension.isEmpty () )
        {
            sb.append ( ':' ).append ( this.extension );
        }
        if ( this.classifier != null && !this.classifier.isEmpty () )
        {
            sb.append ( ':' ).append ( this.classifier );
        }

        sb.append ( ':' ).append ( this.version );

        return sb.toString ();
    }

    @Override
    public int compareTo ( final MavenCoordinates o )
    {
        int rc;

        rc = compare ( this.groupId, o.groupId );
        if ( rc != 0 )
        {
            return rc;
        }
        rc = compare ( this.artifactId, o.artifactId );
        if ( rc != 0 )
        {
            return rc;
        }
        rc = compare ( this.version, o.version );
        if ( rc != 0 )
        {
            return rc;
        }
        rc = compare ( this.classifier, o.classifier );
        if ( rc != 0 )
        {
            return rc;
        }
        return compare ( this.extension, o.extension );
    }

    private int compare ( final String s1, final String s2 )
    {
        if ( s1 == s2 )
        {
            return 0;
        }

        if ( s1 == null )
        {
            return -1;
        }
        if ( s2 == null )
        {
            return 1;
        }
        return s1.compareTo ( s2 );
    }

    @Override
    public int hashCode ()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( this.artifactId == null ? 0 : this.artifactId.hashCode () );
        result = prime * result + ( this.classifier == null ? 0 : this.classifier.hashCode () );
        result = prime * result + ( this.extension == null ? 0 : this.extension.hashCode () );
        result = prime * result + ( this.groupId == null ? 0 : this.groupId.hashCode () );
        result = prime * result + ( this.version == null ? 0 : this.version.hashCode () );
        return result;
    }

    @Override
    public boolean equals ( final Object obj )
    {
        if ( this == obj )
        {
            return true;
        }
        if ( obj == null )
        {
            return false;
        }
        if ( this.getClass() != obj.getClass() )
        {
            return false;
        }
        final MavenCoordinates other = (MavenCoordinates)obj;
        if ( this.artifactId == null )
        {
            if ( other.artifactId != null )
            {
                return false;
            }
        }
        else if ( !this.artifactId.equals ( other.artifactId ) )
        {
            return false;
        }
        if ( this.classifier == null )
        {
            if ( other.classifier != null )
            {
                return false;
            }
        }
        else if ( !this.classifier.equals ( other.classifier ) )
        {
            return false;
        }
        if ( this.extension == null )
        {
            if ( other.extension != null )
            {
                return false;
            }
        }
        else if ( !this.extension.equals ( other.extension ) )
        {
            return false;
        }
        if ( this.groupId == null )
        {
            if ( other.groupId != null )
            {
                return false;
            }
        }
        else if ( !this.groupId.equals ( other.groupId ) )
        {
            return false;
        }
        if ( this.version == null )
        {
            if ( other.version != null )
            {
                return false;
            }
        }
        else if ( !this.version.equals ( other.version ) )
        {
            return false;
        }
        return true;
    }

}