You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/09/28 03:55:35 UTC

svn commit: r819444 [17/27] - in /struts/struts2/trunk/plugins/embeddedjsp: ./ src/main/java/org/apache/struts2/el/ src/main/java/org/apache/struts2/el/lang/ src/main/java/org/apache/struts2/el/parser/ src/main/java/org/apache/struts2/el/util/ src/main...

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,726 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.HashMap;
+
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.servlet.JspServletWrapper;
+import org.apache.struts2.jasper.runtime.JspSourceDependent;
+
+/**
+ * 1. Processes and extracts the directive info in a tag file. 2. Compiles and
+ * loads tag files used in a JSP file.
+ * 
+ * @author Kin-man Chung
+ */
+
+class TagFileProcessor {
+
+    private Vector tempVector;
+
+    /**
+     * A visitor the tag file
+     */
+    private static class TagFileDirectiveVisitor extends Node.Visitor {
+
+        private static final JspUtil.ValidAttribute[] tagDirectiveAttrs = {
+                new JspUtil.ValidAttribute("display-name"),
+                new JspUtil.ValidAttribute("body-content"),
+                new JspUtil.ValidAttribute("dynamic-attributes"),
+                new JspUtil.ValidAttribute("small-icon"),
+                new JspUtil.ValidAttribute("large-icon"),
+                new JspUtil.ValidAttribute("description"),
+                new JspUtil.ValidAttribute("example"),
+                new JspUtil.ValidAttribute("pageEncoding"),
+                new JspUtil.ValidAttribute("language"),
+                new JspUtil.ValidAttribute("import"),
+                new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"), // JSP 2.1
+                new JspUtil.ValidAttribute("trimDirectiveWhitespaces"), // JSP 2.1
+                new JspUtil.ValidAttribute("isELIgnored") };
+
+        private static final JspUtil.ValidAttribute[] attributeDirectiveAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("required"),
+                new JspUtil.ValidAttribute("fragment"),
+                new JspUtil.ValidAttribute("rtexprvalue"),
+                new JspUtil.ValidAttribute("type"),
+                new JspUtil.ValidAttribute("deferredValue"),            // JSP 2.1
+                new JspUtil.ValidAttribute("deferredValueType"),        // JSP 2.1
+                new JspUtil.ValidAttribute("deferredMethod"),           // JSP 2
+                new JspUtil.ValidAttribute("deferredMethodSignature"),  // JSP 21
+                new JspUtil.ValidAttribute("description") };
+
+        private static final JspUtil.ValidAttribute[] variableDirectiveAttrs = {
+                new JspUtil.ValidAttribute("name-given"),
+                new JspUtil.ValidAttribute("name-from-attribute"),
+                new JspUtil.ValidAttribute("alias"),
+                new JspUtil.ValidAttribute("variable-class"),
+                new JspUtil.ValidAttribute("scope"),
+                new JspUtil.ValidAttribute("declare"),
+                new JspUtil.ValidAttribute("description") };
+
+        private ErrorDispatcher err;
+
+        private TagLibraryInfo tagLibInfo;
+
+        private String name = null;
+
+        private String path = null;
+
+        private TagExtraInfo tei = null;
+
+        private String bodycontent = null;
+
+        private String description = null;
+
+        private String displayName = null;
+
+        private String smallIcon = null;
+
+        private String largeIcon = null;
+
+        private String dynamicAttrsMapName;
+
+        private String example = null;
+
+        private Vector attributeVector;
+
+        private Vector variableVector;
+
+        private static final String ATTR_NAME = "the name attribute of the attribute directive";
+
+        private static final String VAR_NAME_GIVEN = "the name-given attribute of the variable directive";
+
+        private static final String VAR_NAME_FROM = "the name-from-attribute attribute of the variable directive";
+
+        private static final String VAR_ALIAS = "the alias attribute of the variable directive";
+
+        private static final String TAG_DYNAMIC = "the dynamic-attributes attribute of the tag directive";
+
+        private HashMap nameTable = new HashMap();
+
+        private HashMap nameFromTable = new HashMap();
+
+        public TagFileDirectiveVisitor(Compiler compiler,
+                TagLibraryInfo tagLibInfo, String name, String path) {
+            err = compiler.getErrorDispatcher();
+            this.tagLibInfo = tagLibInfo;
+            this.name = name;
+            this.path = path;
+            attributeVector = new Vector();
+            variableVector = new Vector();
+        }
+
+        public void visit(Node.TagDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Tag directive", n, tagDirectiveAttrs, err);
+
+            bodycontent = checkConflict(n, bodycontent, "body-content");
+            if (bodycontent != null
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                err.jspError(n, "jsp.error.tagdirective.badbodycontent",
+                        bodycontent);
+            }
+            dynamicAttrsMapName = checkConflict(n, dynamicAttrsMapName,
+                    "dynamic-attributes");
+            if (dynamicAttrsMapName != null) {
+                checkUniqueName(dynamicAttrsMapName, TAG_DYNAMIC, n);
+            }
+            smallIcon = checkConflict(n, smallIcon, "small-icon");
+            largeIcon = checkConflict(n, largeIcon, "large-icon");
+            description = checkConflict(n, description, "description");
+            displayName = checkConflict(n, displayName, "display-name");
+            example = checkConflict(n, example, "example");
+        }
+
+        private String checkConflict(Node n, String oldAttrValue, String attr)
+                throws JasperException {
+
+            String result = oldAttrValue;
+            String attrValue = n.getAttributeValue(attr);
+            if (attrValue != null) {
+                if (oldAttrValue != null && !oldAttrValue.equals(attrValue)) {
+                    err.jspError(n, "jsp.error.tag.conflict.attr", attr,
+                            oldAttrValue, attrValue);
+                }
+                result = attrValue;
+            }
+            return result;
+        }
+
+        public void visit(Node.AttributeDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Attribute directive", n,
+                    attributeDirectiveAttrs, err);
+
+            // JSP 2.1 Table JSP.8-3
+            // handle deferredValue and deferredValueType
+            boolean deferredValue = false;
+            boolean deferredValueSpecified = false;
+            String deferredValueString = n.getAttributeValue("deferredValue");
+            if (deferredValueString != null) {
+                deferredValueSpecified = true;
+                deferredValue = JspUtil.booleanValue(deferredValueString);
+            }
+            String deferredValueType = n.getAttributeValue("deferredValueType");
+            if (deferredValueType != null) {
+                if (deferredValueSpecified && !deferredValue) {
+                    err.jspError(n, "jsp.error.deferredvaluetypewithoutdeferredvalue");
+                } else {
+                    deferredValue = true;
+                }
+            } else if (deferredValue) {
+                deferredValueType = "java.lang.Object";
+            } else {
+                deferredValueType = "java.lang.String";
+            }
+
+            // JSP 2.1 Table JSP.8-3
+            // handle deferredMethod and deferredMethodSignature
+            boolean deferredMethod = false;
+            boolean deferredMethodSpecified = false;
+            String deferredMethodString = n.getAttributeValue("deferredMethod");
+            if (deferredMethodString != null) {
+                deferredMethodSpecified = true;
+                deferredMethod = JspUtil.booleanValue(deferredMethodString);
+            }
+            String deferredMethodSignature = n
+                    .getAttributeValue("deferredMethodSignature");
+            if (deferredMethodSignature != null) {
+                if (deferredMethodSpecified && !deferredMethod) {
+                    err.jspError(n, "jsp.error.deferredmethodsignaturewithoutdeferredmethod");
+                } else {
+                    deferredMethod = true;
+                }
+            } else if (deferredMethod) {
+                deferredMethodSignature = "void methodname()";
+            }
+
+            if (deferredMethod && deferredValue) {
+                err.jspError(n, "jsp.error.deferredmethodandvalue");
+            }
+            
+            String attrName = n.getAttributeValue("name");
+            boolean required = JspUtil.booleanValue(n
+                    .getAttributeValue("required"));
+            boolean rtexprvalue = true;
+            String rtexprvalueString = n.getAttributeValue("rtexprvalue");
+            if (rtexprvalueString != null) {
+                rtexprvalue = JspUtil.booleanValue(rtexprvalueString);
+            }
+            boolean fragment = JspUtil.booleanValue(n
+                    .getAttributeValue("fragment"));
+            String type = n.getAttributeValue("type");
+            if (fragment) {
+                // type is fixed to "JspFragment" and a translation error
+                // must occur if specified.
+                if (type != null) {
+                    err.jspError(n, "jsp.error.fragmentwithtype");
+                }
+                // rtexprvalue is fixed to "true" and a translation error
+                // must occur if specified.
+                rtexprvalue = true;
+                if (rtexprvalueString != null) {
+                    err.jspError(n, "jsp.error.frgmentwithrtexprvalue");
+                }
+            } else {
+                if (type == null)
+                    type = "java.lang.String";
+                
+                if (deferredValue) {
+                    type = ValueExpression.class.getName();
+                } else if (deferredMethod) {
+                    type = MethodExpression.class.getName();
+                }
+            }
+
+            if (("2.0".equals(tagLibInfo.getRequiredVersion()) || ("1.2".equals(tagLibInfo.getRequiredVersion())))
+                    && (deferredMethodSpecified || deferredMethod
+                            || deferredValueSpecified || deferredValue)) {
+                err.jspError("jsp.error.invalid.version", path);
+            }
+            
+            TagAttributeInfo tagAttributeInfo = new TagAttributeInfo(attrName,
+                    required, type, rtexprvalue, fragment, null, deferredValue,
+                    deferredMethod, deferredValueType, deferredMethodSignature);
+            attributeVector.addElement(tagAttributeInfo);
+            checkUniqueName(attrName, ATTR_NAME, n, tagAttributeInfo);
+        }
+
+        public void visit(Node.VariableDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Variable directive", n,
+                    variableDirectiveAttrs, err);
+
+            String nameGiven = n.getAttributeValue("name-given");
+            String nameFromAttribute = n
+                    .getAttributeValue("name-from-attribute");
+            if (nameGiven == null && nameFromAttribute == null) {
+                err.jspError("jsp.error.variable.either.name");
+            }
+
+            if (nameGiven != null && nameFromAttribute != null) {
+                err.jspError("jsp.error.variable.both.name");
+            }
+
+            String alias = n.getAttributeValue("alias");
+            if (nameFromAttribute != null && alias == null
+                    || nameFromAttribute == null && alias != null) {
+                err.jspError("jsp.error.variable.alias");
+            }
+
+            String className = n.getAttributeValue("variable-class");
+            if (className == null)
+                className = "java.lang.String";
+
+            String declareStr = n.getAttributeValue("declare");
+            boolean declare = true;
+            if (declareStr != null)
+                declare = JspUtil.booleanValue(declareStr);
+
+            int scope = VariableInfo.NESTED;
+            String scopeStr = n.getAttributeValue("scope");
+            if (scopeStr != null) {
+                if ("NESTED".equals(scopeStr)) {
+                    // Already the default
+                } else if ("AT_BEGIN".equals(scopeStr)) {
+                    scope = VariableInfo.AT_BEGIN;
+                } else if ("AT_END".equals(scopeStr)) {
+                    scope = VariableInfo.AT_END;
+                }
+            }
+
+            if (nameFromAttribute != null) {
+                /*
+                 * An alias has been specified. We use 'nameGiven' to hold the
+                 * value of the alias, and 'nameFromAttribute' to hold the name
+                 * of the attribute whose value (at invocation-time) denotes the
+                 * name of the variable that is being aliased
+                 */
+                nameGiven = alias;
+                checkUniqueName(nameFromAttribute, VAR_NAME_FROM, n);
+                checkUniqueName(alias, VAR_ALIAS, n);
+            } else {
+                // name-given specified
+                checkUniqueName(nameGiven, VAR_NAME_GIVEN, n);
+            }
+
+            variableVector.addElement(new TagVariableInfo(nameGiven,
+                    nameFromAttribute, className, declare, scope));
+        }
+
+        /*
+         * Returns the vector of attributes corresponding to attribute
+         * directives.
+         */
+        public Vector getAttributesVector() {
+            return attributeVector;
+        }
+
+        /*
+         * Returns the vector of variables corresponding to variable directives.
+         */
+        public Vector getVariablesVector() {
+            return variableVector;
+        }
+
+        /*
+         * Returns the value of the dynamic-attributes tag directive attribute.
+         */
+        public String getDynamicAttributesMapName() {
+            return dynamicAttrsMapName;
+        }
+
+        public TagInfo getTagInfo() throws JasperException {
+
+            if (name == null) {
+                // XXX Get it from tag file name
+            }
+
+            if (bodycontent == null) {
+                bodycontent = TagInfo.BODY_CONTENT_SCRIPTLESS;
+            }
+
+            String tagClassName = JspUtil.getTagHandlerClassName(
+                    path, tagLibInfo.getReliableURN(), err);
+
+            TagVariableInfo[] tagVariableInfos = new TagVariableInfo[variableVector
+                    .size()];
+            variableVector.copyInto(tagVariableInfos);
+
+            TagAttributeInfo[] tagAttributeInfo = new TagAttributeInfo[attributeVector
+                    .size()];
+            attributeVector.copyInto(tagAttributeInfo);
+
+            return new JasperTagInfo(name, tagClassName, bodycontent,
+                    description, tagLibInfo, tei, tagAttributeInfo,
+                    displayName, smallIcon, largeIcon, tagVariableInfos,
+                    dynamicAttrsMapName);
+        }
+
+        static class NameEntry {
+            private String type;
+
+            private Node node;
+
+            private TagAttributeInfo attr;
+
+            NameEntry(String type, Node node, TagAttributeInfo attr) {
+                this.type = type;
+                this.node = node;
+                this.attr = attr;
+            }
+
+            String getType() {
+                return type;
+            }
+
+            Node getNode() {
+                return node;
+            }
+
+            TagAttributeInfo getTagAttributeInfo() {
+                return attr;
+            }
+        }
+
+        /**
+         * Reports a translation error if names specified in attributes of
+         * directives are not unique in this translation unit.
+         * 
+         * The value of the following attributes must be unique. 1. 'name'
+         * attribute of an attribute directive 2. 'name-given' attribute of a
+         * variable directive 3. 'alias' attribute of variable directive 4.
+         * 'dynamic-attributes' of a tag directive except that
+         * 'dynamic-attributes' can (and must) have the same value when it
+         * appears in multiple tag directives.
+         * 
+         * Also, 'name-from' attribute of a variable directive cannot have the
+         * same value as that from another variable directive.
+         */
+        private void checkUniqueName(String name, String type, Node n)
+                throws JasperException {
+            checkUniqueName(name, type, n, null);
+        }
+
+        private void checkUniqueName(String name, String type, Node n,
+                TagAttributeInfo attr) throws JasperException {
+
+            HashMap table = (type == VAR_NAME_FROM) ? nameFromTable : nameTable;
+            NameEntry nameEntry = (NameEntry) table.get(name);
+            if (nameEntry != null) {
+                if (type != TAG_DYNAMIC || nameEntry.getType() != TAG_DYNAMIC) {
+                    int line = nameEntry.getNode().getStart().getLineNumber();
+                    err.jspError(n, "jsp.error.tagfile.nameNotUnique", type,
+                            nameEntry.getType(), Integer.toString(line));
+                }
+            } else {
+                table.put(name, new NameEntry(type, n, attr));
+            }
+        }
+
+        /**
+         * Perform miscellean checks after the nodes are visited.
+         */
+        void postCheck() throws JasperException {
+            // Check that var.name-from-attributes has valid values.
+            Iterator iter = nameFromTable.keySet().iterator();
+            while (iter.hasNext()) {
+                String nameFrom = (String) iter.next();
+                NameEntry nameEntry = (NameEntry) nameTable.get(nameFrom);
+                NameEntry nameFromEntry = (NameEntry) nameFromTable
+                        .get(nameFrom);
+                Node nameFromNode = nameFromEntry.getNode();
+                if (nameEntry == null) {
+                    err.jspError(nameFromNode,
+                            "jsp.error.tagfile.nameFrom.noAttribute", nameFrom);
+                } else {
+                    Node node = nameEntry.getNode();
+                    TagAttributeInfo tagAttr = nameEntry.getTagAttributeInfo();
+                    if (!"java.lang.String".equals(tagAttr.getTypeName())
+                            || !tagAttr.isRequired()
+                            || tagAttr.canBeRequestTime()) {
+                        err.jspError(nameFromNode,
+                                "jsp.error.tagfile.nameFrom.badAttribute",
+                                nameFrom, Integer.toString(node.getStart()
+                                        .getLineNumber()));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parses the tag file, and collects information on the directives included
+     * in it. The method is used to obtain the info on the tag file, when the
+     * handler that it represents is referenced. The tag file is not compiled
+     * here.
+     * 
+     * @param pc
+     *            the current ParserController used in this compilation
+     * @param name
+     *            the tag name as specified in the TLD
+     * @param tagfile
+     *            the path for the tagfile
+     * @param tagLibInfo
+     *            the TagLibraryInfo object associated with this TagInfo
+     * @return a TagInfo object assembled from the directives in the tag file.
+     * @deprecated Use {@link TagFileProcessor#parseTagFileDirectives(
+     *                  ParserController, String, String, URL, TagLibraryInfo)}
+     *             See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+     */
+    public static TagInfo parseTagFileDirectives(ParserController pc,
+            String name, String path, TagLibraryInfo tagLibInfo)
+            throws JasperException {
+        return parseTagFileDirectives(pc, name, path,
+                pc.getJspCompilationContext().getTagFileJarUrl(path),
+                tagLibInfo);
+    }
+    
+    /**
+     * Parses the tag file, and collects information on the directives included
+     * in it. The method is used to obtain the info on the tag file, when the
+     * handler that it represents is referenced. The tag file is not compiled
+     * here.
+     * 
+     * @param pc
+     *            the current ParserController used in this compilation
+     * @param name
+     *            the tag name as specified in the TLD
+     * @param tagfile
+     *            the path for the tagfile
+     * @param tagFileJarUrl
+     *            the url for the Jar containign the tag file 
+     * @param tagLibInfo
+     *            the TagLibraryInfo object associated with this TagInfo
+     * @return a TagInfo object assembled from the directives in the tag file.
+     */
+    public static TagInfo parseTagFileDirectives(ParserController pc,
+            String name, String path, URL tagFileJarUrl, TagLibraryInfo tagLibInfo)
+            throws JasperException {
+
+        ErrorDispatcher err = pc.getCompiler().getErrorDispatcher();
+
+        Node.Nodes page = null;
+        try {
+            page = pc.parseTagFileDirectives(path, tagFileJarUrl);
+        } catch (FileNotFoundException e) {
+            err.jspError("jsp.error.file.not.found", path);
+        } catch (IOException e) {
+            err.jspError("jsp.error.file.not.found", path);
+        }
+
+        TagFileDirectiveVisitor tagFileVisitor = new TagFileDirectiveVisitor(pc
+                .getCompiler(), tagLibInfo, name, path);
+        page.visit(tagFileVisitor);
+        tagFileVisitor.postCheck();
+
+        return tagFileVisitor.getTagInfo();
+    }
+
+    /**
+     * Compiles and loads a tagfile.
+     */
+    private Class loadTagFile(Compiler compiler, String tagFilePath,
+            TagInfo tagInfo, PageInfo parentPageInfo) throws JasperException {
+
+        URL tagFileJarUrl = null;
+        if (tagFilePath.startsWith("/META-INF/")) {
+            try { 
+                tagFileJarUrl = new URL("jar:" +
+                        compiler.getCompilationContext().getTldLocation(
+                        tagInfo.getTagLibrary().getURI())[0] + "!/");
+            } catch (MalformedURLException e) {
+                // Ignore - tagFileJarUrl will be null
+            }
+        }
+        String tagFileJarPath;
+        if (tagFileJarUrl == null) {
+            tagFileJarPath = "";
+        } else {
+            tagFileJarPath = tagFileJarUrl.toString();
+        }
+
+        JspCompilationContext ctxt = compiler.getCompilationContext();
+        JspRuntimeContext rctxt = ctxt.getRuntimeContext();
+        JspServletWrapper wrapper = rctxt.getWrapper(tagFileJarPath + tagFilePath);
+
+        synchronized (rctxt) {
+            if (wrapper == null) {
+                wrapper = new JspServletWrapper(ctxt.getServletContext(), ctxt
+                        .getOptions(), tagFilePath, tagInfo, ctxt
+                        .getRuntimeContext(), tagFileJarUrl);
+                rctxt.addWrapper(tagFileJarPath + tagFilePath, wrapper);
+
+                // Use same classloader and classpath for compiling tag files
+                wrapper.getJspEngineContext().setClassLoader(
+                        (URLClassLoader) ctxt.getClassLoader());
+                wrapper.getJspEngineContext().setClassPath(ctxt.getClassPath());
+            } else {
+                // Make sure that JspCompilationContext gets the latest TagInfo
+                // for the tag file. TagInfo instance was created the last
+                // time the tag file was scanned for directives, and the tag
+                // file may have been modified since then.
+                wrapper.getJspEngineContext().setTagInfo(tagInfo);
+            }
+
+            Class tagClazz;
+            int tripCount = wrapper.incTripCount();
+            try {
+                if (tripCount > 0) {
+                    // When tripCount is greater than zero, a circular
+                    // dependency exists. The circularily dependant tag
+                    // file is compiled in prototype mode, to avoid infinite
+                    // recursion.
+
+                    JspServletWrapper tempWrapper = new JspServletWrapper(ctxt
+                            .getServletContext(), ctxt.getOptions(),
+                            tagFilePath, tagInfo, ctxt.getRuntimeContext(),
+                            ctxt.getTagFileJarUrl(tagFilePath));
+                    tagClazz = tempWrapper.loadTagFilePrototype();
+                    tempVector.add(tempWrapper.getJspEngineContext()
+                            .getCompiler());
+                } else {
+                    tagClazz = wrapper.loadTagFile();
+                }
+            } finally {
+                wrapper.decTripCount();
+            }
+
+            // Add the dependants for this tag file to its parent's
+            // dependant list. The only reliable dependency information
+            // can only be obtained from the tag instance.
+            try {
+                Object tagIns = tagClazz.newInstance();
+                if (tagIns instanceof JspSourceDependent) {
+                    Iterator iter = ((List) ((JspSourceDependent) tagIns)
+                            .getDependants()).iterator();
+                    while (iter.hasNext()) {
+                        parentPageInfo.addDependant((String) iter.next());
+                    }
+                }
+            } catch (Exception e) {
+                // ignore errors
+            }
+
+            return tagClazz;
+        }
+    }
+
+    /*
+     * Visitor which scans the page and looks for tag handlers that are tag
+     * files, compiling (if necessary) and loading them.
+     */
+    private class TagFileLoaderVisitor extends Node.Visitor {
+
+        private Compiler compiler;
+
+        private PageInfo pageInfo;
+
+        TagFileLoaderVisitor(Compiler compiler) {
+
+            this.compiler = compiler;
+            this.pageInfo = compiler.getPageInfo();
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            TagFileInfo tagFileInfo = n.getTagFileInfo();
+            if (tagFileInfo != null) {
+                String tagFilePath = tagFileInfo.getPath();
+                if (tagFilePath.startsWith("/META-INF/")) {
+                    // For tags in JARs, add the TLD and the tag as a dependency
+                    String[] location =
+                        compiler.getCompilationContext().getTldLocation(
+                            tagFileInfo.getTagInfo().getTagLibrary().getURI());
+                    // Add TLD
+                    pageInfo.addDependant("jar:" + location[0] + "!/" +
+                            location[1]);
+                    // Add Tag
+                    pageInfo.addDependant("jar:" + location[0] + "!" +
+                            tagFilePath);
+                } else {
+                    pageInfo.addDependant(tagFilePath);
+                }
+                Class c = loadTagFile(compiler, tagFilePath, n.getTagInfo(),
+                        pageInfo);
+                n.setTagHandlerClass(c);
+            }
+            visitBody(n);
+        }
+    }
+
+    /**
+     * Implements a phase of the translation that compiles (if necessary) the
+     * tag files used in a JSP files. The directives in the tag files are
+     * assumed to have been proccessed and encapsulated as TagFileInfo in the
+     * CustomTag nodes.
+     */
+    public void loadTagFiles(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+
+        tempVector = new Vector();
+        page.visit(new TagFileLoaderVisitor(compiler));
+    }
+
+    /**
+     * Removed the java and class files for the tag prototype generated from the
+     * current compilation.
+     * 
+     * @param classFileName
+     *            If non-null, remove only the class file with with this name.
+     */
+    public void removeProtoTypeFiles(String classFileName) {
+        Iterator iter = tempVector.iterator();
+        while (iter.hasNext()) {
+            Compiler c = (Compiler) iter.next();
+            if (classFileName == null) {
+                c.removeGeneratedClassFiles();
+            } else if (classFileName.equals(c.getCompilationContext()
+                    .getClassFileName())) {
+                c.removeGeneratedClassFiles();
+                tempVector.remove(c);
+                return;
+            }
+        }
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,768 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.TagLibraryValidator;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.ValidationMessage;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.xmlparser.ParserUtils;
+import org.apache.struts2.jasper.xmlparser.TreeNode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Implementation of the TagLibraryInfo class from the JSP spec.
+ * 
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ */
+class TagLibraryInfoImpl extends TagLibraryInfo implements TagConstants {
+
+    // Logger
+    private Log log = LogFactory.getLog(TagLibraryInfoImpl.class);
+
+    private JspCompilationContext ctxt;
+    
+    private PageInfo pi;
+
+    private ErrorDispatcher err;
+
+    private ParserController parserController;
+
+    private final void print(String name, String value, PrintWriter w) {
+        if (value != null) {
+            w.print(name + " = {\n\t");
+            w.print(value);
+            w.print("\n}\n");
+        }
+    }
+
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter out = new PrintWriter(sw);
+        print("tlibversion", tlibversion, out);
+        print("jspversion", jspversion, out);
+        print("shortname", shortname, out);
+        print("urn", urn, out);
+        print("info", info, out);
+        print("uri", uri, out);
+        print("tagLibraryValidator", "" + tagLibraryValidator, out);
+
+        for (int i = 0; i < tags.length; i++)
+            out.println(tags[i].toString());
+
+        for (int i = 0; i < tagFiles.length; i++)
+            out.println(tagFiles[i].toString());
+
+        for (int i = 0; i < functions.length; i++)
+            out.println(functions[i].toString());
+
+        return sw.toString();
+    }
+
+    // XXX FIXME
+    // resolveRelativeUri and/or getResourceAsStream don't seem to properly
+    // handle relative paths when dealing when home and getDocBase are set
+    // the following is a workaround until these problems are resolved.
+    private InputStream getResourceAsStream(String uri)
+            throws FileNotFoundException {
+        try {
+            // see if file exists on the filesystem first
+            String real = ctxt.getRealPath(uri);
+            if (real == null) {
+                return ctxt.getResourceAsStream(uri);
+            } else {
+                return new FileInputStream(real);
+            }
+        } catch (FileNotFoundException ex) {
+            // if file not found on filesystem, get the resource through
+            // the context
+            return ctxt.getResourceAsStream(uri);
+        }
+
+    }
+
+    /**
+     * Constructor.
+     */
+    public TagLibraryInfoImpl(JspCompilationContext ctxt, ParserController pc, PageInfo pi,
+            String prefix, String uriIn, String[] location, ErrorDispatcher err)
+            throws JasperException {
+        super(prefix, uriIn);
+
+        this.ctxt = ctxt;
+        this.parserController = pc;
+        this.pi = pi;
+        this.err = err;
+        InputStream in = null;
+        JarFile jarFile = null;
+
+        if (location == null) {
+            // The URI points to the TLD itself or to a JAR file in which the
+            // TLD is stored
+            location = generateTLDLocation(uri, ctxt);
+        }
+
+        try {
+            if (!location[0].endsWith("jar")) {
+                // Location points to TLD file
+                try {
+                    in = getResourceAsStream(location[0]);
+                    if (in == null) {
+                        throw new FileNotFoundException(location[0]);
+                    }
+                } catch (FileNotFoundException ex) {
+                    err.jspError("jsp.error.file.not.found", location[0]);
+                }
+
+                parseTLD(ctxt, location[0], in, null);
+                // Add TLD to dependency list
+                PageInfo pageInfo = ctxt.createCompiler().getPageInfo();
+                if (pageInfo != null) {
+                    pageInfo.addDependant(location[0]);
+                }
+            } else {
+                // Tag library is packaged in JAR file
+                try {
+                    URL jarFileUrl = new URL("jar:" + location[0] + "!/");
+                    JarURLConnection conn = (JarURLConnection) jarFileUrl
+                            .openConnection();
+                    conn.setUseCaches(false);
+                    conn.connect();
+                    jarFile = conn.getJarFile();
+                    ZipEntry jarEntry = jarFile.getEntry(location[1]);
+                    in = jarFile.getInputStream(jarEntry);
+                    parseTLD(ctxt, location[0], in, jarFileUrl);
+                } catch (Exception ex) {
+                    err.jspError("jsp.error.tld.unable_to_read", location[0],
+                            location[1], ex.toString());
+                }
+            }
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (Throwable t) {
+                }
+            }
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                }
+            }
+        }
+
+    }
+
+    public TagLibraryInfo[] getTagLibraryInfos() {
+        Collection coll = pi.getTaglibs();
+        return (TagLibraryInfo[]) coll.toArray(new TagLibraryInfo[0]);
+    }
+    
+    /*
+     * @param ctxt The JSP compilation context @param uri The TLD's uri @param
+     * in The TLD's input stream @param jarFileUrl The JAR file containing the
+     * TLD, or null if the tag library is not packaged in a JAR
+     */
+    private void parseTLD(JspCompilationContext ctxt, String uri,
+            InputStream in, URL jarFileUrl) throws JasperException {
+        Vector tagVector = new Vector();
+        Vector tagFileVector = new Vector();
+        Hashtable functionTable = new Hashtable();
+
+        // Create an iterator over the child elements of our <taglib> element
+        ParserUtils pu = new ParserUtils();
+        TreeNode tld = pu.parseXMLDocument(uri, in);
+
+        // Check to see if the <taglib> root element contains a 'version'
+        // attribute, which was added in JSP 2.0 to replace the <jsp-version>
+        // subelement
+        this.jspversion = tld.findAttribute("version");
+
+        // Process each child element of our <taglib> element
+        Iterator list = tld.findChildren();
+
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+
+            if ("tlibversion".equals(tname) // JSP 1.1
+                    || "tlib-version".equals(tname)) { // JSP 1.2
+                this.tlibversion = element.getBody();
+            } else if ("jspversion".equals(tname)
+                    || "jsp-version".equals(tname)) {
+                this.jspversion = element.getBody();
+            } else if ("shortname".equals(tname) || "short-name".equals(tname))
+                this.shortname = element.getBody();
+            else if ("uri".equals(tname))
+                this.urn = element.getBody();
+            else if ("info".equals(tname) || "description".equals(tname))
+                this.info = element.getBody();
+            else if ("validator".equals(tname))
+                this.tagLibraryValidator = createValidator(element);
+            else if ("tag".equals(tname))
+                tagVector.addElement(createTagInfo(element, jspversion));
+            else if ("tag-file".equals(tname)) {
+                TagFileInfo tagFileInfo = createTagFileInfo(element, uri,
+                        jarFileUrl);
+                tagFileVector.addElement(tagFileInfo);
+            } else if ("function".equals(tname)) { // JSP2.0
+                FunctionInfo funcInfo = createFunctionInfo(element);
+                String funcName = funcInfo.getName();
+                if (functionTable.containsKey(funcName)) {
+                    err.jspError("jsp.error.tld.fn.duplicate.name", funcName,
+                            uri);
+
+                }
+                functionTable.put(funcName, funcInfo);
+            } else if ("display-name".equals(tname) || // Ignored elements
+                    "small-icon".equals(tname) || "large-icon".equals(tname)
+                    || "listener".equals(tname)) {
+                ;
+            } else if ("taglib-extension".equals(tname)) {
+                // Recognized but ignored
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.taglib", tname));
+                }
+            }
+
+        }
+
+        if (tlibversion == null) {
+            err.jspError("jsp.error.tld.mandatory.element.missing",
+                    "tlib-version");
+        }
+        if (jspversion == null) {
+            err.jspError("jsp.error.tld.mandatory.element.missing",
+                    "jsp-version");
+        }
+
+        this.tags = new TagInfo[tagVector.size()];
+        tagVector.copyInto(this.tags);
+
+        this.tagFiles = new TagFileInfo[tagFileVector.size()];
+        tagFileVector.copyInto(this.tagFiles);
+
+        this.functions = new FunctionInfo[functionTable.size()];
+        int i = 0;
+        Enumeration enumeration = functionTable.elements();
+        while (enumeration.hasMoreElements()) {
+            this.functions[i++] = (FunctionInfo) enumeration.nextElement();
+        }
+    }
+
+    /*
+     * @param uri The uri of the TLD @param ctxt The compilation context
+     * 
+     * @return String array whose first element denotes the path to the TLD. If
+     * the path to the TLD points to a jar file, then the second element denotes
+     * the name of the TLD entry in the jar file, which is hardcoded to
+     * META-INF/taglib.tld.
+     */
+    private String[] generateTLDLocation(String uri, JspCompilationContext ctxt)
+            throws JasperException {
+
+        int uriType = TldLocationsCache.uriType(uri);
+        if (uriType == TldLocationsCache.ABS_URI) {
+            err.jspError("jsp.error.taglibDirective.absUriCannotBeResolved",
+                    uri);
+        } else if (uriType == TldLocationsCache.NOROOT_REL_URI) {
+            uri = ctxt.resolveRelativeUri(uri);
+        }
+
+        String[] location = new String[2];
+        location[0] = uri;
+        if (location[0].endsWith("jar")) {
+            URL url = null;
+            try {
+                url = ctxt.getResource(location[0]);
+            } catch (Exception ex) {
+                err.jspError("jsp.error.tld.unable_to_get_jar", location[0], ex
+                        .toString());
+            }
+            if (url == null) {
+                err.jspError("jsp.error.tld.missing_jar", location[0]);
+            }
+            location[0] = url.toString();
+            location[1] = "META-INF/taglib.tld";
+        }
+
+        return location;
+    }
+
+    private TagInfo createTagInfo(TreeNode elem, String jspVersion)
+            throws JasperException {
+
+        String tagName = null;
+        String tagClassName = null;
+        String teiClassName = null;
+
+        /*
+         * Default body content for JSP 1.2 tag handlers (<body-content> has
+         * become mandatory in JSP 2.0, because the default would be invalid for
+         * simple tag handlers)
+         */
+        String bodycontent = "JSP";
+
+        String info = null;
+        String displayName = null;
+        String smallIcon = null;
+        String largeIcon = null;
+        boolean dynamicAttributes = false;
+
+        Vector attributeVector = new Vector();
+        Vector variableVector = new Vector();
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                tagName = element.getBody();
+            } else if ("tagclass".equals(tname) || "tag-class".equals(tname)) {
+                tagClassName = element.getBody();
+            } else if ("teiclass".equals(tname) || "tei-class".equals(tname)) {
+                teiClassName = element.getBody();
+            } else if ("bodycontent".equals(tname)
+                    || "body-content".equals(tname)) {
+                bodycontent = element.getBody();
+            } else if ("display-name".equals(tname)) {
+                displayName = element.getBody();
+            } else if ("small-icon".equals(tname)) {
+                smallIcon = element.getBody();
+            } else if ("large-icon".equals(tname)) {
+                largeIcon = element.getBody();
+            } else if ("icon".equals(tname)) {
+                TreeNode icon = element.findChild("small-icon");
+                if (icon != null) {
+                    smallIcon = icon.getBody();
+                }
+                icon = element.findChild("large-icon");
+                if (icon != null) {
+                    largeIcon = icon.getBody();
+                }
+            } else if ("info".equals(tname) || "description".equals(tname)) {
+                info = element.getBody();
+            } else if ("variable".equals(tname)) {
+                variableVector.addElement(createVariable(element));
+            } else if ("attribute".equals(tname)) {
+                attributeVector
+                        .addElement(createAttribute(element, jspVersion));
+            } else if ("dynamic-attributes".equals(tname)) {
+                dynamicAttributes = JspUtil.booleanValue(element.getBody());
+            } else if ("example".equals(tname)) {
+                // Ignored elements
+            } else if ("tag-extension".equals(tname)) {
+                // Ignored
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.tag", tname));
+                }
+            }
+        }
+
+        TagExtraInfo tei = null;
+        if (teiClassName != null && !teiClassName.equals("")) {
+            try {
+                Class teiClass = ctxt.getClassLoader().loadClass(teiClassName);
+                tei = (TagExtraInfo) teiClass.newInstance();
+            } catch (Exception e) {
+                err.jspError("jsp.error.teiclass.instantiation", teiClassName,
+                        e);
+            }
+        }
+
+        TagAttributeInfo[] tagAttributeInfo = new TagAttributeInfo[attributeVector
+                .size()];
+        attributeVector.copyInto(tagAttributeInfo);
+
+        TagVariableInfo[] tagVariableInfos = new TagVariableInfo[variableVector
+                .size()];
+        variableVector.copyInto(tagVariableInfos);
+
+        TagInfo taginfo = new TagInfo(tagName, tagClassName, bodycontent, info,
+                this, tei, tagAttributeInfo, displayName, smallIcon, largeIcon,
+                tagVariableInfos, dynamicAttributes);
+        return taginfo;
+    }
+
+    /*
+     * Parses the tag file directives of the given TagFile and turns them into a
+     * TagInfo.
+     * 
+     * @param elem The <tag-file> element in the TLD @param uri The location of
+     * the TLD, in case the tag file is specified relative to it @param jarFile
+     * The JAR file, in case the tag file is packaged in a JAR
+     * 
+     * @return TagInfo correspoding to tag file directives
+     */
+    private TagFileInfo createTagFileInfo(TreeNode elem, String uri,
+            URL jarFileUrl) throws JasperException {
+
+        String name = null;
+        String path = null;
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode child = (TreeNode) list.next();
+            String tname = child.getName();
+            if ("name".equals(tname)) {
+                name = child.getBody();
+            } else if ("path".equals(tname)) {
+                path = child.getBody();
+            } else if ("example".equals(tname)) {
+                // Ignore <example> element: Bugzilla 33538
+            } else if ("tag-extension".equals(tname)) {
+                // Ignore <tag-extension> element: Bugzilla 33538
+            } else if ("icon".equals(tname) 
+                    || "display-name".equals(tname) 
+                    || "description".equals(tname)) {
+                // Ignore these elements: Bugzilla 38015
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.tagfile", tname));
+                }
+            }
+        }
+
+        if (path.startsWith("/META-INF/tags")) {
+            // Tag file packaged in JAR
+            // See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+            // This needs to be removed once all the broken code that depends on
+            // it has been removed
+            ctxt.setTagFileJarUrl(path, jarFileUrl);
+        } else if (!path.startsWith("/WEB-INF/tags")) {
+            err.jspError("jsp.error.tagfile.illegalPath", path);
+        }
+
+        TagInfo tagInfo = TagFileProcessor.parseTagFileDirectives(
+                parserController, name, path, jarFileUrl, this);
+        return new TagFileInfo(name, path, tagInfo);
+    }
+
+    TagAttributeInfo createAttribute(TreeNode elem, String jspVersion) {
+        String name = null;
+        String type = null;
+        String expectedType = null;
+        String methodSignature = null;
+        boolean required = false, rtexprvalue = false, reqTime = false, isFragment = false, deferredValue = false, deferredMethod = false;
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                name = element.getBody();
+            } else if ("required".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    required = JspUtil.booleanValue(s);
+            } else if ("rtexprvalue".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    rtexprvalue = JspUtil.booleanValue(s);
+            } else if ("type".equals(tname)) {
+                type = element.getBody();
+                if ("1.2".equals(jspVersion)
+                        && (type.equals("Boolean") || type.equals("Byte")
+                                || type.equals("Character")
+                                || type.equals("Double")
+                                || type.equals("Float")
+                                || type.equals("Integer")
+                                || type.equals("Long") || type.equals("Object")
+                                || type.equals("Short") || type
+                                .equals("String"))) {
+                    type = "java.lang." + type;
+                }
+            } else if ("fragment".equals(tname)) {
+                String s = element.getBody();
+                if (s != null) {
+                    isFragment = JspUtil.booleanValue(s);
+                }
+            } else if ("deferred-value".equals(tname)) {
+                deferredValue = true;
+                type = "javax.el.ValueExpression";
+                TreeNode child = element.findChild("type");
+                if (child != null) {
+                    expectedType = child.getBody();
+                    if (expectedType != null) {
+                        expectedType = expectedType.trim();
+                    }
+                } else {
+                    expectedType = "java.lang.Object";
+                }
+            } else if ("deferred-method".equals(tname)) {
+                deferredMethod = true;
+                type = "javax.el.MethodExpression";
+                TreeNode child = element.findChild("method-signature");
+                if (child != null) {
+                    methodSignature = child.getBody();
+                    if (methodSignature != null) {
+                        methodSignature = methodSignature.trim();
+                    }
+                } else {
+                    methodSignature = "java.lang.Object method()";
+                }
+            } else if ("description".equals(tname) || // Ignored elements
+            false) {
+                ;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.attribute", tname));
+                }
+            }
+        }
+
+        if (isFragment) {
+            /*
+             * According to JSP.C-3 ("TLD Schema Element Structure - tag"),
+             * 'type' and 'rtexprvalue' must not be specified if 'fragment' has
+             * been specified (this will be enforced by validating parser).
+             * Also, if 'fragment' is TRUE, 'type' is fixed at
+             * javax.servlet.jsp.tagext.JspFragment, and 'rtexprvalue' is fixed
+             * at true. See also JSP.8.5.2.
+             */
+            type = "javax.servlet.jsp.tagext.JspFragment";
+            rtexprvalue = true;
+        }
+
+        if (!rtexprvalue && type == null) {
+            // According to JSP spec, for static values (those determined at
+            // translation time) the type is fixed at java.lang.String.
+            type = "java.lang.String";
+        }
+        
+        return new TagAttributeInfo(name, required, type, rtexprvalue,
+                isFragment, null, deferredValue, deferredMethod, expectedType,
+                methodSignature);
+    }
+
+    TagVariableInfo createVariable(TreeNode elem) {
+        String nameGiven = null;
+        String nameFromAttribute = null;
+        String className = "java.lang.String";
+        boolean declare = true;
+        int scope = VariableInfo.NESTED;
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+            if ("name-given".equals(tname))
+                nameGiven = element.getBody();
+            else if ("name-from-attribute".equals(tname))
+                nameFromAttribute = element.getBody();
+            else if ("variable-class".equals(tname))
+                className = element.getBody();
+            else if ("declare".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    declare = JspUtil.booleanValue(s);
+            } else if ("scope".equals(tname)) {
+                String s = element.getBody();
+                if (s != null) {
+                    if ("NESTED".equals(s)) {
+                        scope = VariableInfo.NESTED;
+                    } else if ("AT_BEGIN".equals(s)) {
+                        scope = VariableInfo.AT_BEGIN;
+                    } else if ("AT_END".equals(s)) {
+                        scope = VariableInfo.AT_END;
+                    }
+                }
+            } else if ("description".equals(tname) || // Ignored elements
+            false) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.variable", tname));
+                }
+            }
+        }
+        return new TagVariableInfo(nameGiven, nameFromAttribute, className,
+                declare, scope);
+    }
+
+    private TagLibraryValidator createValidator(TreeNode elem)
+            throws JasperException {
+
+        String validatorClass = null;
+        Map initParams = new Hashtable();
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+            if ("validator-class".equals(tname))
+                validatorClass = element.getBody();
+            else if ("init-param".equals(tname)) {
+                String[] initParam = createInitParam(element);
+                initParams.put(initParam[0], initParam[1]);
+            } else if ("description".equals(tname) || // Ignored elements
+            false) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.validator", tname));
+                }
+            }
+        }
+
+        TagLibraryValidator tlv = null;
+        if (validatorClass != null && !validatorClass.equals("")) {
+            try {
+                Class tlvClass = ctxt.getClassLoader()
+                        .loadClass(validatorClass);
+                tlv = (TagLibraryValidator) tlvClass.newInstance();
+            } catch (Exception e) {
+                err.jspError("jsp.error.tlvclass.instantiation",
+                        validatorClass, e);
+            }
+        }
+        if (tlv != null) {
+            tlv.setInitParameters(initParams);
+        }
+        return tlv;
+    }
+
+    String[] createInitParam(TreeNode elem) {
+        String[] initParam = new String[2];
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+            if ("param-name".equals(tname)) {
+                initParam[0] = element.getBody();
+            } else if ("param-value".equals(tname)) {
+                initParam[1] = element.getBody();
+            } else if ("description".equals(tname)) {
+                 // Do nothing
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.initParam", tname));
+                }
+            }
+        }
+        return initParam;
+    }
+
+    FunctionInfo createFunctionInfo(TreeNode elem) {
+
+        String name = null;
+        String klass = null;
+        String signature = null;
+
+        Iterator list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = (TreeNode) list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                name = element.getBody();
+            } else if ("function-class".equals(tname)) {
+                klass = element.getBody();
+            } else if ("function-signature".equals(tname)) {
+                signature = element.getBody();
+            } else if ("display-name".equals(tname) || // Ignored elements
+                    "small-icon".equals(tname) || "large-icon".equals(tname)
+                    || "description".equals(tname) || "example".equals(tname)) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.function", tname));
+                }
+            }
+        }
+
+        return new FunctionInfo(name, klass, signature);
+    }
+
+    // *********************************************************************
+    // Until javax.servlet.jsp.tagext.TagLibraryInfo is fixed
+
+    /**
+     * The instance (if any) for the TagLibraryValidator class.
+     * 
+     * @return The TagLibraryValidator instance, if any.
+     */
+    public TagLibraryValidator getTagLibraryValidator() {
+        return tagLibraryValidator;
+    }
+
+    /**
+     * Translation-time validation of the XML document associated with the JSP
+     * page. This is a convenience method on the associated TagLibraryValidator
+     * class.
+     * 
+     * @param thePage
+     *            The JSP page object
+     * @return A string indicating whether the page is valid or not.
+     */
+    public ValidationMessage[] validate(PageData thePage) {
+        TagLibraryValidator tlv = getTagLibraryValidator();
+        if (tlv == null)
+            return null;
+
+        String uri = getURI();
+        if (uri.startsWith("/")) {
+            uri = URN_JSPTLD + uri;
+        }
+
+        return tlv.validate(getPrefixString(), uri, thePage);
+    }
+
+    protected TagLibraryValidator tagLibraryValidator;
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import java.util.*;
+import java.io.*;
+import javax.servlet.ServletContext;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.xmlparser.ParserUtils;
+import org.apache.struts2.jasper.xmlparser.TreeNode;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+/**
+ * Manages tag plugin optimizations.
+ * @author Kin-man Chung
+ */
+
+public class TagPluginManager {
+
+    private static final String TAG_PLUGINS_XML = "/WEB-INF/tagPlugins.xml";
+    private static final String TAG_PLUGINS_ROOT_ELEM = "tag-plugins";
+
+    private boolean initialized = false;
+    private HashMap tagPlugins = null;
+    private ServletContext ctxt;
+    private PageInfo pageInfo;
+
+    public TagPluginManager(ServletContext ctxt) {
+	this.ctxt = ctxt;
+    }
+
+    public void apply(Node.Nodes page, ErrorDispatcher err, PageInfo pageInfo)
+	    throws JasperException {
+
+	init(err);
+	if (tagPlugins == null || tagPlugins.size() == 0) {
+	    return;
+	}
+
+	this.pageInfo = pageInfo;
+
+        page.visit(new Node.Visitor() {
+            public void visit(Node.CustomTag n)
+                    throws JasperException {
+                invokePlugin(n);
+                visitBody(n);
+            }
+        });
+
+    }
+ 
+    private void init(ErrorDispatcher err) throws JasperException {
+	if (initialized)
+	    return;
+
+	InputStream is = ctxt.getResourceAsStream(TAG_PLUGINS_XML);
+	if (is == null)
+	    return;
+
+	TreeNode root = (new ParserUtils()).parseXMLDocument(TAG_PLUGINS_XML,
+							     is);
+	if (root == null) {
+	    return;
+	}
+
+	if (!TAG_PLUGINS_ROOT_ELEM.equals(root.getName())) {
+	    err.jspError("jsp.error.plugin.wrongRootElement", TAG_PLUGINS_XML,
+			 TAG_PLUGINS_ROOT_ELEM);
+	}
+
+	tagPlugins = new HashMap();
+	Iterator pluginList = root.findChildren("tag-plugin");
+	while (pluginList.hasNext()) {
+	    TreeNode pluginNode = (TreeNode) pluginList.next();
+            TreeNode tagClassNode = pluginNode.findChild("tag-class");
+	    if (tagClassNode == null) {
+		// Error
+		return;
+	    }
+	    String tagClass = tagClassNode.getBody().trim();
+	    TreeNode pluginClassNode = pluginNode.findChild("plugin-class");
+	    if (pluginClassNode == null) {
+		// Error
+		return;
+	    }
+
+	    String pluginClassStr = pluginClassNode.getBody();
+	    TagPlugin tagPlugin = null;
+	    try {
+		Class pluginClass = Class.forName(pluginClassStr);
+		tagPlugin = (TagPlugin) pluginClass.newInstance();
+	    } catch (Exception e) {
+		throw new JasperException(e);
+	    }
+	    if (tagPlugin == null) {
+		return;
+	    }
+	    tagPlugins.put(tagClass, tagPlugin);
+	}
+	initialized = true;
+    }
+
+    /**
+     * Invoke tag plugin for the given custom tag, if a plugin exists for 
+     * the custom tag's tag handler.
+     *
+     * The given custom tag node will be manipulated by the plugin.
+     */
+    private void invokePlugin(Node.CustomTag n) {
+	TagPlugin tagPlugin = (TagPlugin)
+		tagPlugins.get(n.getTagHandlerClass().getName());
+	if (tagPlugin == null) {
+	    return;
+	}
+
+	TagPluginContext tagPluginContext = new TagPluginContextImpl(n, pageInfo);
+	n.setTagPluginContext(tagPluginContext);
+	tagPlugin.doTag(tagPluginContext);
+    }
+
+    static class TagPluginContextImpl implements TagPluginContext {
+	private Node.CustomTag node;
+	private Node.Nodes curNodes;
+	private PageInfo pageInfo;
+	private HashMap pluginAttributes;
+
+	TagPluginContextImpl(Node.CustomTag n, PageInfo pageInfo) {
+	    this.node = n;
+	    this.pageInfo = pageInfo;
+	    curNodes = new Node.Nodes();
+	    n.setAtETag(curNodes);
+	    curNodes = new Node.Nodes();
+	    n.setAtSTag(curNodes);
+	    n.setUseTagPlugin(true);
+	    pluginAttributes = new HashMap();
+        }
+
+        public TagPluginContext getParentContext() {
+            Node parent = node.getParent();
+            if (! (parent instanceof Node.CustomTag)) {
+                return null;
+            }
+            return ((Node.CustomTag) parent).getTagPluginContext();
+        }
+
+        public void setPluginAttribute(String key, Object value) {
+            pluginAttributes.put(key, value);
+        }
+
+        public Object getPluginAttribute(String key) {
+            return pluginAttributes.get(key);
+        }
+
+        public boolean isScriptless() {
+            return node.getChildInfo().isScriptless();
+        }
+
+        public boolean isConstantAttribute(String attribute) {
+            Node.JspAttribute attr = getNodeAttribute(attribute);
+            if (attr == null)
+                return false;
+            return attr.isLiteral();
+        }
+
+        public String getConstantAttribute(String attribute) {
+            Node.JspAttribute attr = getNodeAttribute(attribute);
+            if (attr == null)
+                return null;
+            return attr.getValue();
+        }
+
+        public boolean isAttributeSpecified(String attribute) {
+            return getNodeAttribute(attribute) != null;
+        }
+
+        public String getTemporaryVariableName() {
+            return node.getRoot().nextTemporaryVariableName();
+        }
+
+        public void generateImport(String imp) {
+            pageInfo.addImport(imp);
+        }
+
+        public void generateDeclaration(String id, String text) {
+            if (pageInfo.isPluginDeclared(id)) {
+                return;
+            }
+            curNodes.add(new Node.Declaration(text, node.getStart(), null));
+        }
+
+        public void generateJavaSource(String sourceCode) {
+            curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(),
+                                            null));
+        }
+
+        public void generateAttribute(String attributeName) {
+            curNodes.add(new Node.AttributeGenerator(node.getStart(),
+                                                     attributeName,
+                                                     node));
+        }
+
+        public void dontUseTagPlugin() {
+            node.setUseTagPlugin(false);
+        }
+
+        public void generateBody() {
+            // Since we'll generate the body anyway, this is really a nop, 
+            // except for the fact that it lets us put the Java sources the
+            // plugins produce in the correct order (w.r.t the body).
+            curNodes = node.getAtETag();
+        }
+
+        private Node.JspAttribute getNodeAttribute(String attribute) {
+            Node.JspAttribute[] attrs = node.getJspAttributes();
+            for (int i=0; attrs != null && i < attrs.length; i++) {
+                if (attrs[i].getName().equals(attribute)) {
+                    return attrs[i];
+                }
+            }
+            return null;
+        }
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.Options;
+
+/**
+ */
+public class TextOptimizer {
+
+    /**
+     * A visitor to concatenate contiguous template texts.
+     */
+    static class TextCatVisitor extends Node.Visitor {
+
+        private Options options;
+        private PageInfo pageInfo;
+        private int textNodeCount = 0;
+        private Node.TemplateText firstTextNode = null;
+        private StringBuffer textBuffer;
+        private final String emptyText = new String("");
+
+        public TextCatVisitor(Compiler compiler) {
+            options = compiler.getCompilationContext().getOptions();
+            pageInfo = compiler.getPageInfo();
+        }
+
+        public void doVisit(Node n) throws JasperException {
+            collectText();
+        }
+
+	/*
+         * The following directis are ignored in text concatenation
+         */
+
+        public void visit(Node.PageDirective n) throws JasperException {
+        }
+
+        public void visit(Node.TagDirective n) throws JasperException {
+        }
+
+        public void visit(Node.TaglibDirective n) throws JasperException {
+        }
+
+        public void visit(Node.AttributeDirective n) throws JasperException {
+        }
+
+        public void visit(Node.VariableDirective n) throws JasperException {
+        }
+
+        /*
+         * Don't concatenate text across body boundaries
+         */
+        public void visitBody(Node n) throws JasperException {
+            super.visitBody(n);
+            collectText();
+        }
+
+        public void visit(Node.TemplateText n) throws JasperException {
+            if ((options.getTrimSpaces() || pageInfo.isTrimDirectiveWhitespaces()) 
+                    && n.isAllSpace()) {
+                n.setText(emptyText);
+                return;
+            }
+
+            if (textNodeCount++ == 0) {
+                firstTextNode = n;
+                textBuffer = new StringBuffer(n.getText());
+            } else {
+                // Append text to text buffer
+                textBuffer.append(n.getText());
+                n.setText(emptyText);
+            }
+        }
+
+        /**
+         * This method breaks concatenation mode.  As a side effect it copies
+         * the concatenated string to the first text node 
+         */
+        private void collectText() {
+
+            if (textNodeCount > 1) {
+                // Copy the text in buffer into the first template text node.
+                firstTextNode.setText(textBuffer.toString());
+            }
+            textNodeCount = 0;
+        }
+
+    }
+
+    public static void concatenate(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+
+        TextCatVisitor v = new TextCatVisitor(compiler);
+        page.visit(v);
+
+	// Cleanup, in case the page ends with a template text
+        v.collectText();
+    }
+}