All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jasper.compiler.Generator Maven / Gradle / Ivy

There is a newer version: 5.5.23
Show newest version
/*
 * Copyright 1999,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.*;
import java.beans.*;
import java.net.URLEncoder;
import java.lang.reflect.Method;
import javax.servlet.jsp.tagext.*;
import org.xml.sax.Attributes;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.runtime.JspRuntimeLibrary;
import org.apache.jasper.Constants;

/**
 * Generate Java source from Nodes
 *
 * @author Anil K. Vijendran
 * @author Danno Ferrin
 * @author Mandar Raje
 * @author Rajiv Mordani
 * @author Pierre Delisle
 * @author Kin-man Chung
 * @author Jan Luehe
 * @author Denis Benoit
 */

public class Generator {

    private ServletWriter out;
    private MethodsBuffer methodsBuffer;
    private ErrorDispatcher err;
    private BeanRepository beanInfo;
    private JspCompilationContext ctxt;
    private boolean breakAtLF;
    private PageInfo pageInfo;
    private int maxTagNesting;
    private Vector tagHandlerPoolNames;

    /**
     * @param s the input string
     * @return quoted and escaped string, per Java rule
     */
    private static String quote(String s) {

        if (s == null)
            return "null";

        return '"' + escape( s ) + '"';
    }

    /**
     * @param s the input string
     * @return escaped string, per Java rule
     */
    private static String escape(String s) {

        if (s == null)
            return "";

        StringBuffer b = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '"') 
                b.append('\\').append('"');
            else if (c == '\\')
                b.append('\\').append('\\');
            else if (c == '\n')
                b.append('\\').append('n');
            else if (c == '\r')
                b.append('\\').append('r');
            else 
                b.append(c);
        }
        return b.toString();
    }

    /**
     * Generates declarations.  This includes "info" of the page directive,
     * and scriptlet declarations.
     */
    private void generateDeclarations(Node.Nodes page) throws JasperException {

        class DeclarationVisitor extends Node.Visitor {

            public void visit(Node.PageDirective n) throws JasperException {
                String info = n.getAttributeValue("info");
                if (info == null)
                    return;

                out.printil("public String getServletInfo() {");
                out.pushIndent();
                out.printin("return ");
                out.print  (quote(info));
                out.println(";");
                out.popIndent();
                out.print  ('}');
                out.println();
            }

            public void visit(Node.Declaration n) throws JasperException {
                out.printMultiLn(new String(n.getText()));
                out.println();
            }
        }

        out.println();
        page.visit(new DeclarationVisitor());
    }

    /**
     * Compiles list of tag handler pool names.
     */
    private void compileTagHandlerPoolList(Node.Nodes page)
            throws JasperException {

        class TagHandlerPoolVisitor extends Node.Visitor {

            private Vector names;

            /*
             * Constructor
             *
             * @param v Vector of tag handler pool names to populate
             */
            TagHandlerPoolVisitor(Vector v) {
                names = v;
            }

            /*
             * Gets the name of the tag handler pool for the given custom tag
             * and adds it to the list of tag handler pool names unless it is
             * already contained in it.
             */
            public void visit(Node.CustomTag n) throws JasperException {
                
                String name = createTagHandlerPoolName(n.getPrefix(),
                                                       n.getShortName(),
                                                       n.getAttributes(),
                                                       n.getBody() == null);
                n.setTagHandlerPoolName(name);
                if (!names.contains(name)) {
                    names.add(name);
                }

                visitBody(n);
            }

            /*
             * Creates the name of the tag handler pool whose tag handlers may
             * be (re)used to service this action.
             *
             * @return The name of the tag handler pool
             */
            private String createTagHandlerPoolName(String prefix,
                                                    String shortName,
                                                    Attributes attrs,
                                                    boolean hasEmptyBody) {
                String poolName = null;

                if (prefix.indexOf('-') >= 0)
                    prefix = JspUtil.replace(prefix, '-', "$1");
                if (prefix.indexOf('.') >= 0)
                    prefix = JspUtil.replace(prefix, '.', "$2");

                if (shortName.indexOf('-') >= 0)
                    shortName = JspUtil.replace(shortName, '-', "$1");
                if (shortName.indexOf('.') >= 0)
                    shortName = JspUtil.replace(shortName, '.', "$2");
                if (shortName.indexOf(':') >= 0)
                    shortName = JspUtil.replace(shortName, ':', "$3");

                poolName = "_jspx_tagPool_" + prefix + "_" + shortName;
                if (attrs != null) {
                    String[] attrNames = new String[attrs.getLength()];
                    for (int i=0; i 0) {
                    TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
                    VariableInfo[] varInfos = n.getVariableInfos();

                    if (varInfos != null) {
                        for (int i=0; i 0) {
            out.printil("JspxState _jspxState = new JspxState();");
        }
*/
        out.printil("JspWriter _jspx_out = null;");
        out.println();

        declareTemporaryScriptingVars(page);
        out.println();

        out.printil("try {");
        out.pushIndent();

        out.printil("_jspxFactory = JspFactory.getDefaultFactory();");

        out.printin("response.setContentType(");
        out.print  (quote(pageInfo.getContentType()));
        out.println(");");

        out.printil("pageContext = _jspxFactory.getPageContext(this, request, response,");
        out.printin("\t\t\t");
        out.print  (quote(pageInfo.getErrorPage()));
        out.print  (", " + pageInfo.isSession());
        out.print  (", " + pageInfo.getBuffer());
        out.print  (", " + pageInfo.isAutoFlush());
        out.println(");");

        out.printil("application = pageContext.getServletContext();");
        out.printil("config = pageContext.getServletConfig();");

        if (pageInfo.isSession())
            out.printil("session = pageContext.getSession();");
        out.printil("out = pageContext.getOut();");
        out.printil("_jspx_out = out;");
        out.println();
    }

    /*
     * Generates the servlet constructor.
     */
    private void generateServletConstructor(String servletClassName) {
        out.printil("public " + servletClassName + "() {");
        out.pushIndent();
        for (int i=0; i: tag prefix
         *   : hashtable containing introspection on tag handlers:
         *              : tag short name
         *              : introspection info of tag handler for 
         *                        tag
         */
        private Hashtable handlerInfos;

        private Hashtable tagVarNumbers;
        private String parent;
        private String pushBodyCountVar;

        private ServletWriter out;
        private MethodsBuffer methodsBuffer;
        private int methodNesting;

        /**
         * Constructor.
         */
        public GenerateVisitor(ServletWriter out,
                               MethodsBuffer methodsBuffer) {
            this.out = out;
            this.methodsBuffer = methodsBuffer;
            methodNesting = 0;
            handlerInfos = new Hashtable();
            tagVarNumbers = new Hashtable();
        }

        /**
         * Returns an attribute value, optionally URL encoded.  If
         * the value is a runtime expression, the result is the string for
         * the expression, otherwise the result is the string literal,
         * quoted and escaped.
         * @param attr An JspAttribute object
         * @param encode true if to be URL encoded
         */
        private String attributeValue(Node.JspAttribute attr, boolean encode) {
            String v = attr.getValue();
            if (v == null)
                return "";

            if (attr.isExpression()) {
                if (encode) {
                    return "java.net.URLEncoder.encode(\"\" + " + v + ")";
                }
                return v;
            } else {
                if (encode) {
                    v = URLEncoder.encode(v);
                }
                return quote(v);
            }
        }

        /**
         * Prints the attribute value specified in the param action, in the
         * form of name=value string.
         *
         * @param n the parent node for the param action nodes.
         */
        private void printParams(Node n, Node.JspAttribute page)
                                                throws JasperException {

            class ParamVisitor extends Node.Visitor {
                String separator;

                ParamVisitor(String separator){
                    this.separator = separator;
                }

                public void visit(Node.ParamAction n) throws JasperException {

                    out.print(" + ");
                    out.print(separator);
                    out.print(" + \"");
                    out.print(n.getAttributeValue("name"));
                    out.print("=\" + ");
                    out.print(attributeValue(n.getValue(), true));

                    // The separator is '&' after the second use
                    separator = "\"&\"";
                }
            }

            String pValue = page.getValue();
            String sep;
            if (page.isExpression()) {
                sep = "((" + pValue + ").indexOf('?')>0? '&': '?')";
            } else {
                sep = pValue.indexOf('?')>0? "\"&\"": "\"?\"";
            }
            if (n.getBody() != null) {
                n.getBody().visit(new ParamVisitor(sep));
            }
        }

        public void visit(Node.Expression n) throws JasperException {
            n.setBeginJavaLine(out.getJavaLine());
            out.printil("out.print(" + new String(n.getText()) + ");");
            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.Scriptlet n) throws JasperException {
            n.setBeginJavaLine(out.getJavaLine());
            out.printMultiLn(new String(n.getText()));
            out.println();
            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.IncludeAction n) throws JasperException {

            String flush = n.getAttributeValue("flush");

            boolean isFlush = false;        // default to false;
            if ("true".equals(flush))
                isFlush = true;

            n.setBeginJavaLine(out.getJavaLine());

            out.printin("JspRuntimeLibrary.include(request, response, ");
            out.print(attributeValue(n.getPage(), false));
            printParams(n, n.getPage());
            out.println(", out, " + isFlush + ");");

            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.ForwardAction n) throws JasperException {
            String page = n.getAttributeValue("page");

            n.setBeginJavaLine(out.getJavaLine());

            out.printil("if (true) {");        // So that javac won't complain about
            out.pushIndent();                // codes after "return"
            out.printin("pageContext.forward(");
            out.print(attributeValue(n.getPage(), false));
            printParams(n, n.getPage());
            out.println(");");
            out.printil((methodNesting > 0)? "return true;": "return;");
            out.popIndent();
            out.printil("}");

            n.setEndJavaLine(out.getJavaLine());
            // XXX Not sure if we can eliminate dead codes after this.
        }

        public void visit(Node.GetProperty n) throws JasperException {
            String name = n.getAttributeValue("name");
            String property = n.getAttributeValue("property");

            n.setBeginJavaLine(out.getJavaLine());

            if (beanInfo.checkVariable(name)) {
                // Bean is defined using useBean, introspect at compile time
                Class bean = beanInfo.getBeanType(name);
                String beanName = bean.getName();
                java.lang.reflect.Method meth =
                    JspRuntimeLibrary.getReadMethod(bean, property);
                String methodName = meth.getName();
                out.printil("out.print(JspRuntimeLibrary.toString(" +
                            "(((" + beanName + ")pageContext.findAttribute(" +
                            "\"" + name + "\"))." + methodName + "())));");
            } else {
                // The object could be a custom action with an associated
                // VariableInfo entry for this name.
                // Get the class name and then introspect at runtime.
                out.printil("out.print(JspRuntimeLibrary.toString" +
                            "(JspRuntimeLibrary.handleGetProperty" +
                            "(pageContext.findAttribute(\"" +
                            name + "\"), \"" + property + "\")));");
            }

            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.SetProperty n) throws JasperException {
            String name = n.getAttributeValue("name");
            String property = n.getAttributeValue("property");
            String param = n.getAttributeValue("param");
            Node.JspAttribute value = n.getValue();

            n.setBeginJavaLine(out.getJavaLine());

            if ("*".equals(property)){
                out.printil("JspRuntimeLibrary.introspect(" +
                            "pageContext.findAttribute(" +
                            "\"" + name + "\"), request);");
            } else if (value == null) {
                if (param == null)
                    param = property;        // default to same as property
                out.printil("JspRuntimeLibrary.introspecthelper(" +
                            "pageContext.findAttribute(\"" + name + "\"), \"" +
                            property + "\", request.getParameter(\"" + param +
                            "\"), " + "request, \"" + param + "\", false);");
            } else if (value.isExpression()) {
                out.printil("JspRuntimeLibrary.handleSetProperty(" + 
                            "pageContext.findAttribute(\""  + name + "\"), \""
                            + property + "\","); 
                out.print(attributeValue(value, false));
                out.println(");");
            } else {
                out.printil("JspRuntimeLibrary.introspecthelper(" +
                            "pageContext.findAttribute(\"" + name + "\"), \"" +
                            property + "\",");
                out.print(attributeValue(value, false));
                out.println(",null, null, false);");
            }

            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.UseBean n) throws JasperException {

            String name = n.getAttributeValue ("id");
            String scope = n.getAttributeValue ("scope");
            String klass = n.getAttributeValue ("class");
            String type = n.getAttributeValue ("type");
            Node.JspAttribute beanName = n.getBeanName();

            if (type == null)        // if unspecified, use class as type of bean 
                type = klass;

            String scopename = "PageContext.PAGE_SCOPE"; // Default to page
            String lock = "pageContext";

            if ("request".equals(scope)) {
                scopename = "PageContext.REQUEST_SCOPE";
                lock = "request";
            } else if ("session".equals(scope)) {
                scopename = "PageContext.SESSION_SCOPE";
                lock = "session";
            } else if ("application".equals(scope)) {
                scopename = "PageContext.APPLICATION_SCOPE";
                lock = "application";
            }

            n.setBeginJavaLine(out.getJavaLine());

            // Declare bean
            out.printin(type);
            out.print  (' ');
            out.print  (name);
            out.println(" = null;");

            // Lock while getting or creating bean
            out.printin("synchronized (");
            out.print  (lock);
            out.println(") {");
            out.pushIndent();

            // Locate bean from context
            out.printin(name);
            out.print  (" = (");
            out.print  (type);
            out.print  (") pageContext.getAttribute(");
            out.print  (quote(name));
            out.print  (", ");
            out.print  (scopename);
            out.println(");");

            // Create bean
            /*
             * Check if bean is alredy there
             */
            out.printin("if (");
            out.print  (name);
            out.println(" == null){");
            out.pushIndent();
            if (klass == null && beanName == null) {
                /*
                 * If both class name and beanName is not specified, the bean
                 * must be found locally, otherwise it's an error
                 */
                out.printin("throw new java.lang.InstantiationException(\"bean ");
                out.print  (name);
                out.println(" not found within scope\");");
            } else {
                /*
                 * Instantiate bean if not there
                 */
                String className;
                if (beanName != null) {
                    className = attributeValue(beanName, false);
                }
                else {
                    // Implies klass is not null
                    className = quote(klass);
                }
                out.printil("try {");
                out.pushIndent();
                out.printin(name);
                out.print  (" = (");
                out.print  (type);
                out.print  (") java.beans.Beans.instantiate(");
                out.print  ("this.getClass().getClassLoader(), ");
                out.print  (className);
                out.println(");");
                out.popIndent();
                /*
                 * Note: Beans.instantiate throws ClassNotFoundException
                 * if the bean class is abstract.
                 */
                out.printil("} catch (ClassNotFoundException exc) {");
                out.pushIndent();
                out.printil("throw new InstantiationException(exc.getMessage());");
                out.popIndent();
                out.printil("} catch (Exception exc) {");
                out.pushIndent();
                out.printin("throw new ServletException(");
                out.print  ("\"Cannot create bean of class \" + ");
                out.print  (className);
                out.println(", exc);");
                out.popIndent();
                out.printil("}");        // close of try
                /*
                 * Set attribute for bean in the specified scope
                 */
                out.printin("pageContext.setAttribute(");
                out.print  (quote(name));
                out.print  (", ");
                out.print  (name);
                out.print  (", ");
                out.print  (scopename);
                out.println(");");

                // Only visit the body when bean is instantiated
                visitBody(n);
            }
            out.popIndent();
            out.printil("}");

            // End of lock block
            out.popIndent();
            out.printil("}");

            n.setEndJavaLine(out.getJavaLine());
        }
        
        /**
         * @return a string for the form 'attr = "value"'
         */
        private String makeAttr(String attr, String value) {
            if (value == null)
                return "";

            return " " + attr + "=\"" + value + '\"';
        }

        public void visit(Node.PlugIn n) throws JasperException {

            /**
             * A visitor to handle  in a plugin
             */
            class ParamVisitor extends Node.Visitor {

                private boolean ie;

                ParamVisitor(boolean ie) {
                    this.ie = ie;
                }

                public void visit(Node.ParamAction n) throws JasperException {

                    String name = n.getAttributeValue("name");
                    if (name.equalsIgnoreCase("object"))
                        name = "java_object";
                    else if (name.equalsIgnoreCase ("type"))
                        name = "java_type";

                    n.setBeginJavaLine(out.getJavaLine());
                    if( ie ) {
                        // We want something of the form
                        // out.println( "" );
                        out.printil( "out.write( \"\" );" );
                        out.printil("out.write(\"\\n\");");
                    }
                    else {
                        // We want something of the form
                        // out.print( " blah=\"" + ... + "\"" );
                        out.printil( "out.write( \" " + escape( name ) +
                            "=\\\"\" + " +
                            attributeValue( n.getValue(), false) +
                            " + \"\\\"\" );" );
                    }
                    n.setEndJavaLine(out.getJavaLine());
                }
            }

            String type = n.getAttributeValue("type");
            String code = n.getAttributeValue("code");
            String name = n.getAttributeValue("name");
            Node.JspAttribute height = n.getHeight();
            Node.JspAttribute width = n.getWidth();
            String hspace = n.getAttributeValue("hspace");
            String vspace = n.getAttributeValue("vspace");
            String align = n.getAttributeValue("align");
            String iepluginurl = n.getAttributeValue("iepluginurl");
            String nspluginurl = n.getAttributeValue("nspluginurl");
            String codebase = n.getAttributeValue("codebase");
            String archive = n.getAttributeValue("archive");
            String jreversion = n.getAttributeValue("jreversion");

            if (iepluginurl == null)
                iepluginurl = Constants.IE_PLUGIN_URL;
            if (nspluginurl == null)
                nspluginurl = Constants.NS_PLUGIN_URL;


            n.setBeginJavaLine(out.getJavaLine());
            // IE style plugin
            // 
            // First compose the runtime output string 
            String s0 = "');
            // Then print the output string to the java file
            out.printil("out.println(" + s1 + " + " + s2 + " + " + s3 + ");");

            //  for java_code
            s0 = "';
            out.printil("out.println(" + quote(s0) + ");");

            //  for java_codebase
            if (codebase != null) {
                s0 = "';
                out.printil("out.println(" + quote(s0) + ");");
            }

            //  for java_archive
            if (archive != null) {
                s0 = "';
                out.printil("out.println(" + quote(s0) + ");");
            }

            //  for type
            s0 = " for each  in the plugin body
             */
            if (n.getBody() != null)
                n.getBody().visit(new ParamVisitor(true));

            /*
             * Netscape style plugin part
             */
            out.printil("out.println(" + quote("") + ");");
            s0 = " in plugin body
             */
            if (n.getBody() != null)
                n.getBody().visit(new ParamVisitor(false)); 

            out.printil("out.println(" + quote(">") + ");");

            out.printil("out.println(" + quote("") + ");");
            out.printil("out.println(" + quote("</COMMENT>") + ");");

            /*
             * Fallback
             */
            if (n.getBody() != null) {
                visitBody(n);
                out.printil("out.write(\"\\n\");");
            }

            out.printil("out.println(" + quote("") + ");");
            out.printil("out.println(" + quote("") + ");");

            n.setEndJavaLine(out.getJavaLine());
        }

        public void visit(Node.CustomTag n) throws JasperException {

            Hashtable handlerInfosByShortName = (Hashtable)
                handlerInfos.get(n.getPrefix());
            if (handlerInfosByShortName == null) {
                handlerInfosByShortName = new Hashtable();
                handlerInfos.put(n.getPrefix(), handlerInfosByShortName);
            }
            TagHandlerInfo handlerInfo = (TagHandlerInfo)
                handlerInfosByShortName.get(n.getShortName());
            if (handlerInfo == null) {
                handlerInfo = new TagHandlerInfo(n,
                                                 n.getTagHandlerClass(),
                                                 err);
                handlerInfosByShortName.put(n.getShortName(), handlerInfo);
            }

            // Create variable names
            String baseVar = createTagVarName(n.getName(), n.getPrefix(),
                                              n.getShortName());
            String tagEvalVar = "_jspx_eval_" + baseVar;
            String tagHandlerVar = "_jspx_th_" + baseVar;
            String tagPushBodyCountVar = "_jspx_push_body_count_" + baseVar;

            // If the tag contains no scripting element, generate its codes
            // to a method.
            ServletWriter outSave = null;
            MethodsBuffer methodsBufferSave = null;
            if (n.isScriptless() && !n.hasScriptingVars()) {
                // The tag handler and its body code can reside in a separate
                // method if it is scriptless and does not have any scripting
                // variable defined.

                String tagMethod = "_jspx_meth_" + baseVar;

                // Generate a call to this method
                out.printin("if (");
                out.print(tagMethod);
                out.print("(");
                if (parent != null) {
                    out.print(parent);
                    out.print(", ");
                }
//                out.println("pageContext, _jspxState)");
                out.print("pageContext");
                if (pushBodyCountVar != null) {
                    out.print(", ");
                    out.print(pushBodyCountVar);
                }
                out.println("))");
                out.pushIndent();
                out.printil((methodNesting > 0)? "return true;": "return;");
                out.popIndent();

                // Set up new buffer for the method
                outSave = out;
                out = methodsBuffer.getOut();
                methodsBufferSave = methodsBuffer;
                methodsBuffer = new MethodsBuffer();

                methodNesting++;
                // Generate code for method declaration
                out.println();
                out.pushIndent();
                out.printin("private boolean ");
                out.print(tagMethod);
                out.print("(");
                if (parent != null) {
                    out.print("javax.servlet.jsp.tagext.Tag ");
                    out.print(parent);
                    out.print(", ");
                }
//                out.println("javax.servlet.jsp.PageContext pageContext, JspxState _jspxState)");
                out.print("javax.servlet.jsp.PageContext pageContext");
                if (pushBodyCountVar != null) {
                    out.print(", int[] ");
                    out.print(pushBodyCountVar);
                }
                out.println(")");
                out.printil("        throws Throwable {");
                out.pushIndent();

                // Initilaize local variables used in this method.
                out.printil("JspWriter out = pageContext.getOut();");
                if (n.isHasUsebean()) {
                    out.println("HttpSession session = pageContext.getSession();");
                    out.println("ServletContext application = pageContext.getServletContext();");
                }
                if (n.isHasUsebean() || n.isHasIncludeAction() || n.isHasSetProperty()) {
                    out.println("HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();");
                }
                if (n.isHasIncludeAction()) {
                    out.println("HttpServletResponse response = (HttpServletResponse)pageContext.getResponse();");
                }
            }

            // Generate code for start tag, body, and end tag
            generateCustomStart(n, handlerInfo, tagHandlerVar, tagEvalVar,
                                tagPushBodyCountVar);

            String tmpParent = parent;
            parent = tagHandlerVar;
            String tmpPushBodyCountVar = null;
            if (n.implementsTryCatchFinally()) {
                tmpPushBodyCountVar = pushBodyCountVar;
                pushBodyCountVar = tagPushBodyCountVar;
            }

            visitBody(n);

            parent = tmpParent;
            if (n.implementsTryCatchFinally()) {
                pushBodyCountVar = tmpPushBodyCountVar;
            }

            generateCustomEnd(n, tagHandlerVar, tagEvalVar,
                              tagPushBodyCountVar);

            if (n.isScriptless() && !n.hasScriptingVars()) {
                // Generate end of method
                if (methodNesting > 0) {
                    out.printil("return false;");
                }
                out.popIndent();
                out.printil("}");
                out.popIndent();

                methodNesting--;

                // Append any methods that got generated in the body to the
                // current buffer
                out.print(methodsBuffer.toString());

                // restore previous buffer
                methodsBuffer = methodsBufferSave;
                out = outSave;
            }
        }

        private static final String SINGLE_QUOTE = "'";
        private static final String DOUBLE_QUOTE = "\\\"";

        public void visit(Node.UninterpretedTag n) throws JasperException {

            /*
             * Write begin tag
             */
            out.printin("out.write(\"<");
            out.print(n.getName());
            Attributes attrs = n.getAttributes();
            if (attrs != null) {
                int attrsLength = attrs.getLength();
                for (int i=0; i\");");
                
                // Visit tag body
                visitBody(n);

                /*
                 * Write end tag
                 */
                out.printin("out.write(\"\");");
            } else {
                out.println("/>\");");
            }
        }

        private static final int CHUNKSIZE = 1024;

        public void visit(Node.TemplateText n) throws JasperException {

            char[] chars = n.getText();
            int size = chars.length;

            n.setBeginJavaLine(out.getJavaLine());

            out.printin();
            StringBuffer sb = new StringBuffer("out.write(\"");
            int initLength = sb.length();
            int count = CHUNKSIZE;
            for (int i = 0 ; i < size ; i++) {
                char ch = chars[i];
                --count;
                switch(ch) {
                case '"':
                    sb.append('\\').append('\"');
                    break;
                case '\\':
                    sb.append('\\').append('\\');
                    break;
                case '\r':
                    sb.append('\\').append('r');
                    break;
                case '\n':
                    sb.append('\\').append('n');

                    if (breakAtLF || count < 0) {
                        // Generate an out.write() when see a '\n' in template
                        sb.append("\");");
                        out.println(sb.toString());
                        out.printin();
                        sb.setLength(initLength);
                        count = CHUNKSIZE;
                    }
                    break;
                case '\t':        // Not sure we need this
                    sb.append('\\').append('t');
                    break;
                default:
                    sb.append(ch);
                }
            }

            if (sb.length() > initLength) {
                sb.append("\");");
                  out.println(sb.toString());
            }

            n.setEndJavaLine(out.getJavaLine());
        }

        private void generateCustomStart(Node.CustomTag n,
                                         TagHandlerInfo handlerInfo,
                                         String tagHandlerVar,
                                         String tagEvalVar,
                                         String tagPushBodyCountVar)
                            throws JasperException {

            Class tagHandlerClass = handlerInfo.getTagHandlerClass();

            n.setBeginJavaLine(out.getJavaLine());
            out.printin("/* ----  ");
            out.print(n.getName());
            out.println(" ---- */");

            // Declare AT_BEGIN scripting variables
            declareScriptingVars(n, VariableInfo.AT_BEGIN);
            saveScriptingVars(n, VariableInfo.AT_BEGIN);

            out.printin(tagHandlerClass.getName());
            out.print(" ");
            out.print(tagHandlerVar);
            out.print(" = ");
            if (ctxt.getOptions().isPoolingEnabled()) {
                out.print("(");
                out.print(tagHandlerClass.getName());
                out.print(") ");
                out.print(n.getTagHandlerPoolName());
                out.print(".get(");
                out.print(tagHandlerClass.getName());
                out.println(".class);");
            } else {
                out.print("new ");
                out.print(tagHandlerClass.getName());
                out.println("();");
            }

            generateSetters(n, tagHandlerVar, handlerInfo);
            
            if (n.implementsTryCatchFinally()) {
                out.printin("int[] ");
                out.print(tagPushBodyCountVar);
                out.println(" = new int[] { 0 };");
                out.printil("try {");
                out.pushIndent();
            }

            out.printin("int ");
            out.print(tagEvalVar);
            out.print(" = ");
            out.print(tagHandlerVar);
            out.println(".doStartTag();");

            if (!n.implementsBodyTag()) {
                // Synchronize AT_BEGIN scripting variables
                syncScriptingVars(n, VariableInfo.AT_BEGIN);
            }

            if (n.getBody() != null) {
                out.printin("if (");
                out.print(tagEvalVar);
                out.println(" != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {");
                out.pushIndent();
                
                // Declare NESTED scripting variables
                declareScriptingVars(n, VariableInfo.NESTED);
                saveScriptingVars(n, VariableInfo.NESTED);

                if (n.implementsBodyTag()) {
                    out.printin("if (");
                    out.print(tagEvalVar);
                    out.println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {");
                    // Assume EVAL_BODY_BUFFERED
                    out.pushIndent();
                    out.printil("javax.servlet.jsp.tagext.BodyContent _bc = pageContext.pushBody();");
                    if (n.implementsTryCatchFinally()) {
                        out.printin(tagPushBodyCountVar);
                        out.println("[0]++;");
                    } else if (pushBodyCountVar != null) {
                        out.printin(pushBodyCountVar);
                        out.println("[0]++;");
                    }
                    out.printil("out = _bc;");

                    out.printin(tagHandlerVar);
                    out.println(".setBodyContent(_bc);");
                    out.printin(tagHandlerVar);
                    out.println(".doInitBody();");

                    out.popIndent();
                    out.printil("}");

                    // Synchronize AT_BEGIN and NESTED scripting variables
                    syncScriptingVars(n, VariableInfo.AT_BEGIN);
                    syncScriptingVars(n, VariableInfo.NESTED);
                        
                } else {
                    // Synchronize NESTED scripting variables
                    syncScriptingVars(n, VariableInfo.NESTED);
                }

                if (n.implementsIterationTag()) {
                    out.printil("do {");
                    out.pushIndent();
                }
            }
        };
        
        private void generateCustomEnd(Node.CustomTag n,
                                       String tagHandlerVar,
                                       String tagEvalVar,
                                       String tagPushBodyCountVar) {

            VariableInfo[] varInfos = n.getVariableInfos();
            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();

            if (n.getBody() != null) {
                if (n.implementsIterationTag()) {
                    out.printin("int evalDoAfterBody = ");
                    out.print(tagHandlerVar);
                    out.println(".doAfterBody();");

                    // Synchronize AT_BEGIN and NESTED scripting variables
                    syncScriptingVars(n, VariableInfo.AT_BEGIN);
                    syncScriptingVars(n, VariableInfo.NESTED);

                    out.printil("if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)");
                    out.pushIndent();
                    out.printil("break;");
                    out.popIndent();

                    out.popIndent();
                    out.printil("} while (true);");
                }
                
                restoreScriptingVars(n, VariableInfo.NESTED);

                if (n.implementsBodyTag()) {
                    out.printin("if (");
                    out.print(tagEvalVar);
                    out.println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE)");
                    out.pushIndent();
                    out.printil("out = pageContext.popBody();");
                    if (n.implementsTryCatchFinally()) {
                        out.printin(tagPushBodyCountVar);
                        out.println("[0]--;");
                    } else if (pushBodyCountVar != null) {
                        out.printin(pushBodyCountVar);
                        out.println("[0]--;");
                    }
                    out.popIndent();
                }

                out.popIndent(); // EVAL_BODY
                out.printil("}");
            }

            out.printin("if (");
            out.print(tagHandlerVar);
            out.println(".doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)");
            out.pushIndent();
            out.printil((methodNesting > 0)? "return true;": "return;");
            out.popIndent();

            // Synchronize AT_BEGIN scripting variables
            syncScriptingVars(n, VariableInfo.AT_BEGIN);

            // TryCatchFinally
            if (n.implementsTryCatchFinally()) {
                out.popIndent(); // try
                out.printil("} catch (Throwable _jspx_exception) {");
                out.pushIndent();

                out.printin("while (");
                out.print(tagPushBodyCountVar);
                out.println("[0]-- > 0)");
                out.pushIndent();
                out.printil("out = pageContext.popBody();");
                out.popIndent();

                out.printin(tagHandlerVar);
                out.println(".doCatch(_jspx_exception);");
                out.popIndent();
                out.printil("} finally {");
                out.pushIndent();
                out.printin(tagHandlerVar);
                out.println(".doFinally();");
            }

            if (ctxt.getOptions().isPoolingEnabled()) {
                out.printin(n.getTagHandlerPoolName());
                out.print(".reuse(");
                out.print(tagHandlerVar);
                out.println(");");
            }

            if (n.implementsTryCatchFinally()) {
                out.popIndent();
                out.printil("}");
            }

            // Declare and synchronize AT_END scripting variables (must do this
            // outside the try/catch/finally block)
            declareScriptingVars(n, VariableInfo.AT_END);
            syncScriptingVars(n, VariableInfo.AT_END);

            restoreScriptingVars(n, VariableInfo.AT_BEGIN);

            n.setEndJavaLine(out.getJavaLine());
        }

        private void declareScriptingVars(Node.CustomTag n, int scope) {
            
            Vector vec = n.getScriptingVars(scope);
            if (vec != null) {
                for (int i=0; i= 0)
                prefix = JspUtil.replace(prefix, '-', "$1");
            if (prefix.indexOf('.') >= 0)
                prefix = JspUtil.replace(prefix, '.', "$2");

            if (shortName.indexOf('-') >= 0)
                shortName = JspUtil.replace(shortName, '-', "$1");
            if (shortName.indexOf('.') >= 0)
                shortName = JspUtil.replace(shortName, '.', "$2");
            if (shortName.indexOf(':') >= 0)
                shortName = JspUtil.replace(shortName, ':', "$3");

            synchronized (tagVarNumbers) {
                String varName = prefix + "_" + shortName + "_";
                if (tagVarNumbers.get(fullName) != null) {
                    Integer i = (Integer) tagVarNumbers.get(fullName);
                    varName = varName + i.intValue();
                    tagVarNumbers.put(fullName, new Integer(i.intValue() + 1));
                    return varName;
                } else {
                    tagVarNumbers.put(fullName, new Integer(1));
                    return varName + "0";
                }
            }
        }

        private void generateSetters(Node.CustomTag n, String tagHandlerVar,
                                     TagHandlerInfo handlerInfo)
                    throws JasperException {

            out.printin(tagHandlerVar);
            out.println(".setPageContext(pageContext);");
            out.printin(tagHandlerVar);
            out.print(".setParent(");
            out.print(parent);
            out.println(");");

            Node.JspAttribute[] attrs = n.getJspAttributes();
            for (int i=0; i 0)

                if (!attrs[i].isExpression()) {
                    attrValue = convertString(c[0], attrValue, attrName,
                                              handlerInfo.getPropertyEditorClass(attrName));
                }
                
                out.printin(tagHandlerVar);
                out.print(".");
                out.print(m.getName());
                out.print("(");
                out.print(attrValue);
                out.println(");");
            }
        }

        private String convertString(Class c, String s, String attrName,
                                     Class propEditorClass)
                    throws JasperException {
            
            if (propEditorClass != null) {
                return "(" + c.getName()
                    + ")JspRuntimeLibrary.getValueFromBeanInfoPropertyEditor("
                    + c.getName() + ".class, \"" + attrName + "\", "
                    + quote(s) + ", "
                    + propEditorClass.getName() + ".class)";
            } else if (c == String.class) {
                return quote(s);
            } else if (c == boolean.class) {
                return Boolean.valueOf(s).toString();
            } else if (c == Boolean.class) {
                return "new Boolean(" + Boolean.valueOf(s).toString() + ")";
            } else if (c == byte.class) {
                return "((byte)" + Byte.valueOf(s).toString() + ")";
            } else if (c == Byte.class) {
                return "new Byte((byte)" + Byte.valueOf(s).toString() + ")";
            } else if (c == char.class) {
                // non-normative (normative method would fail to compile)
                if (s.length() > 0) {
                    char ch = s.charAt(0);
                    // this trick avoids escaping issues
                    return "((char) " + (int) ch + ")";
                } else {
                    throw new NumberFormatException(
                        err.getString("jsp.error.bad_string_char"));
                }
            } else if (c == Character.class) {
                // non-normative (normative method would fail to compile)
                if (s.length() > 0) {
                    char ch = s.charAt(0);
                    // this trick avoids escaping issues
                    return "new Character((char) " + (int) ch + ")";
                } else {
                    throw new NumberFormatException(
                        err.getString("jsp.error.bad_string_Character"));
                }
            } else if (c == double.class) {
                return Double.valueOf(s).toString();
            } else if (c == Double.class) {
                return "new Double(" + Double.valueOf(s).toString() + ")";
            } else if (c == float.class) {
                return Float.valueOf(s).toString() + "f";
            } else if (c == Float.class) {
                return "new Float(" + Float.valueOf(s).toString() + "f)";
            } else if (c == int.class) {
                return Integer.valueOf(s).toString();
            } else if (c == Integer.class) {
                return "new Integer(" + Integer.valueOf(s).toString() + ")";
            } else if (c == short.class) {
                return "((short) " + Short.valueOf(s).toString() + ")";
            } else if (c == Short.class) {
                return "new Short(" + Short.valueOf(s).toString() + ")";
            } else if (c == long.class) {
                return Long.valueOf(s).toString() + "l";
            } else if (c == Long.class) {
                return "new Long(" + Long.valueOf(s).toString() + "l)";
            } else if (c == Object.class) {
                return "new String(" + quote(s) + ")";
            } else {
                return "(" + c.getName()
                    + ")JspRuntimeLibrary.getValueFromPropertyEditorManager("
                    + c.getName() + ".class, \"" + attrName + "\", "
                    + quote(s) + ")";
            }
        }   
    }

    /**
     * Generates the ending part of the static portion of the servelet.
     */
    private void generatePostamble(Node.Nodes page) {
        out.popIndent();
        out.printil("} catch (Throwable t) {");
        out.pushIndent();

        out.printil("out = _jspx_out;");
        out.printil("if (out != null && out.getBufferSize() != 0)");
        out.pushIndent();
        out.printil("out.clearBuffer();");
        out.popIndent();

        out.printil("if (pageContext != null) pageContext.handlePageException(t);");

        out.popIndent();
        out.printil("} finally {");
        out.pushIndent();

        out.printil("if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);");

        out.popIndent();
        out.printil("}");

        // Close the service method
        out.popIndent();
        out.printil("}");

        // Append any methods that were generated
        out.print(methodsBuffer.toString());

        // generate class definition for JspxState
        if (maxTagNesting > 0) {
            generateJspState();
        }

        // Close the class definition
        out.popIndent();
        out.printil("}");
    }

    /**
     * Constructor.
     */
    Generator(ServletWriter out, Compiler compiler) {
        this.out = out;
        methodsBuffer = new MethodsBuffer();
        err = compiler.getErrorDispatcher();
        ctxt = compiler.getCompilationContext();
        pageInfo = compiler.getPageInfo();
        beanInfo = pageInfo.getBeanRepository();
        breakAtLF = ctxt.getOptions().getMappedFile();
        if (ctxt.getOptions().isPoolingEnabled()) {
            tagHandlerPoolNames = new Vector();
        }
    }

    /**
     * The main entry for Generator.
     * @param out The servlet output writer
     * @param compiler The compiler
     * @param page The input page
     */
    public static void generate(ServletWriter out, Compiler compiler,
                                Node.Nodes page) throws JasperException {
        Generator gen = new Generator(out, compiler);

        if (gen.ctxt.getOptions().isPoolingEnabled()) {
            gen.compileTagHandlerPoolList(page);
        }
        gen.generatePreamble(page);
        page.visit(gen.new GenerateVisitor(out, gen.methodsBuffer));
        gen.generatePostamble(page);
    }

    /**
     * Class storing the result of introspecting a custom tag handler.
     */
    private static class TagHandlerInfo {

        private Hashtable methodMaps;
        private Hashtable propertyEditorMaps;
        private Class tagHandlerClass;
    
        /**
         * Constructor.
         *
         * @param n The custom tag whose tag handler class is to be
         * introspected
         * @param tagHandlerClass Tag handler class
         * @param err Error dispatcher
         */
        TagHandlerInfo(Node n, Class tagHandlerClass, ErrorDispatcher err)
            throws JasperException
        {
            this.tagHandlerClass = tagHandlerClass;
            this.methodMaps = new Hashtable();
            this.propertyEditorMaps = new Hashtable();

            try {
                BeanInfo tagClassInfo
                    = Introspector.getBeanInfo(tagHandlerClass);
                PropertyDescriptor[] pd
                    = tagClassInfo.getPropertyDescriptors();
                for (int i=0; i