copying jasper from /tc6.0.x/tags/TOMCAT_6_0_20/java/org/apache/jasper at r800587

git-svn-id: https://svn.apache.org/repos/asf/struts/sandbox/trunk@800590 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Constants.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Constants.java
new file mode 100644
index 0000000..6fbf52e
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Constants.java
@@ -0,0 +1,208 @@
+/*
+ * 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.jasper;
+
+
+/**
+ * Some constants and other global data that are used by the compiler and the runtime.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+public class Constants {
+    
+    /**
+     * The base class of the generated servlets. 
+     */
+    public static final String JSP_SERVLET_BASE = 
+        System.getProperty("org.apache.jasper.Constants.JSP_SERVLET_BASE", "org.apache.jasper.runtime.HttpJspBase");
+
+    /**
+     * _jspService is the name of the method that is called by 
+     * HttpJspBase.service(). This is where most of the code generated
+     * from JSPs go.
+     */
+    public static final String SERVICE_METHOD_NAME = 
+        System.getProperty("org.apache.jasper.Constants.SERVICE_METHOD_NAME", "_jspService");
+
+    /**
+     * Default servlet content type.
+     */
+    public static final String SERVLET_CONTENT_TYPE = "text/html";
+
+    /**
+     * These classes/packages are automatically imported by the
+     * generated code. 
+     */
+    public static final String[] STANDARD_IMPORTS = { 
+	"javax.servlet.*", 
+	"javax.servlet.http.*", 
+	"javax.servlet.jsp.*"
+    };
+
+    /**
+     * ServletContext attribute for classpath. This is tomcat specific. 
+     * Other servlet engines may choose to support this attribute if they 
+     * want to have this JSP engine running on them. 
+     */
+    public static final String SERVLET_CLASSPATH = 
+        System.getProperty("org.apache.jasper.Constants.SERVLET_CLASSPATH", "org.apache.catalina.jsp_classpath");
+
+    /**
+     * Request attribute for <code>&lt;jsp-file&gt;</code> element of a
+     * servlet definition.  If present on a request, this overrides the
+     * value returned by <code>request.getServletPath()</code> to select
+     * the JSP page to be executed.
+     */
+    public static final String JSP_FILE = 
+        System.getProperty("org.apache.jasper.Constants.JSP_FILE", "org.apache.catalina.jsp_file");
+
+
+    /**
+     * Default size of the JSP buffer.
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+    /**
+     * Default size for the tag buffers.
+     */
+    public static final int DEFAULT_TAG_BUFFER_SIZE = 512;
+
+    /**
+     * Default tag handler pool size.
+     */
+    public static final int MAX_POOL_SIZE = 5;
+
+    /**
+     * The query parameter that causes the JSP engine to just
+     * pregenerated the servlet but not invoke it. 
+     */
+    public static final String PRECOMPILE = 
+        System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile");
+
+    /**
+     * The default package name for compiled jsp pages.
+     */
+    public static final String JSP_PACKAGE_NAME = 
+        System.getProperty("org.apache.jasper.Constants.JSP_PACKAGE_NAME", "org.apache.jsp");
+
+    /**
+     * The default package name for tag handlers generated from tag files
+     */
+    public static final String TAG_FILE_PACKAGE_NAME = 
+        System.getProperty("org.apache.jasper.Constants.TAG_FILE_PACKAGE_NAME", "org.apache.jsp.tag");
+
+    /**
+     * Servlet context and request attributes that the JSP engine
+     * uses. 
+     */
+    public static final String INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
+    public static final String TMP_DIR = "javax.servlet.context.tempdir";
+
+    // Must be kept in sync with org/apache/catalina/Globals.java
+    public static final String ALT_DD_ATTR = 
+        System.getProperty("org.apache.jasper.Constants.ALT_DD_ATTR", "org.apache.catalina.deploy.alt_dd");
+
+    /**
+     * Public Id and the Resource path (of the cached copy) 
+     * of the DTDs for tag library descriptors. 
+     */
+    public static final String TAGLIB_DTD_PUBLIC_ID_11 = 
+	"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN";
+    public static final String TAGLIB_DTD_RESOURCE_PATH_11 = 
+	"/javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd";
+    public static final String TAGLIB_DTD_PUBLIC_ID_12 = 
+	"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN";
+    public static final String TAGLIB_DTD_RESOURCE_PATH_12 = 
+	"/javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd";
+
+    /**
+     * Public Id and the Resource path (of the cached copy) 
+     * of the DTDs for web application deployment descriptors
+     */
+    public static final String WEBAPP_DTD_PUBLIC_ID_22 = 
+	"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN";
+    public static final String WEBAPP_DTD_RESOURCE_PATH_22 = 
+	"/javax/servlet/resources/web-app_2_2.dtd";
+    public static final String WEBAPP_DTD_PUBLIC_ID_23 = 
+	"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN";
+    public static final String WEBAPP_DTD_RESOURCE_PATH_23 = 
+	"/javax/servlet/resources/web-app_2_3.dtd";
+
+    /**
+     * List of the Public IDs that we cache, and their
+     * associated location. This is used by 
+     * an EntityResolver to return the location of the
+     * cached copy of a DTD.
+     */
+    public static final String[] CACHED_DTD_PUBLIC_IDS = {
+	TAGLIB_DTD_PUBLIC_ID_11,
+	TAGLIB_DTD_PUBLIC_ID_12,
+	WEBAPP_DTD_PUBLIC_ID_22,
+	WEBAPP_DTD_PUBLIC_ID_23,
+    };
+    public static final String[] CACHED_DTD_RESOURCE_PATHS = {
+	TAGLIB_DTD_RESOURCE_PATH_11,
+	TAGLIB_DTD_RESOURCE_PATH_12,
+	WEBAPP_DTD_RESOURCE_PATH_22,
+	WEBAPP_DTD_RESOURCE_PATH_23,
+    };
+    
+    /**
+     * Default URLs to download the pluging for Netscape and IE.
+     */
+    public static final String NS_PLUGIN_URL = 
+        "http://java.sun.com/products/plugin/";
+
+    public static final String IE_PLUGIN_URL = 
+        "http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Version=1,2,2,0";
+
+    /**
+     * Prefix to use for generated temporary variable names
+     */
+    public static final String TEMP_VARIABLE_NAME_PREFIX =
+        System.getProperty("org.apache.jasper.Constants.TEMP_VARIABLE_NAME_PREFIX", "_jspx_temp");
+
+    /**
+     * A replacement char for "\$".
+     * XXX This is a hack to avoid changing EL interpreter to recognize "\$"
+     * @deprecated
+     */
+    public static final char ESC = '\u001b';
+    /**
+     * @deprecated
+     */
+    public static final String ESCStr = "'\\u001b'";
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED = 
+        (System.getSecurityManager() != null);
+
+    /**
+     * The name of the path parameter used to pass the session identifier
+     * back and forth with the client.
+     */
+    public static final String SESSION_PARAMETER_NAME =
+        System.getProperty("org.apache.catalina.SESSION_PARAMETER_NAME",
+                "jsessionid");
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java
new file mode 100644
index 0000000..17ec108
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/EmbeddedServletOptions.java
@@ -0,0 +1,673 @@
+/*
+ * 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.jasper;
+
+import java.io.File;
+import java.util.*;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.compiler.TldLocationsCache;
+import org.apache.jasper.compiler.JspConfig;
+import org.apache.jasper.compiler.TagPluginManager;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A class to hold all init parameters specific to the JSP engine. 
+ *
+ * @author Anil K. Vijendran
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ */
+public final class EmbeddedServletOptions implements Options {
+    
+    // Logger
+    private Log log = LogFactory.getLog(EmbeddedServletOptions.class);
+    
+    private Properties settings = new Properties();
+    
+    /**
+     * Is Jasper being used in development mode?
+     */
+    private boolean development = true;
+    
+    /**
+     * Should Ant fork its java compiles of JSP pages.
+     */
+    public boolean fork = true;
+    
+    /**
+     * Do you want to keep the generated Java files around?
+     */
+    private boolean keepGenerated = true;
+    
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    private boolean trimSpaces = false;
+    
+    /**
+     * Determines whether tag handler pooling is enabled.
+     */
+    private boolean isPoolingEnabled = true;
+    
+    /**
+     * Do you want support for "mapped" files? This will generate
+     * servlet that has a print statement per line of the JSP file.
+     * This seems like a really nice feature to have for debugging.
+     */
+    private boolean mappedFile = true;
+    
+    /**
+     * Do we want to include debugging information in the class file?
+     */
+    private boolean classDebugInfo = true;
+    
+    /**
+     * Background compile thread check interval in seconds.
+     */
+    private int checkInterval = 0;
+    
+    /**
+     * Is the generation of SMAP info for JSR45 debuggin suppressed?
+     */
+    private boolean isSmapSuppressed = false;
+    
+    /**
+     * Should SMAP info for JSR45 debugging be dumped to a file?
+     */
+    private boolean isSmapDumped = false;
+    
+    /**
+     * Are Text strings to be generated as char arrays?
+     */
+    private boolean genStringAsCharArray = false;
+    
+    private boolean errorOnUseBeanInvalidClassAttribute = true;
+    
+    /**
+     * I want to see my generated servlets. Which directory are they
+     * in?
+     */
+    private File scratchDir;
+    
+    /**
+     * Need to have this as is for versions 4 and 5 of IE. Can be set from
+     * the initParams so if it changes in the future all that is needed is
+     * to have a jsp initParam of type ieClassId="<value>"
+     */
+    private String ieClassId = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93";
+    
+    /**
+     * What classpath should I use while compiling generated servlets?
+     */
+    private String classpath = null;
+    
+    /**
+     * Compiler to use.
+     */
+    private String compiler = null;
+    
+    /**
+     * Compiler target VM.
+     */
+    private String compilerTargetVM = "1.5";
+    
+    /**
+     * The compiler source VM.
+     */
+    private String compilerSourceVM = "1.5";
+    
+    /**
+     * The compiler class name.
+     */
+    private String compilerClassName = null;
+    
+    /**
+     * Cache for the TLD locations
+     */
+    private TldLocationsCache tldLocationsCache = null;
+    
+    /**
+     * Jsp config information
+     */
+    private JspConfig jspConfig = null;
+    
+    /**
+     * TagPluginManager
+     */
+    private TagPluginManager tagPluginManager = null;
+    
+    /**
+     * Java platform encoding to generate the JSP
+     * page servlet.
+     */
+    private String javaEncoding = "UTF8";
+    
+    /**
+     * Modification test interval.
+     */
+    private int modificationTestInterval = 4;
+    
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    private boolean xpoweredBy;
+    
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    private boolean displaySourceFragment = true;
+
+    
+    public String getProperty(String name ) {
+        return settings.getProperty( name );
+    }
+    
+    public void setProperty(String name, String value ) {
+        if (name != null && value != null){ 
+            settings.setProperty( name, value );
+        }
+    }
+    
+    /**
+     * Are we keeping generated code around?
+     */
+    public boolean getKeepGenerated() {
+        return keepGenerated;
+    }
+    
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    public boolean getTrimSpaces() {
+        return trimSpaces;
+    }
+    
+    public boolean isPoolingEnabled() {
+        return isPoolingEnabled;
+    }
+    
+    /**
+     * Are we supporting HTML mapped servlets?
+     */
+    public boolean getMappedFile() {
+        return mappedFile;
+    }
+    
+    /**
+     * Should errors be sent to client or thrown into stderr?
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getSendErrorToClient() {
+        return true;
+    }
+    
+    /**
+     * Should class files be compiled with debug information?
+     */
+    public boolean getClassDebugInfo() {
+        return classDebugInfo;
+    }
+    
+    /**
+     * Background JSP compile thread check intervall
+     */
+    public int getCheckInterval() {
+        return checkInterval;
+    }
+    
+    /**
+     * Modification test interval.
+     */
+    public int getModificationTestInterval() {
+        return modificationTestInterval;
+    }
+    
+    /**
+     * Is Jasper being used in development mode?
+     */
+    public boolean getDevelopment() {
+        return development;
+    }
+    
+    /**
+     * Is the generation of SMAP info for JSR45 debuggin suppressed?
+     */
+    public boolean isSmapSuppressed() {
+        return isSmapSuppressed;
+    }
+    
+    /**
+     * Should SMAP info for JSR45 debugging be dumped to a file?
+     */
+    public boolean isSmapDumped() {
+        return isSmapDumped;
+    }
+    
+    /**
+     * Are Text strings to be generated as char arrays?
+     */
+    public boolean genStringAsCharArray() {
+        return this.genStringAsCharArray;
+    }
+    
+    /**
+     * Class ID for use in the plugin tag when the browser is IE. 
+     */
+    public String getIeClassId() {
+        return ieClassId;
+    }
+    
+    /**
+     * What is my scratch dir?
+     */
+    public File getScratchDir() {
+        return scratchDir;
+    }
+    
+    /**
+     * What classpath should I use while compiling the servlets
+     * generated from JSP files?
+     */
+    public String getClassPath() {
+        return classpath;
+    }
+    
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    public boolean isXpoweredBy() {
+        return xpoweredBy;
+    }
+    
+    /**
+     * Compiler to use.
+     */
+    public String getCompiler() {
+        return compiler;
+    }
+    
+    /**
+     * @see Options#getCompilerTargetVM
+     */
+    public String getCompilerTargetVM() {
+        return compilerTargetVM;
+    }
+    
+    /**
+     * @see Options#getCompilerSourceVM
+     */
+    public String getCompilerSourceVM() {
+        return compilerSourceVM;
+    }
+    
+    /**
+     * Java compiler class to use.
+     */
+    public String getCompilerClassName() {
+        return compilerClassName;
+    }
+
+    public boolean getErrorOnUseBeanInvalidClassAttribute() {
+        return errorOnUseBeanInvalidClassAttribute;
+    }
+    
+    public void setErrorOnUseBeanInvalidClassAttribute(boolean b) {
+        errorOnUseBeanInvalidClassAttribute = b;
+    }
+    
+    public TldLocationsCache getTldLocationsCache() {
+        return tldLocationsCache;
+    }
+    
+    public void setTldLocationsCache( TldLocationsCache tldC ) {
+        tldLocationsCache = tldC;
+    }
+    
+    public String getJavaEncoding() {
+        return javaEncoding;
+    }
+    
+    public boolean getFork() {
+        return fork;
+    }
+    
+    public JspConfig getJspConfig() {
+        return jspConfig;
+    }
+    
+    public TagPluginManager getTagPluginManager() {
+        return tagPluginManager;
+    }
+    
+    public boolean isCaching() {
+        return false;
+    }
+    
+    public Map getCache() {
+        return null;
+    }
+
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    public boolean getDisplaySourceFragment() {
+        return displaySourceFragment;
+    }
+
+    /**
+     * Create an EmbeddedServletOptions object using data available from
+     * ServletConfig and ServletContext. 
+     */
+    public EmbeddedServletOptions(ServletConfig config,
+            ServletContext context) {
+        
+        // JVM version numbers
+        try {
+            if (Float.parseFloat(System.getProperty("java.specification.version")) > 1.4) {
+                compilerSourceVM = compilerTargetVM = "1.5";
+            } else {
+                compilerSourceVM = compilerTargetVM = "1.4";
+            }
+        } catch (NumberFormatException e) {
+            // Ignore
+        }
+        
+        Enumeration enumeration=config.getInitParameterNames();
+        while( enumeration.hasMoreElements() ) {
+            String k=(String)enumeration.nextElement();
+            String v=config.getInitParameter( k );
+            setProperty( k, v);
+        }
+        
+        // quick hack
+        String validating=config.getInitParameter( "validating");
+        if( "false".equals( validating )) ParserUtils.validating=false;
+        
+        String keepgen = config.getInitParameter("keepgenerated");
+        if (keepgen != null) {
+            if (keepgen.equalsIgnoreCase("true")) {
+                this.keepGenerated = true;
+            } else if (keepgen.equalsIgnoreCase("false")) {
+                this.keepGenerated = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.keepgen"));
+                }
+            }
+        }
+        
+        
+        String trimsp = config.getInitParameter("trimSpaces"); 
+        if (trimsp != null) {
+            if (trimsp.equalsIgnoreCase("true")) {
+                trimSpaces = true;
+            } else if (trimsp.equalsIgnoreCase("false")) {
+                trimSpaces = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.trimspaces"));
+                }
+            }
+        }
+        
+        this.isPoolingEnabled = true;
+        String poolingEnabledParam
+        = config.getInitParameter("enablePooling"); 
+        if (poolingEnabledParam != null
+                && !poolingEnabledParam.equalsIgnoreCase("true")) {
+            if (poolingEnabledParam.equalsIgnoreCase("false")) {
+                this.isPoolingEnabled = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.enablePooling"));
+                }		       	   
+            }
+        }
+        
+        String mapFile = config.getInitParameter("mappedfile"); 
+        if (mapFile != null) {
+            if (mapFile.equalsIgnoreCase("true")) {
+                this.mappedFile = true;
+            } else if (mapFile.equalsIgnoreCase("false")) {
+                this.mappedFile = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.mappedFile"));
+                }
+            }
+        }
+        
+        String debugInfo = config.getInitParameter("classdebuginfo");
+        if (debugInfo != null) {
+            if (debugInfo.equalsIgnoreCase("true")) {
+                this.classDebugInfo  = true;
+            } else if (debugInfo.equalsIgnoreCase("false")) {
+                this.classDebugInfo  = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.classDebugInfo"));
+                }
+            }
+        }
+        
+        String checkInterval = config.getInitParameter("checkInterval");
+        if (checkInterval != null) {
+            try {
+                this.checkInterval = Integer.parseInt(checkInterval);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.checkInterval"));
+                }
+            }
+        }
+        
+        String modificationTestInterval = config.getInitParameter("modificationTestInterval");
+        if (modificationTestInterval != null) {
+            try {
+                this.modificationTestInterval = Integer.parseInt(modificationTestInterval);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.modificationTestInterval"));
+                }
+            }
+        }
+        
+        String development = config.getInitParameter("development");
+        if (development != null) {
+            if (development.equalsIgnoreCase("true")) {
+                this.development = true;
+            } else if (development.equalsIgnoreCase("false")) {
+                this.development = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.development"));
+                }
+            }
+        }
+        
+        String suppressSmap = config.getInitParameter("suppressSmap");
+        if (suppressSmap != null) {
+            if (suppressSmap.equalsIgnoreCase("true")) {
+                isSmapSuppressed = true;
+            } else if (suppressSmap.equalsIgnoreCase("false")) {
+                isSmapSuppressed = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.suppressSmap"));
+                }
+            }
+        }
+        
+        String dumpSmap = config.getInitParameter("dumpSmap");
+        if (dumpSmap != null) {
+            if (dumpSmap.equalsIgnoreCase("true")) {
+                isSmapDumped = true;
+            } else if (dumpSmap.equalsIgnoreCase("false")) {
+                isSmapDumped = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.dumpSmap"));
+                }
+            }
+        }
+        
+        String genCharArray = config.getInitParameter("genStrAsCharArray");
+        if (genCharArray != null) {
+            if (genCharArray.equalsIgnoreCase("true")) {
+                genStringAsCharArray = true;
+            } else if (genCharArray.equalsIgnoreCase("false")) {
+                genStringAsCharArray = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.genchararray"));
+                }
+            }
+        }
+        
+        String errBeanClass =
+            config.getInitParameter("errorOnUseBeanInvalidClassAttribute");
+        if (errBeanClass != null) {
+            if (errBeanClass.equalsIgnoreCase("true")) {
+                errorOnUseBeanInvalidClassAttribute = true;
+            } else if (errBeanClass.equalsIgnoreCase("false")) {
+                errorOnUseBeanInvalidClassAttribute = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.errBean"));
+                }
+            }
+        }
+        
+        String ieClassId = config.getInitParameter("ieClassId");
+        if (ieClassId != null)
+            this.ieClassId = ieClassId;
+        
+        String classpath = config.getInitParameter("classpath");
+        if (classpath != null)
+            this.classpath = classpath;
+        
+        /*
+         * scratchdir
+         */
+        String dir = config.getInitParameter("scratchdir"); 
+        if (dir != null) {
+            scratchDir = new File(dir);
+        } else {
+            // First try the Servlet 2.2 javax.servlet.context.tempdir property
+            scratchDir = (File) context.getAttribute(Constants.TMP_DIR);
+            if (scratchDir == null) {
+                // Not running in a Servlet 2.2 container.
+                // Try to get the JDK 1.2 java.io.tmpdir property
+                dir = System.getProperty("java.io.tmpdir");
+                if (dir != null)
+                    scratchDir = new File(dir);
+            }
+        }      
+        if (this.scratchDir == null) {
+            log.fatal(Localizer.getMessage("jsp.error.no.scratch.dir"));
+            return;
+        }
+        
+        if (!(scratchDir.exists() && scratchDir.canRead() &&
+                scratchDir.canWrite() && scratchDir.isDirectory()))
+            log.fatal(Localizer.getMessage("jsp.error.bad.scratch.dir",
+                    scratchDir.getAbsolutePath()));
+        
+        this.compiler = config.getInitParameter("compiler");
+        
+        String compilerTargetVM = config.getInitParameter("compilerTargetVM");
+        if(compilerTargetVM != null) {
+            this.compilerTargetVM = compilerTargetVM;
+        }
+        
+        String compilerSourceVM = config.getInitParameter("compilerSourceVM");
+        if(compilerSourceVM != null) {
+            this.compilerSourceVM = compilerSourceVM;
+        }
+        
+        String javaEncoding = config.getInitParameter("javaEncoding");
+        if (javaEncoding != null) {
+            this.javaEncoding = javaEncoding;
+        }
+        
+        String compilerClassName = config.getInitParameter("compilerClassName");
+        if (compilerClassName != null) {
+            this.compilerClassName = compilerClassName;
+        }
+        
+        String fork = config.getInitParameter("fork");
+        if (fork != null) {
+            if (fork.equalsIgnoreCase("true")) {
+                this.fork = true;
+            } else if (fork.equalsIgnoreCase("false")) {
+                this.fork = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.fork"));
+                }
+            }
+        }
+        
+        String xpoweredBy = config.getInitParameter("xpoweredBy"); 
+        if (xpoweredBy != null) {
+            if (xpoweredBy.equalsIgnoreCase("true")) {
+                this.xpoweredBy = true;
+            } else if (xpoweredBy.equalsIgnoreCase("false")) {
+                this.xpoweredBy = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.xpoweredBy"));
+                }
+            }
+        }
+        
+        String displaySourceFragment = config.getInitParameter("displaySourceFragment"); 
+        if (displaySourceFragment != null) {
+            if (displaySourceFragment.equalsIgnoreCase("true")) {
+                this.displaySourceFragment = true;
+            } else if (displaySourceFragment.equalsIgnoreCase("false")) {
+                this.displaySourceFragment = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.displaySourceFragment"));
+                }
+            }
+        }
+        
+        // Setup the global Tag Libraries location cache for this
+        // web-application.
+        tldLocationsCache = new TldLocationsCache(context);
+        
+        // Setup the jsp config info for this web app.
+        jspConfig = new JspConfig(context);
+        
+        // Create a Tag plugin instance
+        tagPluginManager = new TagPluginManager(context);
+    }
+    
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JasperException.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JasperException.java
new file mode 100644
index 0000000..cc06a2b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JasperException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jasper;
+
+/**
+ * Base class for all exceptions generated by the JSP engine. Makes it
+ * convienient to catch just this at the top-level. 
+ *
+ * @author Anil K. Vijendran
+ */
+public class JasperException extends javax.servlet.ServletException {
+    
+    public JasperException(String reason) {
+	super(reason);
+    }
+
+    /**
+     * Creates a JasperException with the embedded exception and the reason for
+     * throwing a JasperException
+     */
+    public JasperException (String reason, Throwable exception) {
+   	super(reason, exception);
+    }
+
+    /**
+     * Creates a JasperException with the embedded exception
+     */
+    public JasperException (Throwable exception) {
+   	super(exception);
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspC.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspC.java
new file mode 100644
index 0000000..e7e9dfd
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspC.java
@@ -0,0 +1,1424 @@
+/*
+ * 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.jasper;
+
+import java.io.BufferedReader;
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.jasper.compiler.Compiler;
+import org.apache.jasper.compiler.JspConfig;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.compiler.TagPluginManager;
+import org.apache.jasper.compiler.TldLocationsCache;
+import org.apache.jasper.servlet.JspCServletContext;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.FileUtils;
+
+/**
+ * Shell for the jspc compiler.  Handles all options associated with the
+ * command line and creates compilation contexts which it then compiles
+ * according to the specified options.
+ *
+ * This version can process files from a _single_ webapp at once, i.e.
+ * a single docbase can be specified.
+ *
+ * It can be used as an Ant task using:
+ * <pre>
+ *   &lt;taskdef classname="org.apache.jasper.JspC" name="jasper2" &gt;
+ *      &lt;classpath&gt;
+ *          &lt;pathelement location="${java.home}/../lib/tools.jar"/&gt;
+ *          &lt;fileset dir="${ENV.CATALINA_HOME}/server/lib"&gt;
+ *              &lt;include name="*.jar"/&gt;
+ *          &lt;/fileset&gt;
+ *          &lt;fileset dir="${ENV.CATALINA_HOME}/common/lib"&gt;
+ *              &lt;include name="*.jar"/&gt;
+ *          &lt;/fileset&gt;
+ *          &lt;path refid="myjars"/&gt;
+ *       &lt;/classpath&gt;
+ *  &lt;/taskdef&gt;
+ *
+ *  &lt;jasper2 verbose="0"
+ *           package="my.package"
+ *           uriroot="${webapps.dir}/${webapp.name}"
+ *           webXmlFragment="${build.dir}/generated_web.xml"
+ *           outputDir="${webapp.dir}/${webapp.name}/WEB-INF/src/my/package" /&gt;
+ * </pre>
+ *
+ * @author Danno Ferrin
+ * @author Pierre Delisle
+ * @author Costin Manolache
+ * @author Yoav Shapira
+ */
+public class JspC implements Options {
+
+    public static final String DEFAULT_IE_CLASS_ID =
+            "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93";
+
+    // Logger
+    protected static Log log = LogFactory.getLog(JspC.class);
+
+    protected static final String SWITCH_VERBOSE = "-v";
+    protected static final String SWITCH_HELP = "-help";
+    protected static final String SWITCH_OUTPUT_DIR = "-d";
+    protected static final String SWITCH_PACKAGE_NAME = "-p";
+    protected static final String SWITCH_CACHE = "-cache";
+    protected static final String SWITCH_CLASS_NAME = "-c";
+    protected static final String SWITCH_FULL_STOP = "--";
+    protected static final String SWITCH_COMPILE = "-compile";
+    protected static final String SWITCH_SOURCE = "-source";
+    protected static final String SWITCH_TARGET = "-target";
+    protected static final String SWITCH_URI_BASE = "-uribase";
+    protected static final String SWITCH_URI_ROOT = "-uriroot";
+    protected static final String SWITCH_FILE_WEBAPP = "-webapp";
+    protected static final String SWITCH_WEBAPP_INC = "-webinc";
+    protected static final String SWITCH_WEBAPP_XML = "-webxml";
+    protected static final String SWITCH_MAPPED = "-mapped";
+    protected static final String SWITCH_XPOWERED_BY = "-xpoweredBy";
+    protected static final String SWITCH_TRIM_SPACES = "-trimSpaces";
+    protected static final String SWITCH_CLASSPATH = "-classpath";
+    protected static final String SWITCH_DIE = "-die";
+    protected static final String SWITCH_POOLING = "-poolingEnabled";
+    protected static final String SWITCH_ENCODING = "-javaEncoding";
+    protected static final String SWITCH_SMAP = "-smap";
+    protected static final String SWITCH_DUMP_SMAP = "-dumpsmap";
+
+    protected static final String SHOW_SUCCESS ="-s";
+    protected static final String LIST_ERRORS = "-l";
+    protected static final int INC_WEBXML = 10;
+    protected static final int ALL_WEBXML = 20;
+    protected static final int DEFAULT_DIE_LEVEL = 1;
+    protected static final int NO_DIE_LEVEL = 0;
+
+    protected static final String[] insertBefore =
+    { "</web-app>", "<servlet-mapping>", "<session-config>",
+      "<mime-mapping>", "<welcome-file-list>", "<error-page>", "<taglib>",
+      "<resource-env-ref>", "<resource-ref>", "<security-constraint>",
+      "<login-config>", "<security-role>", "<env-entry>", "<ejb-ref>",
+      "<ejb-local-ref>" };
+
+    protected static int die;
+    protected String classPath = null;
+    protected URLClassLoader loader = null;
+    protected boolean trimSpaces = false;
+    protected boolean genStringAsCharArray = false;
+    protected boolean xpoweredBy;
+    protected boolean mappedFile = false;
+    protected boolean poolingEnabled = true;
+    protected File scratchDir;
+    protected String ieClassId = DEFAULT_IE_CLASS_ID;
+    protected String targetPackage;
+    protected String targetClassName;
+    protected String uriBase;
+    protected String uriRoot;
+    protected Project project;
+    protected int dieLevel;
+    protected boolean helpNeeded = false;
+    protected boolean compile = false;
+    protected boolean smapSuppressed = true;
+    protected boolean smapDumped = false;
+    protected boolean caching = true;
+    protected Map cache = new HashMap();
+
+    protected String compiler = null;
+
+    protected String compilerTargetVM = "1.4";
+    protected String compilerSourceVM = "1.4";
+
+    protected boolean classDebugInfo = true;
+
+    /**
+     * Throw an exception if there's a compilation error, or swallow it.
+     * Default is true to preserve old behavior.
+     */
+    protected boolean failOnError = true;
+
+    /**
+     * The file extensions to be handled as JSP files.
+     * Default list is .jsp and .jspx.
+     */
+    protected List extensions;
+
+    /**
+     * The pages.
+     */
+    protected List pages = new Vector();
+
+    /**
+     * Needs better documentation, this data member does.
+     * True by default.
+     */
+    protected boolean errorOnUseBeanInvalidClassAttribute = true;
+
+    /**
+     * The java file encoding.  Default
+     * is UTF-8.  Added per bugzilla 19622.
+     */
+    protected String javaEncoding = "UTF-8";
+
+    // Generation of web.xml fragments
+    protected String webxmlFile;
+    protected int webxmlLevel;
+    protected boolean addWebXmlMappings = false;
+
+    protected Writer mapout;
+    protected CharArrayWriter servletout;
+    protected CharArrayWriter mappingout;
+
+    /**
+     * The servlet context.
+     */
+    protected JspCServletContext context;
+
+    /**
+     * The runtime context.
+     * Maintain a dummy JspRuntimeContext for compiling tag files.
+     */
+    protected JspRuntimeContext rctxt;
+
+    /**
+     * Cache for the TLD locations
+     */
+    protected TldLocationsCache tldLocationsCache = null;
+
+    protected JspConfig jspConfig = null;
+    protected TagPluginManager tagPluginManager = null;
+
+    protected boolean verbose = false;
+    protected boolean listErrors = false;
+    protected boolean showSuccess = false;
+    protected int argPos;
+    protected boolean fullstop = false;
+    protected String args[];
+
+    public static void main(String arg[]) {
+        if (arg.length == 0) {
+            System.out.println(Localizer.getMessage("jspc.usage"));
+        } else {
+            try {
+                JspC jspc = new JspC();
+                jspc.setArgs(arg);
+                if (jspc.helpNeeded) {
+                    System.out.println(Localizer.getMessage("jspc.usage"));
+                } else {
+                    jspc.execute();
+                }
+            } catch (JasperException je) {
+                System.err.println(je);
+                if (die != NO_DIE_LEVEL) {
+                    System.exit(die);
+                }
+            }
+        }
+    }
+
+    public void setArgs(String[] arg) throws JasperException {
+        args = arg;
+        String tok;
+
+        dieLevel = NO_DIE_LEVEL;
+        die = dieLevel;
+
+        while ((tok = nextArg()) != null) {
+            if (tok.equals(SWITCH_VERBOSE)) {
+                verbose = true;
+                showSuccess = true;
+                listErrors = true;
+            } else if (tok.equals(SWITCH_OUTPUT_DIR)) {
+                tok = nextArg();
+                setOutputDir( tok );
+            } else if (tok.equals(SWITCH_PACKAGE_NAME)) {
+                targetPackage = nextArg();
+            } else if (tok.equals(SWITCH_COMPILE)) {
+                compile=true;
+            } else if (tok.equals(SWITCH_CLASS_NAME)) {
+                targetClassName = nextArg();
+            } else if (tok.equals(SWITCH_URI_BASE)) {
+                uriBase=nextArg();
+            } else if (tok.equals(SWITCH_URI_ROOT)) {
+                setUriroot( nextArg());
+            } else if (tok.equals(SWITCH_FILE_WEBAPP)) {
+                setUriroot( nextArg());
+            } else if ( tok.equals( SHOW_SUCCESS ) ) {
+                showSuccess = true;
+            } else if ( tok.equals( LIST_ERRORS ) ) {
+                listErrors = true;
+            } else if (tok.equals(SWITCH_WEBAPP_INC)) {
+                webxmlFile = nextArg();
+                if (webxmlFile != null) {
+                    webxmlLevel = INC_WEBXML;
+                }
+            } else if (tok.equals(SWITCH_WEBAPP_XML)) {
+                webxmlFile = nextArg();
+                if (webxmlFile != null) {
+                    webxmlLevel = ALL_WEBXML;
+                }
+            } else if (tok.equals(SWITCH_MAPPED)) {
+                mappedFile = true;
+            } else if (tok.equals(SWITCH_XPOWERED_BY)) {
+                xpoweredBy = true;
+            } else if (tok.equals(SWITCH_TRIM_SPACES)) {
+                setTrimSpaces(true);
+            } else if (tok.equals(SWITCH_CACHE)) {
+                tok = nextArg();
+                if ("false".equals(tok)) {
+                    caching = false;
+                } else {
+                    caching = true;
+                }            
+            } else if (tok.equals(SWITCH_CLASSPATH)) {
+                setClassPath(nextArg());
+            } else if (tok.startsWith(SWITCH_DIE)) {
+                try {
+                    dieLevel = Integer.parseInt(
+                        tok.substring(SWITCH_DIE.length()));
+                } catch (NumberFormatException nfe) {
+                    dieLevel = DEFAULT_DIE_LEVEL;
+                }
+                die = dieLevel;
+            } else if (tok.equals(SWITCH_HELP)) {
+                helpNeeded = true;
+            } else if (tok.equals(SWITCH_POOLING)) {
+                tok = nextArg();
+                if ("false".equals(tok)) {
+                    poolingEnabled = false;
+                } else {
+                    poolingEnabled = true;
+                }
+            } else if (tok.equals(SWITCH_ENCODING)) {
+                setJavaEncoding(nextArg());
+            } else if (tok.equals(SWITCH_SOURCE)) {
+                setCompilerSourceVM(nextArg());
+            } else if (tok.equals(SWITCH_TARGET)) {
+                setCompilerTargetVM(nextArg());
+            } else if (tok.equals(SWITCH_SMAP)) {
+                smapSuppressed = false;
+            } else if (tok.equals(SWITCH_DUMP_SMAP)) {
+                smapDumped = true;
+            } else {
+                if (tok.startsWith("-")) {
+                    throw new JasperException("Unrecognized option: " + tok +
+                        ".  Use -help for help.");
+                }
+                if (!fullstop) {
+                    argPos--;
+                }
+                // Start treating the rest as JSP Pages
+                break;
+            }
+        }
+
+        // Add all extra arguments to the list of files
+        while( true ) {
+            String file = nextFile();
+            if( file==null ) {
+                break;
+            }
+            pages.add( file );
+        }
+    }
+
+    public boolean getKeepGenerated() {
+        // isn't this why we are running jspc?
+        return true;
+    }
+
+    public boolean getTrimSpaces() {
+        return trimSpaces;
+    }
+
+    public void setTrimSpaces(boolean ts) {
+        this.trimSpaces = ts;
+    }
+
+    public boolean isPoolingEnabled() {
+        return poolingEnabled;
+    }
+
+    public void setPoolingEnabled(boolean poolingEnabled) {
+        this.poolingEnabled = poolingEnabled;
+    }
+
+    public boolean isXpoweredBy() {
+        return xpoweredBy;
+    }
+
+    public void setXpoweredBy(boolean xpoweredBy) {
+        this.xpoweredBy = xpoweredBy;
+    }
+
+    public boolean getDisplaySourceFragment() {
+        return true;
+    }
+    
+    public boolean getErrorOnUseBeanInvalidClassAttribute() {
+        return errorOnUseBeanInvalidClassAttribute;
+    }
+
+    public void setErrorOnUseBeanInvalidClassAttribute(boolean b) {
+        errorOnUseBeanInvalidClassAttribute = b;
+    }
+
+    public int getTagPoolSize() {
+        return Constants.MAX_POOL_SIZE;
+    }
+
+    /**
+     * Are we supporting HTML mapped servlets?
+     */
+    public boolean getMappedFile() {
+        return mappedFile;
+    }
+
+    // Off-line compiler, no need for security manager
+    public Object getProtectionDomain() {
+        return null;
+    }
+
+    /**
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getSendErrorToClient() {
+        return true;
+    }
+
+    public void setClassDebugInfo( boolean b ) {
+        classDebugInfo=b;
+    }
+
+    public boolean getClassDebugInfo() {
+        // compile with debug info
+        return classDebugInfo;
+    }
+
+     /**
+      * @see Options#isCaching()
+     */
+    public boolean isCaching() {
+        return caching;
+    }
+
+    /**
+     * @see Options#isCaching()
+     */
+    public void setCaching(boolean caching) {
+        this.caching = caching;
+    }
+
+    /**
+     * @see Options#getCache()
+     */
+    public Map getCache() {
+        return cache;
+    }
+
+    /**
+     * Background compilation check intervals in seconds
+     */
+    public int getCheckInterval() {
+        return 0;
+    }
+
+    /**
+     * Modification test interval.
+     */
+    public int getModificationTestInterval() {
+        return 0;
+    }
+
+    /**
+     * Is Jasper being used in development mode?
+     */
+    public boolean getDevelopment() {
+        return false;
+    }
+
+    /**
+     * Is the generation of SMAP info for JSR45 debuggin suppressed?
+     */
+    public boolean isSmapSuppressed() {
+        return smapSuppressed;
+    }
+
+    /**
+     * Set smapSuppressed flag.
+     */
+    public void setSmapSuppressed(boolean smapSuppressed) {
+        this.smapSuppressed = smapSuppressed;
+    }
+
+    
+    /**
+     * Should SMAP info for JSR45 debugging be dumped to a file?
+     */
+    public boolean isSmapDumped() {
+        return smapDumped;
+    }
+
+    /**
+     * Set smapSuppressed flag.
+     */
+    public void setSmapDumped(boolean smapDumped) {
+        this.smapDumped = smapDumped;
+    }
+
+    
+    /**
+     * Determines whether text strings are to be generated as char arrays,
+     * which improves performance in some cases.
+     *
+     * @param genStringAsCharArray true if text strings are to be generated as
+     * char arrays, false otherwise
+     */
+    public void setGenStringAsCharArray(boolean genStringAsCharArray) {
+        this.genStringAsCharArray = genStringAsCharArray;
+    }
+
+    /**
+     * Indicates whether text strings are to be generated as char arrays.
+     *
+     * @return true if text strings are to be generated as char arrays, false
+     * otherwise
+     */
+    public boolean genStringAsCharArray() {
+        return genStringAsCharArray;
+    }
+
+    /**
+     * Sets the class-id value to be sent to Internet Explorer when using
+     * <jsp:plugin> tags.
+     *
+     * @param ieClassId Class-id value
+     */
+    public void setIeClassId(String ieClassId) {
+        this.ieClassId = ieClassId;
+    }
+
+    /**
+     * Gets the class-id value that is sent to Internet Explorer when using
+     * <jsp:plugin> tags.
+     *
+     * @return Class-id value
+     */
+    public String getIeClassId() {
+        return ieClassId;
+    }
+
+    public File getScratchDir() {
+        return scratchDir;
+    }
+
+    public Class getJspCompilerPlugin() {
+       // we don't compile, so this is meanlingless
+        return null;
+    }
+
+    public String getJspCompilerPath() {
+       // we don't compile, so this is meanlingless
+        return null;
+    }
+
+    /**
+     * Compiler to use.
+     */
+    public String getCompiler() {
+        return compiler;
+    }
+
+    public void setCompiler(String c) {
+        compiler=c;
+    }
+
+    /**
+     * Compiler class name to use.
+     */
+    public String getCompilerClassName() {
+        return null;
+    }
+    
+    /**
+     * @see Options#getCompilerTargetVM
+     */
+    public String getCompilerTargetVM() {
+        return compilerTargetVM;
+    }
+
+    public void setCompilerTargetVM(String vm) {
+        compilerTargetVM = vm;
+    }
+
+    /**
+     * @see Options#getCompilerSourceVM()
+     */
+     public String getCompilerSourceVM() {
+         return compilerSourceVM;
+     }
+        
+    /**
+     * @see Options#getCompilerSourceVM()
+     */
+    public void setCompilerSourceVM(String vm) {
+        compilerSourceVM = vm;
+    }
+
+    public TldLocationsCache getTldLocationsCache() {
+        return tldLocationsCache;
+    }
+
+    /**
+     * Returns the encoding to use for
+     * java files.  The default is UTF-8.
+     *
+     * @return String The encoding
+     */
+    public String getJavaEncoding() {
+        return javaEncoding;
+    }
+
+    /**
+     * Sets the encoding to use for
+     * java files.
+     *
+     * @param encodingName The name, e.g. "UTF-8"
+     */
+    public void setJavaEncoding(String encodingName) {
+        javaEncoding = encodingName;
+    }
+
+    public boolean getFork() {
+        return false;
+    }
+
+    public String getClassPath() {
+        if( classPath != null )
+            return classPath;
+        return System.getProperty("java.class.path");
+    }
+
+    public void setClassPath(String s) {
+        classPath=s;
+    }
+
+    /**
+     * Returns the list of file extensions
+     * that are treated as JSP files.
+     *
+     * @return The list of extensions
+     */
+    public List getExtensions() {
+        return extensions;
+    }
+
+    /**
+     * Adds the given file extension to the
+     * list of extensions handled as JSP files.
+     *
+     * @param extension The extension to add, e.g. "myjsp"
+     */
+    protected void addExtension(final String extension) {
+        if(extension != null) {
+            if(extensions == null) {
+                extensions = new Vector();
+            }
+
+            extensions.add(extension);
+        }
+    }
+
+    /**
+     * Sets the project.
+     *
+     * @param theProject The project
+     */
+    public void setProject(final Project theProject) {
+        project = theProject;
+    }
+
+    /**
+     * Returns the project: may be null if not running
+     * inside an Ant project.
+     *
+     * @return The project
+     */
+    public Project getProject() {
+        return project;
+    }
+
+    /**
+     * Base dir for the webapp. Used to generate class names and resolve
+     * includes
+     */
+    public void setUriroot( String s ) {
+        if( s==null ) {
+            uriRoot = s;
+            return;
+        }
+        try {
+            uriRoot = resolveFile(s).getCanonicalPath();
+        } catch( Exception ex ) {
+            uriRoot = s;
+        }
+    }
+
+    /**
+     * Parses comma-separated list of JSP files to be processed.  If the argument
+     * is null, nothing is done.
+     *
+     * <p>Each file is interpreted relative to uriroot, unless it is absolute,
+     * in which case it must start with uriroot.</p>
+     *
+     * @param jspFiles Comma-separated list of JSP files to be processed
+     */
+    public void setJspFiles(final String jspFiles) {
+        if(jspFiles == null) {
+            return;
+        }
+
+        StringTokenizer tok = new StringTokenizer(jspFiles, ",");
+        while (tok.hasMoreTokens()) {
+            pages.add(tok.nextToken());
+        }
+    }
+
+    /**
+     * Sets the compile flag.
+     *
+     * @param b Flag value
+     */
+    public void setCompile( final boolean b ) {
+        compile = b;
+    }
+
+    /**
+     * Sets the verbosity level.  The actual number doesn't
+     * matter: if it's greater than zero, the verbose flag will
+     * be true.
+     *
+     * @param level Positive means verbose
+     */
+    public void setVerbose( final int level ) {
+        if (level > 0) {
+            verbose = true;
+            showSuccess = true;
+            listErrors = true;
+        }
+    }
+
+    public void setValidateXml( boolean b ) {
+        org.apache.jasper.xmlparser.ParserUtils.validating=b;
+    }
+
+    public void setListErrors( boolean b ) {
+        listErrors = b;
+    }
+
+    public void setOutputDir( String s ) {
+        if( s!= null ) {
+            scratchDir = resolveFile(s).getAbsoluteFile();
+        } else {
+            scratchDir=null;
+        }
+    }
+
+    public void setPackage( String p ) {
+        targetPackage=p;
+    }
+
+    /**
+     * Class name of the generated file ( without package ).
+     * Can only be used if a single file is converted.
+     * XXX Do we need this feature ?
+     */
+    public void setClassName( String p ) {
+        targetClassName=p;
+    }
+
+    /**
+     * File where we generate a web.xml fragment with the class definitions.
+     */
+    public void setWebXmlFragment( String s ) {
+        webxmlFile=resolveFile(s).getAbsolutePath();
+        webxmlLevel=INC_WEBXML;
+    }
+
+    /**
+     * File where we generate a complete web.xml with the class definitions.
+     */
+    public void setWebXml( String s ) {
+        webxmlFile=resolveFile(s).getAbsolutePath();
+        webxmlLevel=ALL_WEBXML;
+    }
+
+    public void setAddWebXmlMappings(boolean b) {
+        addWebXmlMappings = b;
+    }
+
+    /**
+     * Set the option that throws an exception in case of a compilation error.
+     */
+    public void setFailOnError(final boolean b) {
+        failOnError = b;
+    }
+
+    public boolean getFailOnError() {
+        return failOnError;
+    }
+
+    /**
+     * Obtain JSP configuration informantion specified in web.xml.
+     */
+    public JspConfig getJspConfig() {
+        return jspConfig;
+    }
+
+    public TagPluginManager getTagPluginManager() {
+        return tagPluginManager;
+    }
+
+    public void generateWebMapping( String file, JspCompilationContext clctxt )
+        throws IOException
+    {
+        if (log.isDebugEnabled()) {
+            log.debug("Generating web mapping for file " + file
+                      + " using compilation context " + clctxt);
+        }
+
+        String className = clctxt.getServletClassName();
+        String packageName = clctxt.getServletPackageName();
+
+        String thisServletName;
+        if  ("".equals(packageName)) {
+            thisServletName = className;
+        } else {
+            thisServletName = packageName + '.' + className;
+        }
+
+        if (servletout != null) {
+            servletout.write("\n    <servlet>\n        <servlet-name>");
+            servletout.write(thisServletName);
+            servletout.write("</servlet-name>\n        <servlet-class>");
+            servletout.write(thisServletName);
+            servletout.write("</servlet-class>\n    </servlet>\n");
+        }
+        if (mappingout != null) {
+            mappingout.write("\n    <servlet-mapping>\n        <servlet-name>");
+            mappingout.write(thisServletName);
+            mappingout.write("</servlet-name>\n        <url-pattern>");
+            mappingout.write(file.replace('\\', '/'));
+            mappingout.write("</url-pattern>\n    </servlet-mapping>\n");
+
+        }
+    }
+
+    /**
+     * Include the generated web.xml inside the webapp's web.xml.
+     */
+    protected void mergeIntoWebXml() throws IOException {
+
+        File webappBase = new File(uriRoot);
+        File webXml = new File(webappBase, "WEB-INF/web.xml");
+        File webXml2 = new File(webappBase, "WEB-INF/web2.xml");
+        String insertStartMarker =
+            Localizer.getMessage("jspc.webinc.insertStart");
+        String insertEndMarker =
+            Localizer.getMessage("jspc.webinc.insertEnd");
+
+        BufferedReader reader = new BufferedReader(new FileReader(webXml));
+        BufferedReader fragmentReader =
+            new BufferedReader(new FileReader(webxmlFile));
+        PrintWriter writer = new PrintWriter(new FileWriter(webXml2));
+
+        // Insert the <servlet> and <servlet-mapping> declarations
+        int pos = -1;
+        String line = null;
+        while (true) {
+            line = reader.readLine();
+            if (line == null) {
+                break;
+            }
+            // Skip anything previously generated by JSPC
+            if (line.indexOf(insertStartMarker) >= 0) {
+                while (true) {
+                    line = reader.readLine();
+                    if (line == null) {
+                        return;
+                    }
+                    if (line.indexOf(insertEndMarker) >= 0) {
+                        line = reader.readLine();
+                        line = reader.readLine();
+                        if (line == null) {
+                            return;
+                        }
+                        break;
+                    }
+                }
+            }
+            for (int i = 0; i < insertBefore.length; i++) {
+                pos = line.indexOf(insertBefore[i]);
+                if (pos >= 0)
+                    break;
+            }
+            if (pos >= 0) {
+                writer.print(line.substring(0, pos));
+                break;
+            } else {
+                writer.println(line);
+            }
+        }
+
+        writer.println(insertStartMarker);
+        while (true) {
+            String line2 = fragmentReader.readLine();
+            if (line2 == null) {
+                writer.println();
+                break;
+            }
+            writer.println(line2);
+        }
+        writer.println(insertEndMarker);
+        writer.println();
+
+        for (int i = 0; i < pos; i++) {
+            writer.print(" ");
+        }
+        writer.println(line.substring(pos));
+
+        while (true) {
+            line = reader.readLine();
+            if (line == null) {
+                break;
+            }
+            writer.println(line);
+        }
+        writer.close();
+
+        reader.close();
+        fragmentReader.close();
+
+        FileInputStream fis = new FileInputStream(webXml2);
+        FileOutputStream fos = new FileOutputStream(webXml);
+
+        byte buf[] = new byte[512];
+        while (true) {
+            int n = fis.read(buf);
+            if (n < 0) {
+                break;
+            }
+            fos.write(buf, 0, n);
+        }
+
+        fis.close();
+        fos.close();
+
+        webXml2.delete();
+        (new File(webxmlFile)).delete();
+
+    }
+
+    protected void processFile(String file)
+        throws JasperException
+    {
+        if (log.isDebugEnabled()) {
+            log.debug("Processing file: " + file);
+        }
+
+        ClassLoader originalClassLoader = null;
+
+        try {
+            // set up a scratch/output dir if none is provided
+            if (scratchDir == null) {
+                String temp = System.getProperty("java.io.tmpdir");
+                if (temp == null) {
+                    temp = "";
+                }
+                scratchDir = new File(new File(temp).getAbsolutePath());
+            }
+
+            String jspUri=file.replace('\\','/');
+            JspCompilationContext clctxt = new JspCompilationContext
+                ( jspUri, false,  this, context, null, rctxt );
+
+            /* Override the defaults */
+            if ((targetClassName != null) && (targetClassName.length() > 0)) {
+                clctxt.setServletClassName(targetClassName);
+                targetClassName = null;
+            }
+            if (targetPackage != null) {
+                clctxt.setServletPackageName(targetPackage);
+            }
+
+            originalClassLoader = Thread.currentThread().getContextClassLoader();
+            if( loader==null ) {
+                initClassLoader( clctxt );
+            }
+            Thread.currentThread().setContextClassLoader(loader);
+
+            clctxt.setClassLoader(loader);
+            clctxt.setClassPath(classPath);
+
+            Compiler clc = clctxt.createCompiler();
+
+            // If compile is set, generate both .java and .class, if
+            // .jsp file is newer than .class file;
+            // Otherwise only generate .java, if .jsp file is newer than
+            // the .java file
+            if( clc.isOutDated(compile) ) {
+                if (log.isDebugEnabled()) {
+                    log.debug(jspUri + " is out dated, compiling...");
+                }
+
+                clc.compile(compile, true);
+            }
+
+            // Generate mapping
+            generateWebMapping( file, clctxt );
+            if ( showSuccess ) {
+                log.info( "Built File: " + file );
+            }
+
+        } catch (JasperException je) {
+            Throwable rootCause = je;
+            while (rootCause instanceof JasperException
+                    && ((JasperException) rootCause).getRootCause() != null) {
+                rootCause = ((JasperException) rootCause).getRootCause();
+            }
+            if (rootCause != je) {
+                log.error(Localizer.getMessage("jspc.error.generalException",
+                                               file),
+                          rootCause);
+            }
+
+            // Bugzilla 35114.
+            if(getFailOnError()) {
+                throw je;
+            } else {
+                log.error(je.getMessage());
+            }
+
+        } catch (Exception e) {
+            if ((e instanceof FileNotFoundException) && log.isWarnEnabled()) {
+                log.warn(Localizer.getMessage("jspc.error.fileDoesNotExist",
+                                              e.getMessage()));
+            }
+            throw new JasperException(e);
+        } finally {
+            if(originalClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(originalClassLoader);
+            }
+        }
+    }
+
+    /**
+     * Locate all jsp files in the webapp. Used if no explicit
+     * jsps are specified.
+     */
+    public void scanFiles( File base ) throws JasperException {
+        Stack<String> dirs = new Stack<String>();
+        dirs.push(base.toString());
+
+        // Make sure default extensions are always included
+        if ((getExtensions() == null) || (getExtensions().size() < 2)) {
+            addExtension("jsp");
+            addExtension("jspx");
+        }
+
+        while (!dirs.isEmpty()) {
+            String s = dirs.pop();
+            File f = new File(s);
+            if (f.exists() && f.isDirectory()) {
+                String[] files = f.list();
+                String ext;
+                for (int i = 0; (files != null) && i < files.length; i++) {
+                    File f2 = new File(s, files[i]);
+                    if (f2.isDirectory()) {
+                        dirs.push(f2.getPath());
+                    } else {
+                        String path = f2.getPath();
+                        String uri = path.substring(uriRoot.length());
+                        ext = files[i].substring(files[i].lastIndexOf('.') +1);
+                        if (getExtensions().contains(ext) ||
+                            jspConfig.isJspPage(uri)) {
+                            pages.add(path);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Executes the compilation.
+     *
+     * @throws JasperException If an error occurs
+     */
+    public void execute() throws JasperException {
+        if(log.isDebugEnabled()) {
+            log.debug("execute() starting for " + pages.size() + " pages.");
+        }
+
+        try {
+            if (uriRoot == null) {
+                if( pages.size() == 0 ) {
+                    throw new JasperException(
+                        Localizer.getMessage("jsp.error.jspc.missingTarget"));
+                }
+                String firstJsp = (String) pages.get( 0 );
+                File firstJspF = new File( firstJsp );
+                if (!firstJspF.exists()) {
+                    throw new JasperException(
+                        Localizer.getMessage("jspc.error.fileDoesNotExist",
+                                             firstJsp));
+                }
+                locateUriRoot( firstJspF );
+            }
+
+            if (uriRoot == null) {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.jspc.no_uriroot"));
+            }
+
+            if( context==null ) {
+                initServletContext();
+            }
+
+            // No explicit pages, we'll process all .jsp in the webapp
+            if (pages.size() == 0) {
+                scanFiles( new File( uriRoot ));
+            }
+
+            File uriRootF = new File(uriRoot);
+            if (!uriRootF.exists() || !uriRootF.isDirectory()) {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.jspc.uriroot_not_dir"));
+            }
+
+            initWebXml();
+
+            Iterator iter = pages.iterator();
+            while (iter.hasNext()) {
+                String nextjsp = iter.next().toString();
+                File fjsp = new File(nextjsp);
+                if (!fjsp.isAbsolute()) {
+                    fjsp = new File(uriRootF, nextjsp);
+                }
+                if (!fjsp.exists()) {
+                    if (log.isWarnEnabled()) {
+                        log.warn
+                            (Localizer.getMessage
+                             ("jspc.error.fileDoesNotExist", fjsp.toString()));
+                    }
+                    continue;
+                }
+                String s = fjsp.getAbsolutePath();
+                if (s.startsWith(uriRoot)) {
+                    nextjsp = s.substring(uriRoot.length());
+                }
+                if (nextjsp.startsWith("." + File.separatorChar)) {
+                    nextjsp = nextjsp.substring(2);
+                }
+                processFile(nextjsp);
+            }
+
+            completeWebXml();
+
+            if (addWebXmlMappings) {
+                mergeIntoWebXml();
+            }
+
+        } catch (IOException ioe) {
+            throw new JasperException(ioe);
+
+        } catch (JasperException je) {
+            Throwable rootCause = je;
+            while (rootCause instanceof JasperException
+                    && ((JasperException) rootCause).getRootCause() != null) {
+                rootCause = ((JasperException) rootCause).getRootCause();
+            }
+            if (rootCause != je) {
+                rootCause.printStackTrace();
+            }
+            throw je;
+        } finally {
+            if (loader != null) {
+                LogFactory.release(loader);
+            }
+        }
+    }
+
+    // ==================== protected utility methods ====================
+
+    protected String nextArg() {
+        if ((argPos >= args.length)
+            || (fullstop = SWITCH_FULL_STOP.equals(args[argPos]))) {
+            return null;
+        } else {
+            return args[argPos++];
+        }
+    }
+
+    protected String nextFile() {
+        if (fullstop) argPos++;
+        if (argPos >= args.length) {
+            return null;
+        } else {
+            return args[argPos++];
+        }
+    }
+
+    protected void initWebXml() {
+        try {
+            if (webxmlLevel >= INC_WEBXML) {
+                File fmapings = new File(webxmlFile);
+                mapout = new FileWriter(fmapings);
+                servletout = new CharArrayWriter();
+                mappingout = new CharArrayWriter();
+            } else {
+                mapout = null;
+                servletout = null;
+                mappingout = null;
+            }
+            if (webxmlLevel >= ALL_WEBXML) {
+                mapout.write(Localizer.getMessage("jspc.webxml.header"));
+                mapout.flush();
+            } else if ((webxmlLevel>= INC_WEBXML) && !addWebXmlMappings) {
+                mapout.write(Localizer.getMessage("jspc.webinc.header"));
+                mapout.flush();
+            }
+        } catch (IOException ioe) {
+            mapout = null;
+            servletout = null;
+            mappingout = null;
+        }
+    }
+
+    protected void completeWebXml() {
+        if (mapout != null) {
+            try {
+                servletout.writeTo(mapout);
+                mappingout.writeTo(mapout);
+                if (webxmlLevel >= ALL_WEBXML) {
+                    mapout.write(Localizer.getMessage("jspc.webxml.footer"));
+                } else if ((webxmlLevel >= INC_WEBXML) && !addWebXmlMappings) {
+                    mapout.write(Localizer.getMessage("jspc.webinc.footer"));
+                }
+                mapout.close();
+            } catch (IOException ioe) {
+                // noting to do if it fails since we are done with it
+            }
+        }
+    }
+
+    protected void initServletContext() {
+        try {
+            context =new JspCServletContext
+                (new PrintWriter(System.out),
+                 new URL("file:" + uriRoot.replace('\\','/') + '/'));
+            tldLocationsCache = new TldLocationsCache(context, true);
+        } catch (MalformedURLException me) {
+            System.out.println("**" + me);
+        }
+        rctxt = new JspRuntimeContext(context, this);
+        jspConfig = new JspConfig(context);
+        tagPluginManager = new TagPluginManager(context);
+    }
+
+    /**
+     * Initializes the classloader as/if needed for the given
+     * compilation context.
+     *
+     * @param clctxt The compilation context
+     * @throws IOException If an error occurs
+     */
+    protected void initClassLoader(JspCompilationContext clctxt)
+        throws IOException {
+
+        classPath = getClassPath();
+
+        ClassLoader jspcLoader = getClass().getClassLoader();
+        if (jspcLoader instanceof AntClassLoader) {
+            classPath += File.pathSeparator
+                + ((AntClassLoader) jspcLoader).getClasspath();
+        }
+
+        // Turn the classPath into URLs
+        ArrayList<URL> urls = new ArrayList<URL>();
+        StringTokenizer tokenizer = new StringTokenizer(classPath,
+                                                        File.pathSeparator);
+        while (tokenizer.hasMoreTokens()) {
+            String path = tokenizer.nextToken();
+            try {
+                File libFile = new File(path);
+                urls.add(libFile.toURL());
+            } catch (IOException ioe) {
+                // Failing a toCanonicalPath on a file that
+                // exists() should be a JVM regression test,
+                // therefore we have permission to freak uot
+                throw new RuntimeException(ioe.toString());
+            }
+        }
+
+        File webappBase = new File(uriRoot);
+        if (webappBase.exists()) {
+            File classes = new File(webappBase, "/WEB-INF/classes");
+            try {
+                if (classes.exists()) {
+                    classPath = classPath + File.pathSeparator
+                        + classes.getCanonicalPath();
+                    urls.add(classes.getCanonicalFile().toURL());
+                }
+            } catch (IOException ioe) {
+                // failing a toCanonicalPath on a file that
+                // exists() should be a JVM regression test,
+                // therefore we have permission to freak out
+                throw new RuntimeException(ioe.toString());
+            }
+            File lib = new File(webappBase, "/WEB-INF/lib");
+            if (lib.exists() && lib.isDirectory()) {
+                String[] libs = lib.list();
+                for (int i = 0; i < libs.length; i++) {
+                    if( libs[i].length() <5 ) continue;
+                    String ext=libs[i].substring( libs[i].length() - 4 );
+                    if (! ".jar".equalsIgnoreCase(ext)) {
+                        if (".tld".equalsIgnoreCase(ext)) {
+                            log.warn("TLD files should not be placed in "
+                                     + "/WEB-INF/lib");
+                        }
+                        continue;
+                    }
+                    try {
+                        File libFile = new File(lib, libs[i]);
+                        classPath = classPath + File.pathSeparator
+                            + libFile.getAbsolutePath();
+                        urls.add(libFile.getAbsoluteFile().toURL());
+                    } catch (IOException ioe) {
+                        // failing a toCanonicalPath on a file that
+                        // exists() should be a JVM regression test,
+                        // therefore we have permission to freak out
+                        throw new RuntimeException(ioe.toString());
+                    }
+                }
+            }
+        }
+
+        // What is this ??
+        urls.add(new File(clctxt.getRealPath("/")).getCanonicalFile().toURL());
+
+        URL urlsA[]=new URL[urls.size()];
+        urls.toArray(urlsA);
+        loader = new URLClassLoader(urlsA, this.getClass().getClassLoader());
+
+    }
+
+    /**
+     * Find the WEB-INF dir by looking up in the directory tree.
+     * This is used if no explicit docbase is set, but only files.
+     * XXX Maybe we should require the docbase.
+     */
+    protected void locateUriRoot( File f ) {
+        String tUriBase = uriBase;
+        if (tUriBase == null) {
+            tUriBase = "/";
+        }
+        try {
+            if (f.exists()) {
+                f = new File(f.getAbsolutePath());
+                while (f != null) {
+                    File g = new File(f, "WEB-INF");
+                    if (g.exists() && g.isDirectory()) {
+                        uriRoot = f.getCanonicalPath();
+                        uriBase = tUriBase;
+                        if (log.isInfoEnabled()) {
+                            log.info(Localizer.getMessage(
+                                        "jspc.implicit.uriRoot",
+                                        uriRoot));
+                        }
+                        break;
+                    }
+                    if (f.exists() && f.isDirectory()) {
+                        tUriBase = "/" + f.getName() + "/" + tUriBase;
+                    }
+
+                    String fParent = f.getParent();
+                    if (fParent == null) {
+                        break;
+                    } else {
+                        f = new File(fParent);
+                    }
+
+                    // If there is no acceptible candidate, uriRoot will
+                    // remain null to indicate to the CompilerContext to
+                    // use the current working/user dir.
+                }
+
+                if (uriRoot != null) {
+                    File froot = new File(uriRoot);
+                    uriRoot = froot.getCanonicalPath();
+                }
+            }
+        } catch (IOException ioe) {
+            // since this is an optional default and a null value
+            // for uriRoot has a non-error meaning, we can just
+            // pass straight through
+        }
+    }
+
+    /**
+     * Resolves the relative or absolute pathname correctly
+     * in both Ant and command-line situations.  If Ant launched
+     * us, we should use the basedir of the current project
+     * to resolve relative paths.
+     *
+     * See Bugzilla 35571.
+     *
+     * @param s The file
+     * @return The file resolved
+     */
+     protected File resolveFile(final String s) {
+         if(getProject() == null) {
+             // Note FileUtils.getFileUtils replaces FileUtils.newFileUtils in Ant 1.6.3
+             return FileUtils.newFileUtils().resolveFile(null, s);
+         } else {
+             return FileUtils.newFileUtils().resolveFile(getProject().getBaseDir(), s);
+         }
+     }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java
new file mode 100644
index 0000000..813cf9b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/JspCompilationContext.java
@@ -0,0 +1,738 @@
+/*
+ * 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.jasper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.tagext.TagInfo;
+
+import org.apache.jasper.compiler.Compiler;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.JspUtil;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.compiler.ServletWriter;
+import org.apache.jasper.servlet.JasperLoader;
+import org.apache.jasper.servlet.JspServletWrapper;
+
+/**
+ * A place holder for various things that are used through out the JSP
+ * engine. This is a per-request/per-context data structure. Some of
+ * the instance variables are set at different points.
+ *
+ * Most of the path-related stuff is here - mangling names, versions, dirs,
+ * loading resources and dealing with uris. 
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Pierre Delisle
+ * @author Costin Manolache
+ * @author Kin-man Chung
+ */
+public class JspCompilationContext {
+
+    protected org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(JspCompilationContext.class);
+
+    protected Map<String, URL> tagFileJarUrls;
+    protected boolean isPackagedTagFile;
+
+    protected String className;
+    protected String jspUri;
+    protected boolean isErrPage;
+    protected String basePackageName;
+    protected String derivedPackageName;
+    protected String servletJavaFileName;
+    protected String javaPath;
+    protected String classFileName;
+    protected String contentType;
+    protected ServletWriter writer;
+    protected Options options;
+    protected JspServletWrapper jsw;
+    protected Compiler jspCompiler;
+    protected String classPath;
+
+    protected String baseURI;
+    protected String outputDir;
+    protected ServletContext context;
+    protected URLClassLoader loader;
+
+    protected JspRuntimeContext rctxt;
+
+    protected int removed = 0;
+
+    protected URLClassLoader jspLoader;
+    protected URL baseUrl;
+    protected Class servletClass;
+
+    protected boolean isTagFile;
+    protected boolean protoTypeMode;
+    protected TagInfo tagInfo;
+    protected URL tagFileJarUrl;
+
+    // jspURI _must_ be relative to the context
+    public JspCompilationContext(String jspUri,
+                                 boolean isErrPage,
+                                 Options options,
+                                 ServletContext context,
+                                 JspServletWrapper jsw,
+                                 JspRuntimeContext rctxt) {
+
+        this.jspUri = canonicalURI(jspUri);
+        this.isErrPage = isErrPage;
+        this.options = options;
+        this.jsw = jsw;
+        this.context = context;
+
+        this.baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1);
+        // hack fix for resolveRelativeURI
+        if (baseURI == null) {
+            baseURI = "/";
+        } else if (baseURI.charAt(0) != '/') {
+            // strip the basde slash since it will be combined with the
+            // uriBase to generate a file
+            baseURI = "/" + baseURI;
+        }
+        if (baseURI.charAt(baseURI.length() - 1) != '/') {
+            baseURI += '/';
+        }
+
+        this.rctxt = rctxt;
+        this.tagFileJarUrls = new HashMap<String, URL>();
+        this.basePackageName = Constants.JSP_PACKAGE_NAME;
+    }
+
+    public JspCompilationContext(String tagfile,
+                                 TagInfo tagInfo, 
+                                 Options options,
+                                 ServletContext context,
+                                 JspServletWrapper jsw,
+                                 JspRuntimeContext rctxt,
+                                 URL tagFileJarUrl) {
+        this(tagfile, false, options, context, jsw, rctxt);
+        this.isTagFile = true;
+        this.tagInfo = tagInfo;
+        this.tagFileJarUrl = tagFileJarUrl;
+        if (tagFileJarUrl != null) {
+            isPackagedTagFile = true;
+        }
+    }
+
+    /* ==================== Methods to override ==================== */
+    
+    /** ---------- Class path and loader ---------- */
+
+    /**
+     * The classpath that is passed off to the Java compiler. 
+     */
+    public String getClassPath() {
+        if( classPath != null )
+            return classPath;
+        return rctxt.getClassPath();
+    }
+
+    /**
+     * The classpath that is passed off to the Java compiler. 
+     */
+    public void setClassPath(String classPath) {
+        this.classPath = classPath;
+    }
+
+    /**
+     * What class loader to use for loading classes while compiling
+     * this JSP?
+     */
+    public ClassLoader getClassLoader() {
+        if( loader != null )
+            return loader;
+        return rctxt.getParentClassLoader();
+    }
+
+    public void setClassLoader(URLClassLoader loader) {
+        this.loader = loader;
+    }
+
+    public ClassLoader getJspLoader() {
+        if( jspLoader == null ) {
+            jspLoader = new JasperLoader
+            (new URL[] {baseUrl},
+                    getClassLoader(),
+                    rctxt.getPermissionCollection(),
+                    rctxt.getCodeSource());
+        }
+        return jspLoader;
+    }
+
+    /** ---------- Input/Output  ---------- */
+    
+    /**
+     * The output directory to generate code into.  The output directory
+     * is make up of the scratch directory, which is provide in Options,
+     * plus the directory derived from the package name.
+     */
+    public String getOutputDir() {
+	if (outputDir == null) {
+	    createOutputDir();
+	}
+
+        return outputDir;
+    }
+
+    /**
+     * Create a "Compiler" object based on some init param data. This
+     * is not done yet. Right now we're just hardcoding the actual
+     * compilers that are created. 
+     */
+    public Compiler createCompiler() throws JasperException {
+        if (jspCompiler != null ) {
+            return jspCompiler;
+        }
+        jspCompiler = null;
+        if (options.getCompilerClassName() != null) {
+            jspCompiler = createCompiler(options.getCompilerClassName());
+        } else {
+            if (options.getCompiler() == null) {
+                jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
+                if (jspCompiler == null) {
+                    jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
+                }
+            } else {
+                jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
+                if (jspCompiler == null) {
+                    jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
+                }
+            }
+        }
+        if (jspCompiler == null) {
+            throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler"));
+        }
+        jspCompiler.init(this, jsw);
+        return jspCompiler;
+    }
+
+    protected Compiler createCompiler(String className) {
+        Compiler compiler = null; 
+        try {
+            compiler = (Compiler) Class.forName(className).newInstance();
+        } catch (InstantiationException e) {
+            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
+        } catch (IllegalAccessException e) {
+            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
+        } catch (NoClassDefFoundError e) {
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
+            }
+        }
+        return compiler;
+    }
+    
+    public Compiler getCompiler() {
+        return jspCompiler;
+    }
+
+    /** ---------- Access resources in the webapp ---------- */
+
+    /** 
+     * Get the full value of a URI relative to this compilations context
+     * uses current file as the base.
+     */
+    public String resolveRelativeUri(String uri) {
+        // sometimes we get uri's massaged from File(String), so check for
+        // a root directory deperator char
+        if (uri.startsWith("/") || uri.startsWith(File.separator)) {
+            return uri;
+        } else {
+            return baseURI + uri;
+        }
+    }
+
+    /**
+     * Gets a resource as a stream, relative to the meanings of this
+     * context's implementation.
+     * @return a null if the resource cannot be found or represented 
+     *         as an InputStream.
+     */
+    public java.io.InputStream getResourceAsStream(String res) {
+        return context.getResourceAsStream(canonicalURI(res));
+    }
+
+
+    public URL getResource(String res) throws MalformedURLException {
+        URL result = null;
+
+        if (res.startsWith("/META-INF/")) {
+            // This is a tag file packaged in a jar that is being compiled
+            URL jarUrl = tagFileJarUrls.get(res);
+            if (jarUrl == null) {
+                jarUrl = tagFileJarUrl;
+            }
+            if (jarUrl != null) {
+                result = new URL(jarUrl.toExternalForm() + res.substring(1));
+            }
+        } else if (res.startsWith("jar:file:")) {
+                // This is a tag file packaged in a jar that is being checked
+                // for a dependency
+                result = new URL(res);
+
+        } else {
+            result = context.getResource(canonicalURI(res));
+        }
+        return result;
+    }
+
+
+    public Set getResourcePaths(String path) {
+        return context.getResourcePaths(canonicalURI(path));
+    }
+
+    /** 
+     * Gets the actual path of a URI relative to the context of
+     * the compilation.
+     */
+    public String getRealPath(String path) {
+        if (context != null) {
+            return context.getRealPath(path);
+        }
+        return path;
+    }
+
+    /**
+     * Returns the tag-file-name-to-JAR-file map of this compilation unit,
+     * which maps tag file names to the JAR files in which the tag files are
+     * packaged.
+     *
+     * The map is populated when parsing the tag-file elements of the TLDs
+     * of any imported taglibs. 
+     */
+    public URL getTagFileJarUrl(String tagFile) {
+        return this.tagFileJarUrls.get(tagFile);
+    }
+
+    public void setTagFileJarUrl(String tagFile, URL tagFileURL) {
+        this.tagFileJarUrls.put(tagFile, tagFileURL);
+    }
+
+    /**
+     * Returns the JAR file in which the tag file for which this
+     * JspCompilationContext was created is packaged, or null if this
+     * JspCompilationContext does not correspond to a tag file, or if the
+     * corresponding tag file is not packaged in a JAR.
+     */
+    public URL getTagFileJarUrl() {
+        return this.tagFileJarUrl;
+    }
+
+    /* ==================== Common implementation ==================== */
+
+    /**
+     * Just the class name (does not include package name) of the
+     * generated class. 
+     */
+    public String getServletClassName() {
+
+        if (className != null) {
+            return className;
+        }
+
+        if (isTagFile) {
+            className = tagInfo.getTagClassName();
+            int lastIndex = className.lastIndexOf('.');
+            if (lastIndex != -1) {
+                className = className.substring(lastIndex + 1);
+            }
+        } else {
+            int iSep = jspUri.lastIndexOf('/') + 1;
+            className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
+        }
+        return className;
+    }
+
+    public void setServletClassName(String className) {
+        this.className = className;
+    }
+    
+    /**
+     * Path of the JSP URI. Note that this is not a file name. This is
+     * the context rooted URI of the JSP file. 
+     */
+    public String getJspFile() {
+        return jspUri;
+    }
+
+    /**
+     * Are we processing something that has been declared as an
+     * errorpage? 
+     */
+    public boolean isErrorPage() {
+        return isErrPage;
+    }
+
+    public void setErrorPage(boolean isErrPage) {
+        this.isErrPage = isErrPage;
+    }
+
+    public boolean isTagFile() {
+        return isTagFile;
+    }
+
+    public TagInfo getTagInfo() {
+        return tagInfo;
+    }
+
+    public void setTagInfo(TagInfo tagi) {
+        tagInfo = tagi;
+    }
+
+    /**
+     * True if we are compiling a tag file in prototype mode.
+     * ie we only generate codes with class for the tag handler with empty
+     * method bodies.
+     */
+    public boolean isPrototypeMode() {
+        return protoTypeMode;
+    }
+
+    public void setPrototypeMode(boolean pm) {
+        protoTypeMode = pm;
+    }
+
+    /**
+     * Package name for the generated class is make up of the base package
+     * name, which is user settable, and the derived package name.  The
+     * derived package name directly mirrors the file heirachy of the JSP page.
+     */
+    public String getServletPackageName() {
+        if (isTagFile()) {
+            String className = tagInfo.getTagClassName();
+            int lastIndex = className.lastIndexOf('.');
+            String pkgName = "";
+            if (lastIndex != -1) {
+                pkgName = className.substring(0, lastIndex);
+            }
+            return pkgName;
+        } else {
+            String dPackageName = getDerivedPackageName();
+            if (dPackageName.length() == 0) {
+                return basePackageName;
+            }
+            return basePackageName + '.' + getDerivedPackageName();
+        }
+    }
+
+    protected String getDerivedPackageName() {
+        if (derivedPackageName == null) {
+            int iSep = jspUri.lastIndexOf('/');
+            derivedPackageName = (iSep > 0) ?
+                    JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
+        }
+        return derivedPackageName;
+    }
+	    
+    /**
+     * The package name into which the servlet class is generated.
+     */
+    public void setServletPackageName(String servletPackageName) {
+        this.basePackageName = servletPackageName;
+    }
+
+    /**
+     * Full path name of the Java file into which the servlet is being
+     * generated. 
+     */
+    public String getServletJavaFileName() {
+        if (servletJavaFileName == null) {
+            servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
+        }
+        return servletJavaFileName;
+    }
+
+    /**
+     * Get hold of the Options object for this context. 
+     */
+    public Options getOptions() {
+        return options;
+    }
+
+    public ServletContext getServletContext() {
+        return context;
+    }
+
+    public JspRuntimeContext getRuntimeContext() {
+        return rctxt;
+    }
+
+    /**
+     * Path of the Java file relative to the work directory.
+     */
+    public String getJavaPath() {
+
+        if (javaPath != null) {
+            return javaPath;
+        }
+
+        if (isTagFile()) {
+	    String tagName = tagInfo.getTagClassName();
+            javaPath = tagName.replace('.', '/') + ".java";
+        } else {
+            javaPath = getServletPackageName().replace('.', '/') + '/' +
+                       getServletClassName() + ".java";
+	}
+        return javaPath;
+    }
+
+    public String getClassFileName() {
+        if (classFileName == null) {
+            classFileName = getOutputDir() + getServletClassName() + ".class";
+        }
+        return classFileName;
+    }
+
+    /**
+     * Get the content type of this JSP.
+     *
+     * Content type includes content type and encoding.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * Where is the servlet being generated?
+     */
+    public ServletWriter getWriter() {
+        return writer;
+    }
+
+    public void setWriter(ServletWriter writer) {
+        this.writer = writer;
+    }
+
+    /**
+     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
+     * 
+     * @return An array of two Strings: The first element denotes the real
+     * 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.
+     * Returns null if the given uri is not associated with any tag library
+     * 'exposed' in the web application.
+     */
+    public String[] getTldLocation(String uri) throws JasperException {
+        String[] location = 
+            getOptions().getTldLocationsCache().getLocation(uri);
+        return location;
+    }
+
+    /**
+     * Are we keeping generated code around?
+     */
+    public boolean keepGenerated() {
+        return getOptions().getKeepGenerated();
+    }
+
+    // ==================== Removal ==================== 
+
+    public void incrementRemoved() {
+        if (removed == 0 && rctxt != null) {
+            rctxt.removeWrapper(jspUri);
+        }
+        removed++;
+    }
+
+    public boolean isRemoved() {
+        if (removed > 1 ) {
+            return true;
+        }
+        return false;
+    }
+
+    // ==================== Compile and reload ====================
+    
+    public void compile() throws JasperException, FileNotFoundException {
+        createCompiler();
+        if (jspCompiler.isOutDated()) {
+            try {
+                jspCompiler.removeGeneratedFiles();
+                jspLoader = null;
+                jspCompiler.compile();
+                jsw.setReload(true);
+                jsw.setCompilationException(null);
+            } catch (JasperException ex) {
+                // Cache compilation exception
+                jsw.setCompilationException(ex);
+                throw ex;
+            } catch (Exception ex) {
+                JasperException je = new JasperException(
+                            Localizer.getMessage("jsp.error.unable.compile"),
+                            ex);
+                // Cache compilation exception
+                jsw.setCompilationException(je);
+                throw je;
+            }
+        }
+    }
+
+    // ==================== Manipulating the class ====================
+
+    public Class load() 
+        throws JasperException, FileNotFoundException
+    {
+        try {
+            getJspLoader();
+            
+            String name;
+            if (isTagFile()) {
+                name = tagInfo.getTagClassName();
+            } else {
+                name = getServletPackageName() + "." + getServletClassName();
+            }
+            servletClass = jspLoader.loadClass(name);
+        } catch (ClassNotFoundException cex) {
+            throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
+                                      cex);
+        } catch (Exception ex) {
+            throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
+                                      ex);
+        }
+        removed = 0;
+        return servletClass;
+    }
+
+    // ==================== protected methods ==================== 
+
+    static Object outputDirLock = new Object();
+
+    public void checkOutputDir() {
+        if (outputDir != null) {
+            if (!(new File(outputDir)).exists()) {
+                makeOutputDir();
+            }
+        } else {
+            createOutputDir();
+        }
+    }
+        
+    protected boolean makeOutputDir() {
+        synchronized(outputDirLock) {
+            File outDirFile = new File(outputDir);
+            return (outDirFile.exists() || outDirFile.mkdirs());
+        }
+    }
+
+    protected void createOutputDir() {
+        String path = null;
+        if (isTagFile()) {
+            String tagName = tagInfo.getTagClassName();
+            path = tagName.replace('.', File.separatorChar);
+            path = path.substring(0, path.lastIndexOf(File.separatorChar));
+        } else {
+            path = getServletPackageName().replace('.',File.separatorChar);
+        }
+
+            // Append servlet or tag handler path to scratch dir
+            try {
+                File base = options.getScratchDir();
+                baseUrl = base.toURI().toURL();
+                outputDir = base.getAbsolutePath() + File.separator + path + 
+                    File.separator;
+                if (!makeOutputDir()) {
+                    throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
+                }
+            } catch (MalformedURLException e) {
+                throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
+            }
+    }
+    
+    protected static final boolean isPathSeparator(char c) {
+       return (c == '/' || c == '\\');
+    }
+
+    protected static final String canonicalURI(String s) {
+       if (s == null) return null;
+       StringBuffer result = new StringBuffer();
+       final int len = s.length();
+       int pos = 0;
+       while (pos < len) {
+           char c = s.charAt(pos);
+           if ( isPathSeparator(c) ) {
+               /*
+                * multiple path separators.
+                * 'foo///bar' -> 'foo/bar'
+                */
+               while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
+                   ++pos;
+               }
+
+               if (pos+1 < len && s.charAt(pos+1) == '.') {
+                   /*
+                    * a single dot at the end of the path - we are done.
+                    */
+                   if (pos+2 >= len) break;
+
+                   switch (s.charAt(pos+2)) {
+                       /*
+                        * self directory in path
+                        * foo/./bar -> foo/bar
+                        */
+                   case '/':
+                   case '\\':
+                       pos += 2;
+                       continue;
+
+                       /*
+                        * two dots in a path: go back one hierarchy.
+                        * foo/bar/../baz -> foo/baz
+                        */
+                   case '.':
+                       // only if we have exactly _two_ dots.
+                       if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
+                           pos += 3;
+                           int separatorPos = result.length()-1;
+                           while (separatorPos >= 0 && 
+                                  ! isPathSeparator(result
+                                                    .charAt(separatorPos))) {
+                               --separatorPos;
+                           }
+                           if (separatorPos >= 0)
+                               result.setLength(separatorPos);
+                           continue;
+                       }
+                   }
+               }
+           }
+           result.append(c);
+           ++pos;
+       }
+       return result.toString();
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Options.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Options.java
new file mode 100644
index 0000000..f52b370
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/Options.java
@@ -0,0 +1,202 @@
+/*
+ * 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.jasper;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.jasper.compiler.JspConfig;
+import org.apache.jasper.compiler.TagPluginManager;
+import org.apache.jasper.compiler.TldLocationsCache;
+
+/**
+ * A class to hold all init parameters specific to the JSP engine. 
+ *
+ * @author Anil K. Vijendran
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ */
+public interface Options {
+
+    /**
+     * Returns true if Jasper issues a compilation error instead of a runtime
+     * Instantiation error if the class attribute specified in useBean action
+     * is invalid.
+     */
+    public boolean getErrorOnUseBeanInvalidClassAttribute();
+
+    /**
+     * Are we keeping generated code around?
+     */
+    public boolean getKeepGenerated();
+
+    /**
+     * Returns true if tag handler pooling is enabled, false otherwise.
+     */
+    public boolean isPoolingEnabled();
+
+    /**
+     * Are we supporting HTML mapped servlets?
+     */
+    public boolean getMappedFile();
+
+    /**
+     * Should errors be sent to client or thrown into stderr?
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getSendErrorToClient();
+ 
+    /**
+     * Should we include debug information in compiled class?
+     */
+    public boolean getClassDebugInfo();
+
+    /**
+     * Background compile thread check interval in seconds
+     */
+    public int getCheckInterval();
+
+    /**
+     * Is Jasper being used in development mode?
+     */
+    public boolean getDevelopment();
+
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    public boolean getDisplaySourceFragment();
+
+    /**
+     * Is the generation of SMAP info for JSR45 debugging suppressed?
+     */
+    public boolean isSmapSuppressed();
+
+    /**
+     * Indicates whether SMAP info for JSR45 debugging should be dumped to a
+     * file.
+     * Ignored is suppressSmap() is true
+     */
+    public boolean isSmapDumped();
+
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    public boolean getTrimSpaces();
+
+    /**
+     * Class ID for use in the plugin tag when the browser is IE. 
+     */
+    public String getIeClassId();
+
+    /**
+     * What is my scratch dir?
+     */
+    public File getScratchDir();
+
+    /**
+     * What classpath should I use while compiling the servlets
+     * generated from JSP files?
+     */
+    public String getClassPath();
+
+    /**
+     * Compiler to use.
+     */
+    public String getCompiler();
+
+    /**
+     * The compiler target VM, e.g. 1.1, 1.2, 1.3, 1.4, or 1.5.
+     */
+    public String getCompilerTargetVM();
+
+    /**
+     * Compiler source VM, e.g. 1.3, 1.4, or 1.5.
+     */
+    public String getCompilerSourceVM();   
+
+    /**
+     * Java compiler class to use.
+     */
+    public String getCompilerClassName();   
+
+    /**
+     * The cache for the location of the TLD's
+     * for the various tag libraries 'exposed'
+     * by the web application.
+     * A tag library is 'exposed' either explicitely in 
+     * web.xml or implicitely via the uri tag in the TLD 
+     * of a taglib deployed in a jar file (WEB-INF/lib).
+     *
+     * @return the instance of the TldLocationsCache
+     * for the web-application.
+     */
+    public TldLocationsCache getTldLocationsCache();
+
+    /**
+     * Java platform encoding to generate the JSP
+     * page servlet.
+     */
+    public String getJavaEncoding();
+
+    /**
+     * boolean flag to tell Ant whether to fork JSP page compilations.
+     */
+    public boolean getFork();
+
+    /**
+     * Obtain JSP configuration informantion specified in web.xml.  
+     */
+    public JspConfig getJspConfig();
+
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    public boolean isXpoweredBy();
+
+    /**
+     * Obtain a Tag Plugin Manager
+     */
+    public TagPluginManager getTagPluginManager();
+
+    /**
+     * Are Text strings to be generated as char arrays?
+     */
+    public boolean genStringAsCharArray();
+    
+    /**
+     * Modification test interval.
+     */
+    public int getModificationTestInterval();
+    
+    /**
+     * Is caching enabled (used for precompilation).
+     */
+    public boolean isCaching();
+    
+    /**
+     * The web-application wide cache for the returned TreeNode
+     * by parseXMLDocument in TagLibraryInfoImpl.parseTLD,
+     * if isCaching returns true.
+     * 
+     * @return the Map(String uri, TreeNode tld) instance.
+     */
+    public Map getCache();
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java
new file mode 100644
index 0000000..351267a
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java
@@ -0,0 +1,493 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.jasper.JasperException;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Javac;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PatternSet;
+
+/**
+ * Main JSP compiler class. This class uses Ant for compiling.
+ *
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ * @author Mark Roth
+ */
+public class AntCompiler extends Compiler {
+
+    protected static Object javacLock = new Object();
+
+    static {
+        System.setErr(new SystemLogHandler(System.err));
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected Project project = null;
+    protected JasperAntLogger logger;
+
+    // ------------------------------------------------------------ Constructor
+
+    // Lazy eval - if we don't need to compile we probably don't need the project
+    protected Project getProject() {
+        
+        if (project != null)
+            return project;
+        
+        // Initializing project
+        project = new Project();
+        logger = new JasperAntLogger();
+        logger.setOutputPrintStream(System.out);
+        logger.setErrorPrintStream(System.err);
+        logger.setMessageOutputLevel(Project.MSG_INFO);
+        project.addBuildListener( logger);
+        if (System.getProperty("catalina.home") != null) {
+            project.setBasedir( System.getProperty("catalina.home"));
+        }
+        
+        if( options.getCompiler() != null ) {
+            if( log.isDebugEnabled() )
+                log.debug("Compiler " + options.getCompiler() );
+            project.setProperty("build.compiler", options.getCompiler() );
+        }
+        project.init();
+        return project;
+    }
+    
+    public class JasperAntLogger extends DefaultLogger {
+        
+        protected StringBuffer reportBuf = new StringBuffer();
+        
+        protected void printMessage(final String message,
+                final PrintStream stream,
+                final int priority) {
+        }
+        
+        protected void log(String message) {
+            reportBuf.append(message);
+            reportBuf.append(System.getProperty("line.separator"));
+        }
+        
+        protected String getReport() {
+            String report = reportBuf.toString();
+            reportBuf.setLength(0);
+            return report;
+        }
+    }
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /** 
+     * Compile the servlet from .java file to .class file
+     */
+    protected void generateClass(String[] smap)
+        throws FileNotFoundException, JasperException, Exception {
+        
+        long t1 = 0;
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+
+        String javaEncoding = ctxt.getOptions().getJavaEncoding();
+        String javaFileName = ctxt.getServletJavaFileName();
+        String classpath = ctxt.getClassPath(); 
+        
+        String sep = System.getProperty("path.separator");
+        
+        StringBuffer errorReport = new StringBuffer();
+        
+        StringBuffer info=new StringBuffer();
+        info.append("Compile: javaFileName=" + javaFileName + "\n" );
+        info.append("    classpath=" + classpath + "\n" );
+        
+        // Start capturing the System.err output for this thread
+        SystemLogHandler.setThread();
+        
+        // Initializing javac task
+        getProject();
+        Javac javac = (Javac) project.createTask("javac");
+        
+        // Initializing classpath
+        Path path = new Path(project);
+        path.setPath(System.getProperty("java.class.path"));
+        info.append("    cp=" + System.getProperty("java.class.path") + "\n");
+        StringTokenizer tokenizer = new StringTokenizer(classpath, sep);
+        while (tokenizer.hasMoreElements()) {
+            String pathElement = tokenizer.nextToken();
+            File repository = new File(pathElement);
+            path.setLocation(repository);
+            info.append("    cp=" + repository + "\n");
+        }
+        
+        if( log.isDebugEnabled() )
+            log.debug( "Using classpath: " + System.getProperty("java.class.path") + sep
+                    + classpath);
+        
+        // Initializing sourcepath
+        Path srcPath = new Path(project);
+        srcPath.setLocation(options.getScratchDir());
+        
+        info.append("    work dir=" + options.getScratchDir() + "\n");
+        
+        // Initialize and set java extensions
+        String exts = System.getProperty("java.ext.dirs");
+        if (exts != null) {
+            Path extdirs = new Path(project);
+            extdirs.setPath(exts);
+            javac.setExtdirs(extdirs);
+            info.append("    extension dir=" + exts + "\n");
+        }
+
+        // Add endorsed directories if any are specified and we're forking
+        // See Bugzilla 31257
+        if(ctxt.getOptions().getFork()) {
+            String endorsed = System.getProperty("java.endorsed.dirs");
+            if(endorsed != null) {
+                Javac.ImplementationSpecificArgument endorsedArg = 
+                    javac.createCompilerArg();
+                endorsedArg.setLine("-J-Djava.endorsed.dirs=" +
+                        quotePathList(endorsed));
+                info.append("    endorsed dir=" + quotePathList(endorsed) +
+                        "\n");
+            } else {
+                info.append("    no endorsed dirs specified\n");
+            }
+        }
+        
+        // Configure the compiler object
+        javac.setEncoding(javaEncoding);
+        javac.setClasspath(path);
+        javac.setDebug(ctxt.getOptions().getClassDebugInfo());
+        javac.setSrcdir(srcPath);
+        javac.setTempdir(options.getScratchDir());
+        javac.setOptimize(! ctxt.getOptions().getClassDebugInfo() );
+        javac.setFork(ctxt.getOptions().getFork());
+        info.append("    srcDir=" + srcPath + "\n" );
+        
+        // Set the Java compiler to use
+        if (options.getCompiler() != null) {
+            javac.setCompiler(options.getCompiler());
+            info.append("    compiler=" + options.getCompiler() + "\n");
+        }
+
+        if (options.getCompilerTargetVM() != null) {
+            javac.setTarget(options.getCompilerTargetVM());
+            info.append("   compilerTargetVM=" + options.getCompilerTargetVM() + "\n");
+        }
+
+        if (options.getCompilerSourceVM() != null) {
+            javac.setSource(options.getCompilerSourceVM());
+            info.append("   compilerSourceVM=" + options.getCompilerSourceVM() + "\n");
+        }
+        
+        // Build includes path
+        PatternSet.NameEntry includes = javac.createInclude();
+        
+        includes.setName(ctxt.getJavaPath());
+        info.append("    include="+ ctxt.getJavaPath() + "\n" );
+        
+        BuildException be = null;
+        
+        try {
+            if (ctxt.getOptions().getFork()) {
+                javac.execute();
+            } else {
+                synchronized(javacLock) {
+                    javac.execute();
+                }
+            }
+        } catch (BuildException e) {
+            be = e;
+            log.error(Localizer.getMessage("jsp.error.javac"), e);
+            log.error(Localizer.getMessage("jsp.error.javac.env") + info.toString());
+        }
+        
+        errorReport.append(logger.getReport());
+
+        // Stop capturing the System.err output for this thread
+        String errorCapture = SystemLogHandler.unsetThread();
+        if (errorCapture != null) {
+            errorReport.append(System.getProperty("line.separator"));
+            errorReport.append(errorCapture);
+        }
+
+        if (!ctxt.keepGenerated()) {
+            File javaFile = new File(javaFileName);
+            javaFile.delete();
+        }
+        
+        if (be != null) {
+            String errorReportString = errorReport.toString();
+            log.error(Localizer.getMessage("jsp.error.compilation", javaFileName, errorReportString));
+            JavacErrorDetail[] javacErrors = ErrorDispatcher.parseJavacErrors(
+                    errorReportString, javaFileName, pageNodes);
+            if (javacErrors != null) {
+                errDispatcher.javacError(javacErrors);
+            } else {
+                errDispatcher.javacError(errorReportString, be);
+            }
+        }
+        
+        if( log.isDebugEnabled() ) {
+            long t2 = System.currentTimeMillis();
+            log.debug("Compiled " + ctxt.getServletJavaFileName() + " "
+                      + (t2-t1) + "ms");
+        }
+        
+        logger = null;
+        project = null;
+        
+        if (ctxt.isPrototypeMode()) {
+            return;
+        }
+        
+        // JSR45 Support
+        if (! options.isSmapSuppressed()) {
+            SmapUtil.installSmap(smap);
+        }
+    }
+
+    private String quotePathList(String list) {
+        StringBuffer result = new StringBuffer(list.length() + 10);
+        StringTokenizer st = new StringTokenizer(list, File.pathSeparator);
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (token.indexOf(' ') == -1) {
+                result.append(token);
+            } else {
+                result.append('\"');
+                result.append(token);
+                result.append('\"');
+            }
+            if (st.hasMoreTokens()) {
+                result.append(File.pathSeparatorChar);
+            }
+        }
+        return result.toString();
+    }
+
+
+    protected static class SystemLogHandler extends PrintStream {
+
+
+        // ----------------------------------------------------------- Constructors
+
+
+        /**
+         * Construct the handler to capture the output of the given steam.
+         */
+        public SystemLogHandler(PrintStream wrapped) {
+            super(wrapped);
+            this.wrapped = wrapped;
+        }
+
+
+        // ----------------------------------------------------- Instance Variables
+
+
+        /**
+         * Wrapped PrintStream.
+         */
+        protected PrintStream wrapped = null;
+
+
+        /**
+         * Thread <-> PrintStream associations.
+         */
+        protected static ThreadLocal streams = new ThreadLocal();
+
+
+        /**
+         * Thread <-> ByteArrayOutputStream associations.
+         */
+        protected static ThreadLocal data = new ThreadLocal();
+
+
+        // --------------------------------------------------------- Public Methods
+
+
+        public PrintStream getWrapped() {
+          return wrapped;
+        }
+
+        /**
+         * Start capturing thread's output.
+         */
+        public static void setThread() {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            data.set(baos);
+            streams.set(new PrintStream(baos));
+        }
+
+
+        /**
+         * Stop capturing thread's output and return captured data as a String.
+         */
+        public static String unsetThread() {
+            ByteArrayOutputStream baos = 
+                (ByteArrayOutputStream) data.get();
+            if (baos == null) {
+                return null;
+            }
+            streams.set(null);
+            data.set(null);
+            return baos.toString();
+        }
+
+
+        // ------------------------------------------------------ Protected Methods
+
+
+        /**
+         * Find PrintStream to which the output must be written to.
+         */
+        protected PrintStream findStream() {
+            PrintStream ps = (PrintStream) streams.get();
+            if (ps == null) {
+                ps = wrapped;
+            }
+            return ps;
+        }
+
+
+        // ---------------------------------------------------- PrintStream Methods
+
+
+        public void flush() {
+            findStream().flush();
+        }
+
+        public void close() {
+            findStream().close();
+        }
+
+        public boolean checkError() {
+            return findStream().checkError();
+        }
+
+        protected void setError() {
+            //findStream().setError();
+        }
+
+        public void write(int b) {
+            findStream().write(b);
+        }
+
+        public void write(byte[] b)
+            throws IOException {
+            findStream().write(b);
+        }
+
+        public void write(byte[] buf, int off, int len) {
+            findStream().write(buf, off, len);
+        }
+
+        public void print(boolean b) {
+            findStream().print(b);
+        }
+
+        public void print(char c) {
+            findStream().print(c);
+        }
+
+        public void print(int i) {
+            findStream().print(i);
+        }
+
+        public void print(long l) {
+            findStream().print(l);
+        }
+
+        public void print(float f) {
+            findStream().print(f);
+        }
+
+        public void print(double d) {
+            findStream().print(d);
+        }
+
+        public void print(char[] s) {
+            findStream().print(s);
+        }
+
+        public void print(String s) {
+            findStream().print(s);
+        }
+
+        public void print(Object obj) {
+            findStream().print(obj);
+        }
+
+        public void println() {
+            findStream().println();
+        }
+
+        public void println(boolean x) {
+            findStream().println(x);
+        }
+
+        public void println(char x) {
+            findStream().println(x);
+        }
+
+        public void println(int x) {
+            findStream().println(x);
+        }
+
+        public void println(long x) {
+            findStream().println(x);
+        }
+
+        public void println(float x) {
+            findStream().println(x);
+        }
+
+        public void println(double x) {
+            findStream().println(x);
+        }
+
+        public void println(char[] x) {
+            findStream().println(x);
+        }
+
+        public void println(String x) {
+            findStream().println(x);
+        }
+
+        public void println(Object x) {
+            findStream().println(x);
+        }
+
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java
new file mode 100644
index 0000000..cd32b7f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java
@@ -0,0 +1,75 @@
+/*
+ * 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.jasper.compiler;
+
+
+import java.util.HashMap;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Repository of {page, request, session, application}-scoped beans 
+ *
+ * @author Mandar Raje
+ * @author Remy Maucherat
+ */
+public class BeanRepository {
+
+    protected HashMap<String, String> beanTypes;
+    protected ClassLoader loader;
+    protected ErrorDispatcher errDispatcher;
+
+    /**
+     * Constructor.
+     */    
+    public BeanRepository(ClassLoader loader, ErrorDispatcher err) {
+        this.loader = loader;
+        this.errDispatcher = err;
+        beanTypes = new HashMap<String, String>();
+    }
+
+    public void addBean(Node.UseBean n, String s, String type, String scope)
+        throws JasperException {
+
+        if (!(scope == null || scope.equals("page") || scope.equals("request") 
+                || scope.equals("session") || scope.equals("application"))) {
+            errDispatcher.jspError(n, "jsp.error.usebean.badScope");
+        }
+
+        beanTypes.put(s, type);
+    }
+            
+    public Class getBeanType(String bean)
+        throws JasperException {
+        Class clazz = null;
+        try {
+            clazz = loader.loadClass(beanTypes.get(bean));
+        } catch (ClassNotFoundException ex) {
+            throw new JasperException (ex);
+        }
+        return clazz;
+    }
+      
+    public boolean checkVariable(String bean) {
+        return beanTypes.containsKey(bean);
+    }
+
+}
+
+
+
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Collector.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Collector.java
new file mode 100644
index 0000000..50b2b9d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Collector.java
@@ -0,0 +1,204 @@
+/*
+ * 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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Collect info about the page and nodes, and make them availabe through
+ * the PageInfo object.
+ *
+ * @author Kin-man Chung
+ * @author Mark Roth
+ */
+
+class Collector {
+
+    /**
+     * A visitor for collecting information on the page and the body of
+     * the custom tags.
+     */
+    static class CollectVisitor extends Node.Visitor {
+
+        private boolean scriptingElementSeen = false;
+        private boolean usebeanSeen = false;
+        private boolean includeActionSeen = false;
+        private boolean paramActionSeen = false;
+        private boolean setPropertySeen = false;
+        private boolean hasScriptingVars = false;
+
+        public void visit(Node.ParamAction n) throws JasperException {
+            if (n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            paramActionSeen = true;
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            includeActionSeen = true;
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            if (n.getValue() != null && n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            setPropertySeen = true;
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            if (n.getBeanName() != null && n.getBeanName().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            usebeanSeen = true;
+                visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            if (n.getHeight() != null && n.getHeight().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            if (n.getWidth() != null && n.getWidth().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            // Check to see what kinds of element we see as child elements
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        /**
+         * Check all child nodes for various elements and update the given
+         * ChildInfo object accordingly.  Visits body in the process.
+         */
+        private void checkSeen( Node.ChildInfo ci, Node n )
+            throws JasperException
+        {
+            // save values collected so far
+            boolean scriptingElementSeenSave = scriptingElementSeen;
+            scriptingElementSeen = false;
+            boolean usebeanSeenSave = usebeanSeen;
+            usebeanSeen = false;
+            boolean includeActionSeenSave = includeActionSeen;
+            includeActionSeen = false;
+            boolean paramActionSeenSave = paramActionSeen;
+            paramActionSeen = false;
+            boolean setPropertySeenSave = setPropertySeen;
+            setPropertySeen = false;
+            boolean hasScriptingVarsSave = hasScriptingVars;
+            hasScriptingVars = false;
+
+            // Scan attribute list for expressions
+            if( n instanceof Node.CustomTag ) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                Node.JspAttribute[] attrs = ct.getJspAttributes();
+                for (int i = 0; attrs != null && i < attrs.length; i++) {
+                    if (attrs[i].isExpression()) {
+                        scriptingElementSeen = true;
+                        break;
+                    }
+                }
+            }
+
+            visitBody(n);
+
+            if( (n instanceof Node.CustomTag) && !hasScriptingVars) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                hasScriptingVars = ct.getVariableInfos().length > 0 ||
+                    ct.getTagVariableInfos().length > 0;
+            }
+
+            // Record if the tag element and its body contains any scriptlet.
+            ci.setScriptless(! scriptingElementSeen);
+            ci.setHasUseBean(usebeanSeen);
+            ci.setHasIncludeAction(includeActionSeen);
+            ci.setHasParamAction(paramActionSeen);
+            ci.setHasSetProperty(setPropertySeen);
+            ci.setHasScriptingVars(hasScriptingVars);
+
+            // Propagate value of scriptingElementSeen up.
+            scriptingElementSeen = scriptingElementSeen || scriptingElementSeenSave;
+            usebeanSeen = usebeanSeen || usebeanSeenSave;
+            setPropertySeen = setPropertySeen || setPropertySeenSave;
+            includeActionSeen = includeActionSeen || includeActionSeenSave;
+            paramActionSeen = paramActionSeen || paramActionSeenSave;
+            hasScriptingVars = hasScriptingVars || hasScriptingVarsSave;
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+            if (n.getNameAttribute().isExpression())
+                scriptingElementSeen = true;
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; i < attrs.length; i++) {
+                if (attrs[i].isExpression()) {
+                    scriptingElementSeen = true;
+                    break;
+                }
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void updatePageInfo(PageInfo pageInfo) {
+            pageInfo.setScriptless(! scriptingElementSeen);
+        }
+    }
+
+
+    public static void collect(Compiler compiler, Node.Nodes page)
+        throws JasperException {
+
+    CollectVisitor collectVisitor = new CollectVisitor();
+        page.visit(collectVisitor);
+        collectVisitor.updatePageInfo(compiler.getPageInfo());
+
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java
new file mode 100644
index 0000000..9a764f1
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java
@@ -0,0 +1,551 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.servlet.JspServletWrapper;
+
+/**
+ * Main JSP compiler class. This class uses Ant for compiling.
+ * 
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ * @author Mark Roth
+ */
+public abstract class Compiler {
+    
+    protected org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+            .getLog(Compiler.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected JspCompilationContext ctxt;
+
+    protected ErrorDispatcher errDispatcher;
+
+    protected PageInfo pageInfo;
+
+    protected JspServletWrapper jsw;
+
+    protected TagFileProcessor tfp;
+
+    protected Options options;
+
+    protected Node.Nodes pageNodes;
+
+    // ------------------------------------------------------------ Constructor
+
+    public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
+        this.jsw = jsw;
+        this.ctxt = ctxt;
+        this.options = ctxt.getOptions();
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * <p>
+     * Retrieves the parsed nodes of the JSP page, if they are available. May
+     * return null. Used in development mode for generating detailed error
+     * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
+     * </p>
+     */
+    public Node.Nodes getPageNodes() {
+        return this.pageNodes;
+    }
+
+    /**
+     * Compile the jsp file into equivalent servlet in .java file
+     * 
+     * @return a smap for the current JSP page, if one is generated, null
+     *         otherwise
+     */
+    protected String[] generateJava() throws Exception {
+
+        String[] smapStr = null;
+
+        long t1, t2, t3, t4;
+
+        t1 = t2 = t3 = t4 = 0;
+
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+
+        // Setup page info area
+        pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
+                errDispatcher), ctxt.getJspFile());
+
+        JspConfig jspConfig = options.getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(ctxt
+                .getJspFile());
+
+        /*
+         * If the current uri is matched by a pattern specified in a
+         * jsp-property-group in web.xml, initialize pageInfo with those
+         * properties.
+         */
+        if (jspProperty.isELIgnored() != null) {
+            pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty
+                    .isELIgnored()));
+        }
+        if (jspProperty.isScriptingInvalid() != null) {
+            pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty
+                    .isScriptingInvalid()));
+        }
+        if (jspProperty.getIncludePrelude() != null) {
+            pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
+        }
+        if (jspProperty.getIncludeCoda() != null) {
+            pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
+        }
+        if (jspProperty.isDeferedSyntaxAllowedAsLiteral() != null) {
+            pageInfo.setDeferredSyntaxAllowedAsLiteral(JspUtil.booleanValue(jspProperty
+                    .isDeferedSyntaxAllowedAsLiteral()));
+        }
+        if (jspProperty.isTrimDirectiveWhitespaces() != null) {
+            pageInfo.setTrimDirectiveWhitespaces(JspUtil.booleanValue(jspProperty
+                    .isTrimDirectiveWhitespaces()));
+        }
+
+        ctxt.checkOutputDir();
+        String javaFileName = ctxt.getServletJavaFileName();
+
+        ServletWriter writer = null;
+        try {
+            /*
+             * The setting of isELIgnored changes the behaviour of the parser
+             * in subtle ways. To add to the 'fun', isELIgnored can be set in
+             * any file that forms part of the translation unit so setting it
+             * in a file included towards the end of the translation unit can
+             * change how the parser should have behaved when parsing content
+             * up to the point where isELIgnored was set. Arghh!
+             * Previous attempts to hack around this have only provided partial
+             * solutions. We now use two passes to parse the translation unit.
+             * The first just parses the directives and the second parses the
+             * whole translation unit once we know how isELIgnored has been set.
+             * TODO There are some possible optimisations of this process.  
+             */ 
+            // Parse the file
+            ParserController parserCtl = new ParserController(ctxt, this);
+
+            // Pass 1 - the directives
+            Node.Nodes directives =
+                parserCtl.parseDirectives(ctxt.getJspFile());
+            Validator.validateDirectives(this, directives);
+
+            // Pass 2 - the whole translation unit
+            pageNodes = parserCtl.parse(ctxt.getJspFile());
+
+            if (ctxt.isPrototypeMode()) {
+                // generate prototype .java file for the tag file
+                writer = setupContextWriter(javaFileName);
+                Generator.generate(writer, this, pageNodes);
+                writer.close();
+                writer = null;
+                return null;
+            }
+
+            // Validate and process attributes - don't re-validate the
+            // directives we validated in pass 1
+            Validator.validateExDirectives(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t2 = System.currentTimeMillis();
+            }
+
+            // Collect page info
+            Collector.collect(this, pageNodes);
+
+            // Compile (if necessary) and load the tag files referenced in
+            // this compilation unit.
+            tfp = new TagFileProcessor();
+            tfp.loadTagFiles(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t3 = System.currentTimeMillis();
+            }
+
+            // Determine which custom tag needs to declare which scripting vars
+            ScriptingVariabler.set(pageNodes, errDispatcher);
+
+            // Optimizations by Tag Plugins
+            TagPluginManager tagPluginManager = options.getTagPluginManager();
+            tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
+
+            // Optimization: concatenate contiguous template texts.
+            TextOptimizer.concatenate(this, pageNodes);
+
+            // Generate static function mapper codes.
+            ELFunctionMapper.map(this, pageNodes);
+
+            // generate servlet .java file
+            writer = setupContextWriter(javaFileName);
+            Generator.generate(writer, this, pageNodes);
+            writer.close();
+            writer = null;
+
+            // The writer is only used during the compile, dereference
+            // it in the JspCompilationContext when done to allow it
+            // to be GC'd and save memory.
+            ctxt.setWriter(null);
+
+            if (log.isDebugEnabled()) {
+                t4 = System.currentTimeMillis();
+                log.debug("Generated " + javaFileName + " total=" + (t4 - t1)
+                        + " generate=" + (t4 - t3) + " validate=" + (t2 - t1));
+            }
+
+        } catch (Exception e) {
+            if (writer != null) {
+                try {
+                    writer.close();
+                    writer = null;
+                } catch (Exception e1) {
+                    // do nothing
+                }
+            }
+            // Remove the generated .java file
+            new File(javaFileName).delete();
+            throw e;
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (Exception e2) {
+                    // do nothing
+                }
+            }
+        }
+
+        // JSR45 Support
+        if (!options.isSmapSuppressed()) {
+            smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
+        }
+
+        // If any proto type .java and .class files was generated,
+        // the prototype .java may have been replaced by the current
+        // compilation (if the tag file is self referencing), but the
+        // .class file need to be removed, to make sure that javac would
+        // generate .class again from the new .java file just generated.
+        tfp.removeProtoTypeFiles(ctxt.getClassFileName());
+
+        return smapStr;
+    }
+
+	private ServletWriter setupContextWriter(String javaFileName)
+			throws FileNotFoundException, JasperException {
+		ServletWriter writer;
+		// Setup the ServletWriter
+		String javaEncoding = ctxt.getOptions().getJavaEncoding();
+		OutputStreamWriter osw = null;
+
+		try {
+		    osw = new OutputStreamWriter(
+		            new FileOutputStream(javaFileName), javaEncoding);
+		} catch (UnsupportedEncodingException ex) {
+		    errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
+		            javaEncoding);
+		}
+
+		writer = new ServletWriter(new PrintWriter(osw));
+		ctxt.setWriter(writer);
+		return writer;
+	}
+
+    /**
+     * Compile the servlet from .java file to .class file
+     */
+    protected abstract void generateClass(String[] smap)
+            throws FileNotFoundException, JasperException, Exception;
+
+    /**
+     * Compile the jsp file from the current engine context
+     */
+    public void compile() throws FileNotFoundException, JasperException,
+            Exception {
+        compile(true);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     */
+    public void compile(boolean compileClass) throws FileNotFoundException,
+            JasperException, Exception {
+        compile(compileClass, false);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     * @param jspcMode
+     *            true if invoked from JspC, false otherwise
+     */
+    public void compile(boolean compileClass, boolean jspcMode)
+            throws FileNotFoundException, JasperException, Exception {
+        if (errDispatcher == null) {
+            this.errDispatcher = new ErrorDispatcher(jspcMode);
+        }
+
+        try {
+            String[] smap = generateJava();
+            if (compileClass) {
+                generateClass(smap);
+                // Fix for bugzilla 41606
+                // Set JspServletWrapper.servletClassLastModifiedTime after successful compile
+                String targetFileName = ctxt.getClassFileName();
+                if (targetFileName != null) {
+                    File targetFile = new File(targetFileName);
+                    if (targetFile.exists() && jsw != null) {
+                        jsw.setServletClassLastModifiedTime(targetFile.lastModified());
+                    }
+                }
+            }
+        } finally {
+            if (tfp != null && ctxt.isPrototypeMode()) {
+                tfp.removeProtoTypeFiles(null);
+            }
+            // Make sure these object which are only used during the
+            // generation and compilation of the JSP page get
+            // dereferenced so that they can be GC'd and reduce the
+            // memory footprint.
+            tfp = null;
+            errDispatcher = null;
+            pageInfo = null;
+
+            // Only get rid of the pageNodes if in production.
+            // In development mode, they are used for detailed
+            // error messages.
+            // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
+            if (!this.options.getDevelopment()) {
+                pageNodes = null;
+            }
+
+            if (ctxt.getWriter() != null) {
+                ctxt.getWriter().close();
+                ctxt.setWriter(null);
+            }
+        }
+    }
+
+    /**
+     * This is a protected method intended to be overridden by subclasses of
+     * Compiler. This is used by the compile method to do all the compilation.
+     */
+    public boolean isOutDated() {
+        return isOutDated(true);
+    }
+
+    /**
+     * Determine if a compilation is necessary by checking the time stamp of the
+     * JSP page with that of the corresponding .class or .java file. If the page
+     * has dependencies, the check is also extended to its dependeants, and so
+     * on. This method can by overidden by a subclasses of Compiler.
+     * 
+     * @param checkClass
+     *            If true, check against .class file, if false, check against
+     *            .java file.
+     */
+    public boolean isOutDated(boolean checkClass) {
+
+        String jsp = ctxt.getJspFile();
+
+        if (jsw != null
+                && (ctxt.getOptions().getModificationTestInterval() > 0)) {
+
+            if (jsw.getLastModificationTest()
+                    + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
+                    .currentTimeMillis()) {
+                return false;
+            } else {
+                jsw.setLastModificationTest(System.currentTimeMillis());
+            }
+        }
+
+        long jspRealLastModified = 0;
+        try {
+            URL jspUrl = ctxt.getResource(jsp);
+            if (jspUrl == null) {
+                ctxt.incrementRemoved();
+                return false;
+            }
+            URLConnection uc = jspUrl.openConnection();
+            if (uc instanceof JarURLConnection) {
+                jspRealLastModified =
+                    ((JarURLConnection) uc).getJarEntry().getTime();
+            } else {
+                jspRealLastModified = uc.getLastModified();
+            }
+            uc.getInputStream().close();
+        } catch (Exception e) {
+            return true;
+        }
+
+        long targetLastModified = 0;
+        File targetFile;
+
+        if (checkClass) {
+            targetFile = new File(ctxt.getClassFileName());
+        } else {
+            targetFile = new File(ctxt.getServletJavaFileName());
+        }
+
+        if (!targetFile.exists()) {
+            return true;
+        }
+
+        targetLastModified = targetFile.lastModified();
+        if (checkClass && jsw != null) {
+            jsw.setServletClassLastModifiedTime(targetLastModified);
+        }
+        if (targetLastModified < jspRealLastModified) {
+            if (log.isDebugEnabled()) {
+                log.debug("Compiler: outdated: " + targetFile + " "
+                        + targetLastModified);
+            }
+            return true;
+        }
+
+        // determine if source dependent files (e.g. includes using include
+        // directives) have been changed.
+        if (jsw == null) {
+            return false;
+        }
+
+        List depends = jsw.getDependants();
+        if (depends == null) {
+            return false;
+        }
+
+        Iterator it = depends.iterator();
+        while (it.hasNext()) {
+            String include = (String) it.next();
+            try {
+                URL includeUrl = ctxt.getResource(include);
+                if (includeUrl == null) {
+                    return true;
+                }
+
+                URLConnection iuc = includeUrl.openConnection();
+                long includeLastModified = 0;
+                if (iuc instanceof JarURLConnection) {
+                    includeLastModified =
+                        ((JarURLConnection) iuc).getJarEntry().getTime();
+                } else {
+                    includeLastModified = iuc.getLastModified();
+                }
+                iuc.getInputStream().close();
+
+                if (includeLastModified > targetLastModified) {
+                    return true;
+                }
+            } catch (Exception e) {
+                return true;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Gets the error dispatcher.
+     */
+    public ErrorDispatcher getErrorDispatcher() {
+        return errDispatcher;
+    }
+
+    /**
+     * Gets the info about the page under compilation
+     */
+    public PageInfo getPageInfo() {
+        return pageInfo;
+    }
+
+    public JspCompilationContext getCompilationContext() {
+        return ctxt;
+    }
+
+    /**
+     * Remove generated files
+     */
+    public void removeGeneratedFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                classFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+        try {
+            String javaFileName = ctxt.getServletJavaFileName();
+            if (javaFileName != null) {
+                File javaFile = new File(javaFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + javaFile);
+                javaFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+
+    public void removeGeneratedClassFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                classFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java
new file mode 100644
index 0000000..97d543c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java
@@ -0,0 +1,109 @@
+/*
+ * 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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Default implementation of ErrorHandler interface.
+ *
+ * @author Jan Luehe
+ */
+class DefaultErrorHandler implements ErrorHandler {
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param fname Name of the JSP file in which the parse error occurred
+     * @param line Parse error line number
+     * @param column Parse error column number
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String fname, int line, int column, String errMsg,
+            Exception ex) throws JasperException {
+        throw new JasperException(fname + "(" + line + "," + column + ")"
+                + " " + errMsg, ex);
+    }
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String errMsg, Exception ex) throws JasperException {
+        throw new JasperException(errMsg, ex);
+    }
+    
+    /*
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    public void javacError(JavacErrorDetail[] details) throws JasperException {
+        
+        if (details == null) {
+            return;
+        }
+        
+        Object[] args = null;
+        StringBuffer buf = new StringBuffer();
+        
+        for (int i=0; i < details.length; i++) {
+            if (details[i].getJspBeginLineNumber() >= 0) {
+                args = new Object[] {
+                        new Integer(details[i].getJspBeginLineNumber()), 
+                        details[i].getJspFileName() };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.single.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+                buf.append("\n");
+                buf.append(details[i].getJspExtract());
+            } else {
+                args = new Object[] {
+                        new Integer(details[i].getJavaLineNumber()) };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.java.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+            }
+        }
+        buf.append("\n\nStacktrace:");
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile") + ": " + buf);
+    }
+    
+    /**
+     * Processes the given javac error report and exception.
+     *
+     * @param errorReport Compilation error report
+     * @param exception Compilation exception
+     */
+    public void javacError(String errorReport, Exception exception)
+    throws JasperException {
+        
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile"), exception);
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java
new file mode 100644
index 0000000..1925f55
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java
@@ -0,0 +1,207 @@
+/*
+ * 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.jasper.compiler;
+
+import org.xml.sax.Attributes;
+import org.apache.jasper.JasperException;
+
+class Dumper {
+
+    static class DumpVisitor extends Node.Visitor {
+	private int indent = 0;
+
+	private String getAttributes(Attributes attrs) {
+	    if (attrs == null)
+		return "";
+
+	    StringBuffer buf = new StringBuffer();
+	    for (int i=0; i < attrs.getLength(); i++) {
+		buf.append(" " + attrs.getQName(i) + "=\""
+			   + attrs.getValue(i) + "\"");
+	    }
+	    return buf.toString();
+	}
+
+	private void printString(String str) {
+	    printIndent();
+	    System.out.print(str);
+	}
+
+	private void printString(String prefix, char[] chars, String suffix) {
+	    String str = null;
+	    if (chars != null) {
+		str = new String(chars);
+	    }
+	    printString(prefix, str, suffix);
+	}
+	     
+	private void printString(String prefix, String str, String suffix) {
+	    printIndent();
+	    if (str != null) {
+		System.out.print(prefix + str + suffix);
+	    } else {
+		System.out.print(prefix + suffix);
+	    }
+	}
+
+	private void printAttributes(String prefix, Attributes attrs,
+				     String suffix) {
+	    printString(prefix, getAttributes(attrs), suffix);
+	}
+
+	private void dumpBody(Node n) throws JasperException {
+	    Node.Nodes page = n.getBody();
+	    if (page != null) {
+//		indent++;
+		page.visit(this);
+//		indent--;
+	    }
+        }
+
+        public void visit(Node.PageDirective n) throws JasperException {
+	    printAttributes("<%@ page", n.getAttributes(), "%>");
+        }
+
+        public void visit(Node.TaglibDirective n) throws JasperException {
+	    printAttributes("<%@ taglib", n.getAttributes(), "%>");
+        }
+
+        public void visit(Node.IncludeDirective n) throws JasperException {
+	    printAttributes("<%@ include", n.getAttributes(), "%>");
+	    dumpBody(n);
+        }
+
+        public void visit(Node.Comment n) throws JasperException {
+	    printString("<%--", n.getText(), "--%>");
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+	    printString("<%!", n.getText(), "%>");
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+	    printString("<%=", n.getText(), "%>");
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+	    printString("<%", n.getText(), "%>");
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+	    printAttributes("<jsp:include", n.getAttributes(), ">");
+	    dumpBody(n);
+            printString("</jsp:include>");
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+	    printAttributes("<jsp:forward", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:forward>");
+        }
+
+        public void visit(Node.GetProperty n) throws JasperException {
+	    printAttributes("<jsp:getProperty", n.getAttributes(), "/>");
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+	    printAttributes("<jsp:setProperty", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:setProperty>");
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+	    printAttributes("<jsp:useBean", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:useBean>");
+        }
+	
+        public void visit(Node.PlugIn n) throws JasperException {
+	    printAttributes("<jsp:plugin", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:plugin>");
+	}
+        
+        public void visit(Node.ParamsAction n) throws JasperException {
+	    printAttributes("<jsp:params", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:params>");
+        }
+        
+        public void visit(Node.ParamAction n) throws JasperException {
+	    printAttributes("<jsp:param", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:param>");
+        }
+        
+        public void visit(Node.NamedAttribute n) throws JasperException {
+	    printAttributes("<jsp:attribute", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:attribute>");
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+	    printAttributes("<jsp:body", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:body>");
+        }
+        
+        public void visit(Node.ELExpression n) throws JasperException {
+	    printString( "${" + new String( n.getText() ) + "}" );
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+	    printAttributes("<" + n.getQName(), n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</" + n.getQName() + ">");
+        }
+
+	public void visit(Node.UninterpretedTag n) throws JasperException {
+	    String tag = n.getQName();
+	    printAttributes("<"+tag, n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</" + tag + ">");
+        }
+
+	public void visit(Node.TemplateText n) throws JasperException {
+	    printString(new String(n.getText()));
+	}
+
+	private void printIndent() {
+	    for (int i=0; i < indent; i++) {
+		System.out.print("  ");
+	    }
+	}
+    }
+
+    public static void dump(Node n) {
+	try {
+	    n.accept(new DumpVisitor());	
+	} catch (JasperException e) {
+	    e.printStackTrace();
+	}
+    }
+
+    public static void dump(Node.Nodes page) {
+	try {
+	    page.visit(new DumpVisitor());
+	} catch (JasperException e) {
+	    e.printStackTrace();
+	}
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java
new file mode 100644
index 0000000..f2df33d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java
@@ -0,0 +1,281 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.jasper.JasperException;
+
+/**
+ * This class generates functions mappers for the EL expressions in the page.
+ * Instead of a global mapper, a mapper is used for ecah call to EL
+ * evaluator, thus avoiding the prefix overlapping and redefinition
+ * issues.
+ *
+ * @author Kin-man Chung
+ */
+
+public class ELFunctionMapper {
+    private int currFunc = 0;
+    StringBuffer ds;  // Contains codes to initialize the functions mappers.
+    StringBuffer ss;  // Contains declarations of the functions mappers.
+
+    /**
+     * Creates the functions mappers for all EL expressions in the JSP page.
+     *
+     * @param compiler Current compiler, mainly for accessing error dispatcher.
+     * @param page The current compilation unit.
+     */
+    public static void map(Compiler compiler, Node.Nodes page) 
+                throws JasperException {
+
+        ELFunctionMapper map = new ELFunctionMapper();
+        map.ds = new StringBuffer();
+        map.ss = new StringBuffer();
+
+        page.visit(map.new ELFunctionVisitor());
+
+        // Append the declarations to the root node
+        String ds = map.ds.toString();
+        if (ds.length() > 0) {
+            Node root = page.getRoot();
+            new Node.Declaration(map.ss.toString(), null, root);
+            new Node.Declaration("static {\n" + ds + "}\n", null, root);
+        }
+    }
+
+    /**
+     * A visitor for the page.  The places where EL is allowed are scanned
+     * for functions, and if found functions mappers are created.
+     */
+    class ELFunctionVisitor extends Node.Visitor {
+        
+        /**
+         * Use a global name map to facilitate reuse of function maps.
+         * The key used is prefix:function:uri.
+         */
+        private HashMap<String, String> gMap = new HashMap<String, String>();
+
+        public void visit(Node.ParamAction n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            doMap(n.getBeanName());
+            visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            doMap(n.getHeight());
+            doMap(n.getWidth());
+            visitBody(n);
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            doMap(n.getNameAttribute());
+            visitBody(n);
+        }
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            doMap(n.getEL());
+        }
+
+        private void doMap(Node.JspAttribute attr) 
+                throws JasperException {
+            if (attr != null) {
+                doMap(attr.getEL());
+            }
+        }
+
+        /**
+         * Creates function mappers, if needed, from ELNodes
+         */
+        private void doMap(ELNode.Nodes el) 
+                throws JasperException {
+
+            // Only care about functions in ELNode's
+            class Fvisitor extends ELNode.Visitor {
+                ArrayList<ELNode.Function> funcs =
+                    new ArrayList<ELNode.Function>();
+                HashMap<String, String> keyMap = new HashMap<String, String>();
+                public void visit(ELNode.Function n) throws JasperException {
+                    String key = n.getPrefix() + ":" + n.getName();
+                    if (! keyMap.containsKey(key)) {
+                        keyMap.put(key,"");
+                        funcs.add(n);
+                    }
+                }
+            }
+
+            if (el == null) {
+                return;
+            }
+
+            // First locate all unique functions in this EL
+            Fvisitor fv = new Fvisitor();
+            el.visit(fv);
+            ArrayList functions = fv.funcs;
+
+            if (functions.size() == 0) {
+                return;
+            }
+
+            // Reuse a previous map if possible
+            String decName = matchMap(functions);
+            if (decName != null) {
+                el.setMapName(decName);
+                return;
+            }
+        
+            // Generate declaration for the map statically
+            decName = getMapName();
+            ss.append("static private org.apache.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n");
+
+            ds.append("  " + decName + "= ");
+            ds.append("org.apache.jasper.runtime.ProtectedFunctionMapper");
+
+            // Special case if there is only one function in the map
+            String funcMethod = null;
+            if (functions.size() == 1) {
+                funcMethod = ".getMapForFunction";
+            } else {
+                ds.append(".getInstance();\n");
+                funcMethod = "  " + decName + ".mapFunction";
+            }
+
+            // Setup arguments for either getMapForFunction or mapFunction
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = (ELNode.Function)functions.get(i);
+                FunctionInfo funcInfo = f.getFunctionInfo();
+                String key = f.getPrefix()+ ":" + f.getName();
+                ds.append(funcMethod + "(\"" + key + "\", " +
+                        funcInfo.getFunctionClass() + ".class, " +
+                        '\"' + f.getMethodName() + "\", " +
+                        "new Class[] {");
+                String params[] = f.getParameters();
+                for (int k = 0; k < params.length; k++) {
+                    if (k != 0) {
+                        ds.append(", ");
+                    }
+                    int iArray = params[k].indexOf('[');
+                    if (iArray < 0) {
+                        ds.append(params[k] + ".class");
+                    }
+                    else {
+                        String baseType = params[k].substring(0, iArray);
+                        ds.append("java.lang.reflect.Array.newInstance(");
+                        ds.append(baseType);
+                        ds.append(".class,");
+
+                        // Count the number of array dimension
+                        int aCount = 0;
+                        for (int jj = iArray; jj < params[k].length(); jj++ ) {
+                            if (params[k].charAt(jj) == '[') {
+                                aCount++;
+                            }
+                        }
+                        if (aCount == 1) {
+                            ds.append("0).getClass()");
+                        } else {
+                            ds.append("new int[" + aCount + "]).getClass()");
+                        }
+                    }
+                }
+                ds.append("});\n");
+                // Put the current name in the global function map
+                gMap.put(f.getPrefix() + ':' + f.getName() + ':' + f.getUri(),
+                         decName);
+            }
+            el.setMapName(decName);
+        }
+
+        /**
+         * Find the name of the function mapper for an EL.  Reuse a
+         * previously generated one if possible.
+         * @param functions An ArrayList of ELNode.Function instances that
+         *                  represents the functions in an EL
+         * @return A previous generated function mapper name that can be used
+         *         by this EL; null if none found.
+         */
+        private String matchMap(ArrayList functions) {
+
+            String mapName = null;
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = (ELNode.Function)functions.get(i);
+                String temName = (String) gMap.get(f.getPrefix() + ':' +
+                                        f.getName() + ':' + f.getUri());
+                if (temName == null) {
+                    return null;
+                }
+                if (mapName == null) {
+                    mapName = temName;
+                } else if (!temName.equals(mapName)) {
+                    // If not all in the previous match, then no match.
+                    return null;
+                }
+            }
+            return mapName;
+        }
+
+        /*
+         * @return An unique name for a function mapper.
+         */
+        private String getMapName() {
+            return "_jspx_fnmap_" + currFunc++;
+        }
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java
new file mode 100644
index 0000000..3ee629e
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java
@@ -0,0 +1,255 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.jasper.JasperException;
+
+/**
+ * This class defines internal representation for an EL Expression
+ *
+ * It currently only defines functions.  It can be expanded to define
+ * all the components of an EL expression, if need to.
+ *
+ * @author Kin-man Chung
+ */
+
+abstract class ELNode {
+
+    abstract public void accept(Visitor v) throws JasperException;
+
+    /**
+     * Child classes
+     */
+
+
+    /**
+     * Represents an EL expression: anything in ${ and }.
+     */
+    public static class Root extends ELNode {
+
+	private ELNode.Nodes expr;
+    private char type;
+
+	Root(ELNode.Nodes expr, char type) {
+	    this.expr = expr;
+        this.type = type;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public ELNode.Nodes getExpression() {
+	    return expr;
+	}
+
+    public char getType() {
+        return type;
+    }
+    }
+
+    /**
+     * Represents text outside of EL expression.
+     */
+    public static class Text extends ELNode {
+
+	private String text;
+
+	Text(String text) {
+	    this.text = text;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getText() {
+	    return text;
+	}
+    }
+
+    /**
+     * Represents anything in EL expression, other than functions, including
+     * function arguments etc
+     */
+    public static class ELText extends ELNode {
+
+	private String text;
+
+	ELText(String text) {
+	    this.text = text;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getText() {
+	    return text;
+	}
+    }
+
+    /**
+     * Represents a function
+     * Currently only include the prefix and function name, but not its
+     * arguments.
+     */
+    public static class Function extends ELNode {
+
+	private String prefix;
+	private String name;
+	private String uri;
+	private FunctionInfo functionInfo;
+	private String methodName;
+	private String[] parameters;
+
+	Function(String prefix, String name) {
+	    this.prefix = prefix;
+	    this.name = name;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getPrefix() {
+	    return prefix;
+	}
+
+	public String getName() {
+	    return name;
+	}
+
+	public void setUri(String uri) {
+	    this.uri = uri;
+	}
+
+	public String getUri() {
+	    return uri;
+	}
+
+	public void setFunctionInfo(FunctionInfo f) {
+	    this.functionInfo = f;
+	}
+
+	public FunctionInfo getFunctionInfo() {
+	    return functionInfo;
+	}
+
+	public void setMethodName(String methodName) {
+	    this.methodName = methodName;
+	}
+
+	public String getMethodName() {
+	    return methodName;
+	}
+
+	public void setParameters(String[] parameters) {
+	    this.parameters = parameters;
+	}
+
+	public String[] getParameters() {
+	    return parameters;
+	}
+    }
+
+    /**
+     * An ordered list of ELNode.
+     */
+    public static class Nodes {
+
+	/* Name used for creating a map for the functions in this
+	   EL expression, for communication to Generator.
+	 */
+	String mapName = null;	// The function map associated this EL
+	private List<ELNode> list;
+
+	public Nodes() {
+	    list = new ArrayList<ELNode>();
+	}
+
+	public void add(ELNode en) {
+	    list.add(en);
+	}
+
+	/**
+	 * Visit the nodes in the list with the supplied visitor
+	 * @param v The visitor used
+	 */
+	public void visit(Visitor v) throws JasperException {
+	    Iterator<ELNode> iter = list.iterator();
+	    while (iter.hasNext()) {
+	        ELNode n = iter.next();
+	        n.accept(v);
+	    }
+	}
+
+	public Iterator<ELNode> iterator() {
+	    return list.iterator();
+	}
+
+	public boolean isEmpty() {
+	    return list.size() == 0;
+	}
+
+	/**
+	 * @return true if the expression contains a ${...}
+	 */
+	public boolean containsEL() {
+	    Iterator<ELNode> iter = list.iterator();
+	    while (iter.hasNext()) {
+	        ELNode n = iter.next();
+	        if (n instanceof Root) {
+	            return true;
+	        }
+	    }
+	    return false;
+	}
+
+	public void setMapName(String name) {
+	    this.mapName = name;
+	}
+
+	public String getMapName() {
+	    return mapName;
+	}
+    
+    }
+
+    /*
+     * A visitor class for traversing ELNodes
+     */
+    public static class Visitor {
+
+	public void visit(Root n) throws JasperException {
+	    n.getExpression().visit(this);
+	}
+
+	public void visit(Function n) throws JasperException {
+	}
+
+	public void visit(Text n) throws JasperException {
+	}
+
+	public void visit(ELText n) throws JasperException {
+	}
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java
new file mode 100644
index 0000000..e8cba02
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java
@@ -0,0 +1,382 @@
+/*
+ * 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.jasper.compiler;
+
+/**
+ * This class implements a parser for EL expressions.
+ * 
+ * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a
+ * ELNode.Nodes.
+ * 
+ * Currently, it only handles text outside ${..} and functions in ${ ..}.
+ * 
+ * @author Kin-man Chung
+ */
+
+public class ELParser {
+
+    private Token curToken; // current token
+
+    private ELNode.Nodes expr;
+
+    private ELNode.Nodes ELexpr;
+
+    private int index; // Current index of the expression
+
+    private String expression; // The EL expression
+    
+    private char type;
+
+    private boolean escapeBS; // is '\' an escape char in text outside EL?
+
+    private static final String reservedWords[] = { "and", "div", "empty",
+            "eq", "false", "ge", "gt", "instanceof", "le", "lt", "mod", "ne",
+            "not", "null", "or", "true" };
+
+    public ELParser(String expression) {
+        index = 0;
+        this.expression = expression;
+        expr = new ELNode.Nodes();
+    }
+
+    /**
+     * Parse an EL expression
+     * 
+     * @param expression
+     *            The input expression string of the form Char* ('${' Char*
+     *            '}')* Char*
+     * @return Parsed EL expression in ELNode.Nodes
+     */
+    public static ELNode.Nodes parse(String expression) {
+        ELParser parser = new ELParser(expression);
+        while (parser.hasNextChar()) {
+            String text = parser.skipUntilEL();
+            if (text.length() > 0) {
+                parser.expr.add(new ELNode.Text(text));
+            }
+            ELNode.Nodes elexpr = parser.parseEL();
+            if (!elexpr.isEmpty()) {
+                parser.expr.add(new ELNode.Root(elexpr, parser.type));
+            }
+        }
+        return parser.expr;
+    }
+
+    /**
+     * Parse an EL expression string '${...}'
+     * 
+     * @return An ELNode.Nodes representing the EL expression TODO: Currently
+     *         only parsed into functions and text strings. This should be
+     *         rewritten for a full parser.
+     */
+    private ELNode.Nodes parseEL() {
+
+        StringBuffer buf = new StringBuffer();
+        ELexpr = new ELNode.Nodes();
+        while (hasNext()) {
+            curToken = nextToken();
+            if (curToken instanceof Char) {
+                if (curToken.toChar() == '}') {
+                    break;
+                }
+                buf.append(curToken.toChar());
+            } else {
+                // Output whatever is in buffer
+                if (buf.length() > 0) {
+                    ELexpr.add(new ELNode.ELText(buf.toString()));
+                }
+                if (!parseFunction()) {
+                    ELexpr.add(new ELNode.ELText(curToken.toString()));
+                }
+            }
+        }
+        if (buf.length() > 0) {
+            ELexpr.add(new ELNode.ELText(buf.toString()));
+        }
+
+        return ELexpr;
+    }
+
+    /**
+     * Parse for a function FunctionInvokation ::= (identifier ':')? identifier
+     * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse
+     * arguments
+     */
+    private boolean parseFunction() {
+        if (!(curToken instanceof Id) || isELReserved(curToken.toString())) {
+            return false;
+        }
+        String s1 = null; // Function prefix
+        String s2 = curToken.toString(); // Function name
+        int mark = getIndex();
+        if (hasNext()) {
+            Token t = nextToken();
+            if (t.toChar() == ':') {
+                if (hasNext()) {
+                    Token t2 = nextToken();
+                    if (t2 instanceof Id) {
+                        s1 = s2;
+                        s2 = t2.toString();
+                        if (hasNext()) {
+                            t = nextToken();
+                        }
+                    }
+                }
+            }
+            if (t.toChar() == '(') {
+                ELexpr.add(new ELNode.Function(s1, s2));
+                return true;
+            }
+        }
+        setIndex(mark);
+        return false;
+    }
+
+    /**
+     * Test if an id is a reserved word in EL
+     */
+    private boolean isELReserved(String id) {
+        int i = 0;
+        int j = reservedWords.length;
+        while (i < j) {
+            int k = (i + j) / 2;
+            int result = reservedWords[k].compareTo(id);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k + 1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Skip until an EL expression ('${' || '#{') is reached, allowing escape
+     * sequences '\\' and '\$' and '\#'.
+     * 
+     * @return The text string up to the EL expression
+     */
+    private String skipUntilEL() {
+        char prev = 0;
+        StringBuffer buf = new StringBuffer();
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (prev == '\\') {
+                prev = 0;
+                if (ch == '\\') {
+                    buf.append('\\');
+                    if (!escapeBS)
+                        prev = '\\';
+                } else if (ch == '$' || ch == '#') {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (prev == '$' || prev == '#') {
+                if (ch == '{') {
+                    this.type = prev;
+                    prev = 0;
+                    break;
+                }
+                buf.append(prev);
+                prev = 0;
+            }
+            if (ch == '\\' || ch == '$' || ch == '#') {
+                prev = ch;
+            } else {
+                buf.append(ch);
+            }
+        }
+        if (prev != 0) {
+            buf.append(prev);
+        }
+        return buf.toString();
+    }
+
+    /*
+     * @return true if there is something left in EL expression buffer other
+     * than white spaces.
+     */
+    private boolean hasNext() {
+        skipSpaces();
+        return hasNextChar();
+    }
+
+    /*
+     * @return The next token in the EL expression buffer.
+     */
+    private Token nextToken() {
+        skipSpaces();
+        if (hasNextChar()) {
+            char ch = nextChar();
+            if (Character.isJavaIdentifierStart(ch)) {
+                StringBuffer buf = new StringBuffer();
+                buf.append(ch);
+                while ((ch = peekChar()) != -1
+                        && Character.isJavaIdentifierPart(ch)) {
+                    buf.append(ch);
+                    nextChar();
+                }
+                return new Id(buf.toString());
+            }
+
+            if (ch == '\'' || ch == '"') {
+                return parseQuotedChars(ch);
+            } else {
+                // For now...
+                return new Char(ch);
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Parse a string in single or double quotes, allowing for escape sequences
+     * '\\', and ('\"', or "\'")
+     */
+    private Token parseQuotedChars(char quote) {
+        StringBuffer buf = new StringBuffer();
+        buf.append(quote);
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (ch == '\\') {
+                ch = nextChar();
+                if (ch == '\\' || ch == quote) {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (ch == quote) {
+                buf.append(ch);
+                break;
+            } else {
+                buf.append(ch);
+            }
+        }
+        return new QuotedString(buf.toString());
+    }
+
+    /*
+     * A collection of low level parse methods dealing with character in the EL
+     * expression buffer.
+     */
+
+    private void skipSpaces() {
+        while (hasNextChar()) {
+            if (expression.charAt(index) > ' ')
+                break;
+            index++;
+        }
+    }
+
+    private boolean hasNextChar() {
+        return index < expression.length();
+    }
+
+    private char nextChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index++);
+    }
+
+    private char peekChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index);
+    }
+
+    private int getIndex() {
+        return index;
+    }
+
+    private void setIndex(int i) {
+        index = i;
+    }
+
+    /*
+     * Represents a token in EL expression string
+     */
+    private static class Token {
+
+        char toChar() {
+            return 0;
+        }
+
+        public String toString() {
+            return "";
+        }
+    }
+
+    /*
+     * Represents an ID token in EL
+     */
+    private static class Id extends Token {
+        String id;
+
+        Id(String id) {
+            this.id = id;
+        }
+
+        public String toString() {
+            return id;
+        }
+    }
+
+    /*
+     * Represents a character token in EL
+     */
+    private static class Char extends Token {
+
+        private char ch;
+
+        Char(char ch) {
+            this.ch = ch;
+        }
+
+        char toChar() {
+            return ch;
+        }
+
+        public String toString() {
+            return (new Character(ch)).toString();
+        }
+    }
+
+    /*
+     * Represents a quoted (single or double) string token in EL
+     */
+    private static class QuotedString extends Token {
+
+        private String value;
+
+        QuotedString(String v) {
+            this.value = v;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+
+    public char getType() {
+        return type;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java
new file mode 100644
index 0000000..b4872c4
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java
@@ -0,0 +1,615 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.SAXException;
+
+/**
+ * Class responsible for dispatching JSP parse and javac compilation errors
+ * to the configured error handler.
+ *
+ * This class is also responsible for localizing any error codes before they
+ * are passed on to the configured error handler.
+ * 
+ * In the case of a Java compilation error, the compiler error message is
+ * parsed into an array of JavacErrorDetail instances, which is passed on to 
+ * the configured error handler.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public class ErrorDispatcher {
+
+    // Custom error handler
+    private ErrorHandler errHandler;
+
+    // Indicates whether the compilation was initiated by JspServlet or JspC
+    private boolean jspcMode = false;
+
+
+    /*
+     * Constructor.
+     *
+     * @param jspcMode true if compilation has been initiated by JspC, false
+     * otherwise
+     */
+    public ErrorDispatcher(boolean jspcMode) {
+	// XXX check web.xml for custom error handler
+	errHandler = new DefaultErrorHandler();
+        this.jspcMode = jspcMode;
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     */
+    public void jspError(String errCode) throws JasperException {
+	dispatch(null, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     */
+    public void jspError(Mark where, String errCode) throws JasperException {
+	dispatch(where, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     */
+    public void jspError(Node n, String errCode) throws JasperException {
+	dispatch(n.getStart(), errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg) throws JasperException {
+	dispatch(null, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg)
+	        throws JasperException {
+	dispatch(where, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Node n, String errCode, String arg)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2, String arg3)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(where, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Mark where, String errCode, String arg1, String arg2,
+                         String arg3)
+                throws JasperException {
+        dispatch(where, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2,
+                         String arg3)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given parsing exception to the configured error handler.
+     *
+     * @param e Parsing exception
+     */
+    public void jspError(Exception e) throws JasperException {
+	dispatch(null, null, null, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(String errCode, String arg, Exception e)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg}, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(Node n, String errCode, String arg, Exception e)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg}, e);
+    }
+
+    /**
+     * Parses the given error message into an array of javac compilation error
+     * messages (one per javac compilation error line number).
+     *
+     * @param errMsg Error message
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of javac compilation errors, or null if the given error
+     * message does not contain any compilation error line numbers
+     */
+    public static JavacErrorDetail[] parseJavacErrors(String errMsg,
+                                                      String fname,
+                                                      Node.Nodes page)
+            throws JasperException, IOException {
+
+	return parseJavacMessage(errMsg, fname, page);
+    }
+
+    /*
+     * Dispatches the given javac compilation errors to the configured error
+     * handler.
+     *
+     * @param javacErrors Array of javac compilation errors
+     */
+    public void javacError(JavacErrorDetail[] javacErrors)
+            throws JasperException {
+
+        errHandler.javacError(javacErrors);
+    }
+
+
+    /*
+     * Dispatches the given compilation error report and exception to the
+     * configured error handler.
+     *
+     * @param errorReport Compilation error report
+     * @param e Compilation exception
+     */
+    public void javacError(String errorReport, Exception e)
+                throws JasperException {
+
+        errHandler.javacError(errorReport, e);
+    }
+
+
+    //*********************************************************************
+    // Private utility methods
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param args Arguments for parametric replacement
+     * @param e Parsing exception
+     */
+    private void dispatch(Mark where, String errCode, Object[] args,
+			  Exception e) throws JasperException {
+	String file = null;
+	String errMsg = null;
+	int line = -1;
+	int column = -1;
+	boolean hasLocation = false;
+
+	// Localize
+	if (errCode != null) {
+	    errMsg = Localizer.getMessage(errCode, args);
+	} else if (e != null) {
+	    // give a hint about what's wrong
+	    errMsg = e.getMessage();
+	}
+
+	// Get error location
+	if (where != null) {
+            if (jspcMode) {
+                // Get the full URL of the resource that caused the error
+                try {
+                    file = where.getURL().toString();
+                } catch (MalformedURLException me) {
+                    // Fallback to using context-relative path
+                    file = where.getFile();
+                }
+            } else {
+                // Get the context-relative resource path, so as to not
+                // disclose any local filesystem details
+                file = where.getFile();
+            }
+	    line = where.getLineNumber();
+	    column = where.getColumnNumber();
+	    hasLocation = true;
+	}
+
+	// Get nested exception
+	Exception nestedEx = e;
+	if ((e instanceof SAXException)
+	        && (((SAXException) e).getException() != null)) {
+	    nestedEx = ((SAXException) e).getException();
+	}
+
+	if (hasLocation) {
+	    errHandler.jspError(file, line, column, errMsg, nestedEx);
+	} else {
+	    errHandler.jspError(errMsg, nestedEx);
+	}
+    }
+
+    /*
+     * Parses the given Java compilation error message, which may contain one
+     * or more compilation errors, into an array of JavacErrorDetail instances.
+     *
+     * Each JavacErrorDetail instance contains the information about a single
+     * compilation error.
+     *
+     * @param errMsg Compilation error message that was generated by the
+     * javac compiler
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    private static JavacErrorDetail[] parseJavacMessage(
+                                String errMsg, String fname, Node.Nodes page)
+	        throws IOException, JasperException {
+
+        ArrayList<JavacErrorDetail> errors = new ArrayList<JavacErrorDetail>();
+        StringBuffer errMsgBuf = null;
+        int lineNum = -1;
+        JavacErrorDetail javacError = null;
+        
+        BufferedReader reader = new BufferedReader(new StringReader(errMsg));
+        
+        /*
+         * Parse compilation errors. Each compilation error consists of a file
+         * path and error line number, followed by a number of lines describing
+         * the error.
+         */
+        String line = null;
+        while ((line = reader.readLine()) != null) {
+            
+            /*
+             * Error line number is delimited by set of colons.
+             * Ignore colon following drive letter on Windows (fromIndex = 2).
+             * XXX Handle deprecation warnings that don't have line info
+             */
+            int beginColon = line.indexOf(':', 2); 
+            int endColon = line.indexOf(':', beginColon + 1);
+            if ((beginColon >= 0) && (endColon >= 0)) {
+                if (javacError != null) {
+                    // add previous error to error vector
+                    errors.add(javacError);
+                }
+                
+                String lineNumStr = line.substring(beginColon + 1, endColon);
+                try {
+                    lineNum = Integer.parseInt(lineNumStr);
+                } catch (NumberFormatException e) {
+                    lineNum = -1;
+                }
+                
+                errMsgBuf = new StringBuffer();
+                
+                javacError = createJavacError(fname, page, errMsgBuf, lineNum);
+            }
+            
+            // Ignore messages preceding first error
+            if (errMsgBuf != null) {
+                errMsgBuf.append(line);
+                errMsgBuf.append("\n");
+            }
+        }
+        
+        // Add last error to error vector
+        if (javacError != null) {
+            errors.add(javacError);
+        } 
+        
+        reader.close();
+        
+        JavacErrorDetail[] errDetails = null;
+        if (errors.size() > 0) {
+            errDetails = new JavacErrorDetail[errors.size()];
+            errors.toArray(errDetails);
+        }
+        
+        return errDetails;
+    }
+
+
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, StringBuffer errMsgBuf, int lineNum)
+    throws JasperException {
+        return createJavacError(fname, page, errMsgBuf, lineNum, null);
+    }
+    
+    
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @param ctxt
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, StringBuffer errMsgBuf, int lineNum,
+            JspCompilationContext ctxt) throws JasperException {
+        JavacErrorDetail javacError;
+        // Attempt to map javac error line number to line in JSP page
+        ErrorVisitor errVisitor = new ErrorVisitor(lineNum);
+        page.visit(errVisitor);
+        Node errNode = errVisitor.getJspSourceNode();
+        if ((errNode != null) && (errNode.getStart() != null)) {
+            // If this is a scriplet node then there is a one to one mapping
+            // between JSP lines and Java lines
+            if (errVisitor.getJspSourceNode() instanceof Node.Scriptlet) {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber() + lineNum -
+                            errVisitor.getJspSourceNode().getBeginJavaLine(),
+                        errMsgBuf,
+                        ctxt);
+            } else {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber(),
+                        errMsgBuf,
+                        ctxt);
+            }
+        } else {
+            /*
+             * javac error line number cannot be mapped to JSP page
+             * line number. For example, this is the case if a 
+             * scriptlet is missing a closing brace, which causes
+             * havoc with the try-catch-finally block that the code
+             * generator places around all generated code: As a result
+             * of this, the javac error line numbers will be outside
+             * the range of begin and end java line numbers that were
+             * generated for the scriptlet, and therefore cannot be
+             * mapped to the start line number of the scriptlet in the
+             * JSP page.
+             * Include just the javac error info in the error detail.
+             */
+            javacError = new JavacErrorDetail(
+                    fname,
+                    lineNum,
+                    errMsgBuf);
+        }
+        return javacError;
+    }
+
+
+    /*
+     * Visitor responsible for mapping a line number in the generated servlet
+     * source code to the corresponding JSP node.
+     */
+    static class ErrorVisitor extends Node.Visitor {
+
+	// Java source line number to be mapped
+	private int lineNum;
+
+	/*
+	 * JSP node whose Java source code range in the generated servlet
+	 * contains the Java source line number to be mapped
+	 */
+	Node found;
+
+	/*
+	 * Constructor.
+	 *
+	 * @param lineNum Source line number in the generated servlet code
+	 */
+	public ErrorVisitor(int lineNum) {
+	    this.lineNum = lineNum;
+	}
+
+	public void doVisit(Node n) throws JasperException {
+	    if ((lineNum >= n.getBeginJavaLine())
+		    && (lineNum < n.getEndJavaLine())) {
+		found = n;
+	    }
+        }
+
+	/*
+	 * Gets the JSP node to which the source line number in the generated
+	 * servlet code was mapped.
+	 *
+	 * @return JSP node to which the source line number in the generated
+	 * servlet code was mapped
+	 */
+	public Node getJspSourceNode() {
+	    return found;
+	}
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java
new file mode 100644
index 0000000..a998eda
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Interface for handling JSP parse and javac compilation errors.
+ * 
+ * An implementation of this interface may be registered with the
+ * ErrorDispatcher by setting the XXX initialization parameter in the JSP
+ * page compiler and execution servlet in Catalina's web.xml file to the
+ * implementation's fully qualified class name.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public interface ErrorHandler {
+
+    /**
+     * Processes the given JSP parse error.
+     *
+     * @param fname Name of the JSP file in which the parse error occurred
+     * @param line Parse error line number
+     * @param column Parse error column number
+     * @param msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String fname, int line, int column, String msg,
+			 Exception exception) throws JasperException;
+
+    /**
+     * Processes the given JSP parse error.
+     *
+     * @param msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String msg, Exception exception)
+	throws JasperException;
+
+    /**
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    public void javacError(JavacErrorDetail[] details)
+	throws JasperException;
+
+    /**
+     * Processes the given javac error report and exception.
+     *
+     * @param errorReport Compilation error report
+     * @param exception Compilation exception
+     */
+    public void javacError(String errorReport, Exception exception)
+        throws JasperException;
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Generator.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Generator.java
new file mode 100644
index 0000000..59c2d27
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Generator.java
@@ -0,0 +1,4199 @@
+/*
+ * 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.jasper.compiler;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.compiler.Node.NamedAttribute;
+import org.apache.jasper.runtime.JspRuntimeLibrary;
+import org.xml.sax.Attributes;
+
+/**
+ * Generate Java source from Nodes
+ * 
+ * @author Anil K. Vijendran
+ * @author Danno Ferrin
+ * @author Mandar Raje
+ * @author Rajiv Mordani
+ * @author Pierre Delisle
+ * 
+ * Tomcat 4.1.x and Tomcat 5:
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ * @author Denis Benoit
+ * 
+ * Tomcat 6.x
+ * @author Jacob Hookom
+ * @author Remy Maucherat
+ */
+
+class Generator {
+
+    private static final Class[] OBJECT_CLASS = { Object.class };
+
+    private static final String VAR_EXPRESSIONFACTORY = 
+        System.getProperty("org.apache.jasper.compiler.Generator.VAR_EXPRESSIONFACTORY", "_el_expressionfactory");
+    private static final String VAR_ANNOTATIONPROCESSOR = 
+        System.getProperty("org.apache.jasper.compiler.Generator.VAR_ANNOTATIONPROCESSOR", "_jsp_annotationprocessor");
+
+    private ServletWriter out;
+
+    private ArrayList methodsBuffered;
+
+    private FragmentHelperClass fragmentHelperClass;
+
+    private ErrorDispatcher err;
+
+    private BeanRepository beanInfo;
+
+    private JspCompilationContext ctxt;
+
+    private boolean isPoolingEnabled;
+
+    private boolean breakAtLF;
+
+    private String jspIdPrefix;
+
+    private int jspId;
+
+    private PageInfo pageInfo;
+
+    private Vector<String> tagHandlerPoolNames;
+
+    private GenBuffer charArrayBuffer;
+
+    /**
+     * @param s
+     *            the input string
+     * @return quoted and escaped string, per Java rule
+     */
+    static String quote(String s) {
+
+        if (s == null)
+            return "null";
+
+        return '"' + escape(s) + '"';
+    }
+
+    /**
+     * @param s
+     *            the input string
+     * @return escaped string, per Java rule
+     */
+    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();
+    }
+
+    /**
+     * Single quote and escape a character
+     */
+    static String quote(char c) {
+
+        StringBuffer b = new StringBuffer();
+        b.append('\'');
+        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);
+        b.append('\'');
+        return b.toString();
+    }
+
+    private String createJspId() throws JasperException {
+        if (this.jspIdPrefix == null) {
+            StringBuffer sb = new StringBuffer(32);
+            String name = ctxt.getServletJavaFileName();
+            sb.append("jsp_").append(Math.abs(name.hashCode())).append('_');
+            this.jspIdPrefix = sb.toString();
+        }
+        return this.jspIdPrefix + (this.jspId++);
+    }
+
+    /**
+     * 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 {
+
+            private boolean getServletInfoGenerated = false;
+
+            /*
+             * Generates getServletInfo() method that returns the value of the
+             * page directive's 'info' attribute, if present.
+             * 
+             * The Validator has already ensured that if the translation unit
+             * contains more than one page directive with an 'info' attribute,
+             * their values match.
+             */
+            public void visit(Node.PageDirective n) throws JasperException {
+
+                if (getServletInfoGenerated) {
+                    return;
+                }
+
+                String info = n.getAttributeValue("info");
+                if (info == null)
+                    return;
+
+                getServletInfoGenerated = true;
+                out.printil("public String getServletInfo() {");
+                out.pushIndent();
+                out.printin("return ");
+                out.print(quote(info));
+                out.println(";");
+                out.popIndent();
+                out.printil("}");
+                out.println();
+            }
+
+            public void visit(Node.Declaration n) throws JasperException {
+                n.setBeginJavaLine(out.getJavaLine());
+                out.printMultiLn(new String(n.getText()));
+                out.println();
+                n.setEndJavaLine(out.getJavaLine());
+            }
+
+            // Custom Tags may contain declarations from tag plugins.
+            public void visit(Node.CustomTag n) throws JasperException {
+                if (n.useTagPlugin()) {
+                    if (n.getAtSTag() != null) {
+                        n.getAtSTag().visit(this);
+                    }
+                    visitBody(n);
+                    if (n.getAtETag() != null) {
+                        n.getAtETag().visit(this);
+                    }
+                } else {
+                    visitBody(n);
+                }
+            }
+        }
+
+        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 {
+
+                if (!n.implementsSimpleTag()) {
+                    String name = createTagHandlerPoolName(n.getPrefix(), n
+                            .getLocalName(), n.getAttributes(), 
+                            n.getNamedAttributeNodes(), n.hasEmptyBody());
+                    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, Node.Nodes namedAttrs,
+                    boolean hasEmptyBody) {
+                String poolName = null;
+
+                poolName = "_jspx_tagPool_" + prefix + "_" + shortName;
+                if (attrs != null) {
+                    String[] attrNames =
+                        new String[attrs.getLength() + namedAttrs.size()];
+                    for (int i = 0; i < attrNames.length; i++) {
+                        attrNames[i] = attrs.getQName(i);
+                    }
+                    for (int i = 0; i < namedAttrs.size(); i++) {
+                        attrNames[attrs.getLength() + i] =
+                            ((NamedAttribute) namedAttrs.getNode(i)).getQName();
+                    }
+                    Arrays.sort(attrNames, Collections.reverseOrder());
+                    if (attrNames.length > 0) {
+                        poolName = poolName + "&";
+                    }
+                    for (int i = 0; i < attrNames.length; i++) {
+                        poolName = poolName + "_" + attrNames[i];
+                    }
+                }
+                if (hasEmptyBody) {
+                    poolName = poolName + "_nobody";
+                }
+                return JspUtil.makeJavaIdentifier(poolName);
+            }
+        }
+
+        page.visit(new TagHandlerPoolVisitor(tagHandlerPoolNames));
+    }
+
+    private void declareTemporaryScriptingVars(Node.Nodes page)
+            throws JasperException {
+
+        class ScriptingVarVisitor extends Node.Visitor {
+
+            private Vector vars;
+
+            ScriptingVarVisitor() {
+                vars = new Vector();
+            }
+
+            public void visit(Node.CustomTag n) throws JasperException {
+
+                if (n.getCustomNestingLevel() > 0) {
+                    TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+                    VariableInfo[] varInfos = n.getVariableInfos();
+
+                    if (varInfos.length > 0) {
+                        for (int i = 0; i < varInfos.length; i++) {
+                            String varName = varInfos[i].getVarName();
+                            String tmpVarName = "_jspx_" + varName + "_"
+                                    + n.getCustomNestingLevel();
+                            if (!vars.contains(tmpVarName)) {
+                                vars.add(tmpVarName);
+                                out.printin(varInfos[i].getClassName());
+                                out.print(" ");
+                                out.print(tmpVarName);
+                                out.print(" = ");
+                                out.print(null);
+                                out.println(";");
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i < tagVarInfos.length; i++) {
+                            String varName = tagVarInfos[i].getNameGiven();
+                            if (varName == null) {
+                                varName = n.getTagData().getAttributeString(
+                                        tagVarInfos[i].getNameFromAttribute());
+                            } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                                // alias
+                                continue;
+                            }
+                            String tmpVarName = "_jspx_" + varName + "_"
+                                    + n.getCustomNestingLevel();
+                            if (!vars.contains(tmpVarName)) {
+                                vars.add(tmpVarName);
+                                out.printin(tagVarInfos[i].getClassName());
+                                out.print(" ");
+                                out.print(tmpVarName);
+                                out.print(" = ");
+                                out.print(null);
+                                out.println(";");
+                            }
+                        }
+                    }
+                }
+
+                visitBody(n);
+            }
+        }
+
+        page.visit(new ScriptingVarVisitor());
+    }
+
+    /**
+     * Generates the _jspInit() method for instantiating the tag handler pools.
+     * For tag file, _jspInit has to be invoked manually, and the ServletConfig
+     * object explicitly passed.
+     * 
+     * In JSP 2.1, we also instantiate an ExpressionFactory
+     */
+    private void generateInit() {
+
+        if (ctxt.isTagFile()) {
+            out.printil("private void _jspInit(ServletConfig config) {");
+        } else {
+            out.printil("public void _jspInit() {");
+        }
+
+        out.pushIndent();
+        if (isPoolingEnabled) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                out.printin(tagHandlerPoolNames.elementAt(i));
+                out.print(" = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(");
+                if (ctxt.isTagFile()) {
+                    out.print("config");
+                } else {
+                    out.print("getServletConfig()");
+                }
+                out.println(");");
+            }
+        }
+        
+        out.printin(VAR_EXPRESSIONFACTORY);
+        out.print(" = _jspxFactory.getJspApplicationContext(");
+        if (ctxt.isTagFile()) {
+            out.print("config");
+        } else {
+            out.print("getServletConfig()");
+        }
+        out.println(".getServletContext()).getExpressionFactory();");
+
+        out.printin(VAR_ANNOTATIONPROCESSOR);
+        out.print(" = (org.apache.AnnotationProcessor) ");
+        if (ctxt.isTagFile()) {
+            out.print("config");
+        } else {
+            out.print("getServletConfig()");
+        }
+        out.println(".getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());");
+
+        out.popIndent();
+        out.printil("}");
+        out.println();
+    }
+
+    /**
+     * Generates the _jspDestroy() method which is responsible for calling the
+     * release() method on every tag handler in any of the tag handler pools.
+     */
+    private void generateDestroy() {
+
+        out.printil("public void _jspDestroy() {");
+        out.pushIndent();
+        
+        if (isPoolingEnabled) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                                out.printin((String) tagHandlerPoolNames.elementAt(i));
+                                out.println(".release();");
+            }
+        }
+        
+        out.popIndent();
+        out.printil("}");
+        out.println();
+    }
+
+    /**
+     * Generate preamble package name (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreamblePackage(String packageName) throws JasperException {
+        if (!"".equals(packageName) && packageName != null) {
+            out.printil("package " + packageName + ";");
+            out.println();
+        }
+    }
+
+    /**
+     * Generate preamble imports (shared by servlet and tag handler preamble
+     * generation)
+     */
+    private void genPreambleImports() throws JasperException {
+        Iterator iter = pageInfo.getImports().iterator();
+        while (iter.hasNext()) {
+            out.printin("import ");
+            out.print((String) iter.next());
+            out.println(";");
+        }
+
+        out.println();
+    }
+
+    /**
+     * Generation of static initializers in preamble. For example, dependant
+     * list, el function map, prefix map. (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreambleStaticInitializers() throws JasperException {
+        out.printil("private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();");
+        out.println();
+
+        // Static data for getDependants()
+        out.printil("private static java.util.List _jspx_dependants;");
+        out.println();
+        List dependants = pageInfo.getDependants();
+        Iterator iter = dependants.iterator();
+        if (!dependants.isEmpty()) {
+            out.printil("static {");
+            out.pushIndent();
+            out.printin("_jspx_dependants = new java.util.ArrayList(");
+            out.print("" + dependants.size());
+            out.println(");");
+            while (iter.hasNext()) {
+                out.printin("_jspx_dependants.add(\"");
+                out.print((String) iter.next());
+                out.println("\");");
+            }
+            out.popIndent();
+            out.printil("}");
+            out.println();
+        }
+    }
+
+    /**
+     * Declare tag handler pools (tags of the same type and with the same
+     * attribute set share the same tag handler pool) (shared by servlet and tag
+     * handler preamble generation)
+     * 
+     * In JSP 2.1, we also scope an instance of ExpressionFactory
+     */
+    private void genPreambleClassVariableDeclarations(String className)
+            throws JasperException {
+        if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                out.printil("private org.apache.jasper.runtime.TagHandlerPool "
+                        + tagHandlerPoolNames.elementAt(i) + ";");
+            }
+            out.println();
+        }
+        out.printin("private javax.el.ExpressionFactory ");
+        out.print(VAR_EXPRESSIONFACTORY);
+        out.println(";");
+        out.printin("private org.apache.AnnotationProcessor ");
+        out.print(VAR_ANNOTATIONPROCESSOR);
+        out.println(";");
+        out.println();
+    }
+
+    /**
+     * Declare general-purpose methods (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreambleMethods() throws JasperException {
+        // Method used to get compile time file dependencies
+        out.printil("public Object getDependants() {");
+        out.pushIndent();
+        out.printil("return _jspx_dependants;");
+        out.popIndent();
+        out.printil("}");
+        out.println();
+        
+        generateInit();
+        generateDestroy();
+    }
+
+    /**
+     * Generates the beginning of the static portion of the servlet.
+     */
+    private void generatePreamble(Node.Nodes page) throws JasperException {
+
+        String servletPackageName = ctxt.getServletPackageName();
+        String servletClassName = ctxt.getServletClassName();
+        String serviceMethodName = Constants.SERVICE_METHOD_NAME;
+
+        // First the package name:
+        genPreamblePackage(servletPackageName);
+
+        // Generate imports
+        genPreambleImports();
+
+        // Generate class declaration
+        out.printin("public final class ");
+        out.print(servletClassName);
+        out.print(" extends ");
+        out.println(pageInfo.getExtends());
+        out.printin("    implements org.apache.jasper.runtime.JspSourceDependent");
+        if (!pageInfo.isThreadSafe()) {
+            out.println(",");
+            out.printin("                 SingleThreadModel");
+        }
+        out.println(" {");
+        out.pushIndent();
+
+        // Class body begins here
+        generateDeclarations(page);
+
+        // Static initializations here
+        genPreambleStaticInitializers();
+
+        // Class variable declarations
+        genPreambleClassVariableDeclarations(servletClassName);
+
+        // Constructor
+        // generateConstructor(className);
+
+        // Methods here
+        genPreambleMethods();
+
+        // Now the service method
+        out.printin("public void ");
+        out.print(serviceMethodName);
+        out.println("(HttpServletRequest request, HttpServletResponse response)");
+        out.println("        throws java.io.IOException, ServletException {");
+
+        out.pushIndent();
+        out.println();
+
+        // Local variable declarations
+        out.printil("PageContext pageContext = null;");
+
+        if (pageInfo.isSession())
+            out.printil("HttpSession session = null;");
+
+        if (pageInfo.isErrorPage()) {
+            out.printil("Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);");
+            out.printil("if (exception != null) {");
+            out.pushIndent();
+            out.printil("response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);");
+            out.popIndent();
+            out.printil("}");
+        }
+
+        out.printil("ServletContext application = null;");
+        out.printil("ServletConfig config = null;");
+        out.printil("JspWriter out = null;");
+        out.printil("Object page = this;");
+
+        out.printil("JspWriter _jspx_out = null;");
+        out.printil("PageContext _jspx_page_context = null;");
+        out.println();
+
+        declareTemporaryScriptingVars(page);
+        out.println();
+
+        out.printil("try {");
+        out.pushIndent();
+
+        out.printin("response.setContentType(");
+        out.print(quote(pageInfo.getContentType()));
+        out.println(");");
+
+        if (ctxt.getOptions().isXpoweredBy()) {
+            out.printil("response.addHeader(\"X-Powered-By\", \"JSP/2.1\");");
+        }
+
+        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("_jspx_page_context = pageContext;");
+
+        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 an XML Prolog, which includes an XML declaration and an XML
+     * doctype declaration.
+     */
+    private void generateXmlProlog(Node.Nodes page) {
+
+        /*
+         * An XML declaration is generated under the following conditions: -
+         * 'omit-xml-declaration' attribute of <jsp:output> action is set to
+         * "no" or "false" - JSP document without a <jsp:root>
+         */
+        String omitXmlDecl = pageInfo.getOmitXmlDecl();
+        if ((omitXmlDecl != null && !JspUtil.booleanValue(omitXmlDecl))
+                || (omitXmlDecl == null && page.getRoot().isXmlSyntax()
+                        && !pageInfo.hasJspRoot() && !ctxt.isTagFile())) {
+            String cType = pageInfo.getContentType();
+            String charSet = cType.substring(cType.indexOf("charset=") + 8);
+            out.printil("out.write(\"<?xml version=\\\"1.0\\\" encoding=\\\""
+                    + charSet + "\\\"?>\\n\");");
+        }
+
+        /*
+         * Output a DOCTYPE declaration if the doctype-root-element appears. If
+         * doctype-public appears: <!DOCTYPE name PUBLIC "doctypePublic"
+         * "doctypeSystem"> else <!DOCTYPE name SYSTEM "doctypeSystem" >
+         */
+
+        String doctypeName = pageInfo.getDoctypeName();
+        if (doctypeName != null) {
+            String doctypePublic = pageInfo.getDoctypePublic();
+            String doctypeSystem = pageInfo.getDoctypeSystem();
+            out.printin("out.write(\"<!DOCTYPE ");
+            out.print(doctypeName);
+            if (doctypePublic == null) {
+                out.print(" SYSTEM \\\"");
+            } else {
+                out.print(" PUBLIC \\\"");
+                out.print(doctypePublic);
+                out.print("\\\" \\\"");
+            }
+            out.print(doctypeSystem);
+            out.println("\\\">\\n\");");
+        }
+    }
+
+    /*
+     * Generates the constructor. (shared by servlet and tag handler preamble
+     * generation)
+     */
+    private void generateConstructor(String className) {
+        out.printil("public " + className + "() {");
+        out.printil("}");
+        out.println();
+    }
+
+    /**
+     * A visitor that generates codes for the elements in the page.
+     */
+    class GenerateVisitor extends Node.Visitor {
+
+        /*
+         * Hashtable containing introspection information on tag handlers:
+         * <key>: tag prefix <value>: hashtable containing introspection on tag
+         * handlers: <key>: tag short name <value>: introspection info of tag
+         * handler for <prefix:shortName> tag
+         */
+        private Hashtable handlerInfos;
+
+        private Hashtable tagVarNumbers;
+
+        private String parent;
+
+        private boolean isSimpleTagParent; // Is parent a SimpleTag?
+
+        private String pushBodyCountVar;
+
+        private String simpleTagHandlerVar;
+
+        private boolean isSimpleTagHandler;
+
+        private boolean isFragment;
+
+        private boolean isTagFile;
+
+        private ServletWriter out;
+
+        private ArrayList methodsBuffered;
+
+        private FragmentHelperClass fragmentHelperClass;
+
+        private int methodNesting;
+
+        private TagInfo tagInfo;
+
+        private ClassLoader loader;
+
+        private int charArrayCount;
+
+        private HashMap textMap;
+
+        /**
+         * Constructor.
+         */
+        public GenerateVisitor(boolean isTagFile, ServletWriter out,
+                ArrayList methodsBuffered,
+                FragmentHelperClass fragmentHelperClass, ClassLoader loader,
+                TagInfo tagInfo) {
+
+            this.isTagFile = isTagFile;
+            this.out = out;
+            this.methodsBuffered = methodsBuffered;
+            this.fragmentHelperClass = fragmentHelperClass;
+            this.loader = loader;
+            this.tagInfo = tagInfo;
+            methodNesting = 0;
+            handlerInfos = new Hashtable();
+            tagVarNumbers = new Hashtable();
+            textMap = new HashMap();
+        }
+
+        /**
+         * Returns an attribute value, optionally URL encoded. If the value is a
+         * runtime expression, the result is the expression itself, as a string.
+         * If the result is an EL expression, we insert a call to the
+         * interpreter. If the result is a Named Attribute we insert the
+         * generated variable name. Otherwise the result is a string literal,
+         * quoted and escaped.
+         * 
+         * @param attr
+         *            An JspAttribute object
+         * @param encode
+         *            true if to be URL encoded
+         * @param expectedType
+         *            the expected type for an EL evaluation (ignored for
+         *            attributes that aren't EL expressions)
+         */
+        private String attributeValue(Node.JspAttribute attr, boolean encode,
+                Class expectedType) {
+            String v = attr.getValue();
+            if (!attr.isNamedAttribute() && (v == null))
+                return "";
+
+            if (attr.isExpression()) {
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(String.valueOf("
+                            + v + "), request.getCharacterEncoding())";
+                }
+                return v;
+            } else if (attr.isELInterpreterInput()) {
+                v = attributeValueWithEL(this.isTagFile, v, expectedType,
+                        attr.getEL().getMapName());
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+                            + v + ", request.getCharacterEncoding())";
+                }
+                return v;
+            } else if (attr.isNamedAttribute()) {
+                return attr.getNamedAttributeNode().getTemporaryVariableName();
+            } else {
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+                            + quote(v) + ", request.getCharacterEncoding())";
+                }
+                return quote(v);
+            }
+        }
+
+
+        /*
+         * When interpreting the EL attribute value, literals outside the EL
+         * must not be unescaped but the EL processor will unescape them.
+         * Therefore, make sure only the EL expressions are processed by the EL
+         * processor.
+         */
+        private String attributeValueWithEL(boolean isTag, String tx,
+                Class<?> expectedType, String mapName) {
+            if (tx==null) return null;
+            Class<?> type = expectedType;
+            int size = tx.length();
+            StringBuffer output = new StringBuffer(size);
+            boolean el = false;
+            int i = 0;
+            int mark = 0;
+            char ch;
+            
+            while(i < size){
+                ch = tx.charAt(i);
+                
+                // Start of an EL expression
+                if (!el && i+1 < size && ch == '$' && tx.charAt(i+1)=='{') {
+                    if (mark < i) {
+                        if (output.length() > 0) {
+                            output.append(" + ");
+                            // Composite expression - must coerce to String
+                            type = String.class;
+                        }
+                        output.append(quote(tx.substring(mark, i)));
+                    }
+                    mark = i;
+                    el = true;
+                    i += 2;
+                } else if (ch=='\\' && i+1 < size &&
+                        (tx.charAt(i+1)=='$' || tx.charAt(i+1)=='}')) { 
+                    // Skip an escaped $ or }
+                    i += 2;
+                } else if (el && ch=='}') {
+                    // End of an EL expression
+                    if (output.length() > 0) {
+                        output.append(" + ");
+                        // Composite expression - must coerce to String
+                        type = String.class;
+                    }
+                    output.append(
+                            JspUtil.interpreterCall(isTag,
+                                    tx.substring(mark, i+1), type,
+                                    mapName, false));
+                    mark = i + 1;
+                    el = false;
+                    ++i;
+                } else {
+                    // Nothing to see here - move to next character
+                    ++i;
+                }
+            }
+            if (!el && mark < i) {
+                if (output.length() > 0) {
+                    output.append(" + ");
+                }
+                output.append(quote(tx.substring(mark, i)));
+            }
+            return output.toString();
+        }
+
+
+        /**
+         * 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, String pageParam, boolean literal)
+                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("org.apache.jasper.runtime.JspRuntimeLibrary."
+                            + "URLEncode(" + quote(n.getTextAttribute("name"))
+                            + ", request.getCharacterEncoding())");
+                    out.print("+ \"=\" + ");
+                    out.print(attributeValue(n.getValue(), true, String.class));
+
+                    // The separator is '&' after the second use
+                    separator = "\"&\"";
+                }
+            }
+
+            String sep;
+            if (literal) {
+                sep = pageParam.indexOf('?') > 0 ? "\"&\"" : "\"?\"";
+            } else {
+                sep = "((" + pageParam + ").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.printin("out.print(");
+            out.printMultiLn(n.getText());
+            out.println(");");
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            n.setBeginJavaLine(out.getJavaLine());
+            out.printMultiLn(n.getText());
+            out.println();
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            n.setBeginJavaLine(out.getJavaLine());
+            if (!pageInfo.isELIgnored() && (n.getEL() != null)) {
+                out.printil("out.write("
+                        + JspUtil.interpreterCall(this.isTagFile, n.getType() + "{"
+                                + new String(n.getText()) + "}", String.class,
+                                n.getEL().getMapName(), false) + ");");
+            } else {
+                out.printil("out.write("
+                        + quote(n.getType() + "{" + new String(n.getText()) + "}") + ");");
+            }
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+
+            String flush = n.getTextAttribute("flush");
+            Node.JspAttribute page = n.getPage();
+
+            boolean isFlush = false; // default to false;
+            if ("true".equals(flush))
+                isFlush = true;
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            String pageParam;
+            if (page.isNamedAttribute()) {
+                // If the page for jsp:include was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                pageParam = generateNamedAttributeValue(page
+                        .getNamedAttributeNode());
+            } else {
+                pageParam = attributeValue(page, false, String.class);
+            }
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            Node jspBody = findJspBody(n);
+            if (jspBody != null) {
+                prepareParams(jspBody);
+            } else {
+                prepareParams(n);
+            }
+
+            out
+                    .printin("org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "
+                            + pageParam);
+            printParams(n, pageParam, page.isLiteral());
+            out.println(", out, " + isFlush + ");");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        /**
+         * Scans through all child nodes of the given parent for <param>
+         * subelements. For each <param> element, if its value is specified via
+         * a Named Attribute (<jsp:attribute>), generate the code to evaluate
+         * those bodies first.
+         * <p>
+         * If parent is null, simply returns.
+         */
+        private void prepareParams(Node parent) throws JasperException {
+            if (parent == null)
+                return;
+
+            Node.Nodes subelements = parent.getBody();
+            if (subelements != null) {
+                for (int i = 0; i < subelements.size(); i++) {
+                    Node n = subelements.getNode(i);
+                    if (n instanceof Node.ParamAction) {
+                        Node.Nodes paramSubElements = n.getBody();
+                        for (int j = 0; (paramSubElements != null)
+                                && (j < paramSubElements.size()); j++) {
+                            Node m = paramSubElements.getNode(j);
+                            if (m instanceof Node.NamedAttribute) {
+                                generateNamedAttributeValue((Node.NamedAttribute) m);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Finds the <jsp:body> subelement of the given parent node. If not
+         * found, null is returned.
+         */
+        private Node.JspBody findJspBody(Node parent) throws JasperException {
+            Node.JspBody result = null;
+
+            Node.Nodes subelements = parent.getBody();
+            for (int i = 0; (subelements != null) && (i < subelements.size()); i++) {
+                Node n = subelements.getNode(i);
+                if (n instanceof Node.JspBody) {
+                    result = (Node.JspBody) n;
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            Node.JspAttribute page = n.getPage();
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            out.printil("if (true) {"); // So that javac won't complain about
+            out.pushIndent(); // codes after "return"
+
+            String pageParam;
+            if (page.isNamedAttribute()) {
+                // If the page for jsp:forward was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                pageParam = generateNamedAttributeValue(page
+                        .getNamedAttributeNode());
+            } else {
+                pageParam = attributeValue(page, false, String.class);
+            }
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            Node jspBody = findJspBody(n);
+            if (jspBody != null) {
+                prepareParams(jspBody);
+            } else {
+                prepareParams(n);
+            }
+
+            out.printin("_jspx_page_context.forward(");
+            out.print(pageParam);
+            printParams(n, pageParam, page.isLiteral());
+            out.println(");");
+            if (isTagFile || isFragment) {
+                out.printil("throw new SkipPageException();");
+            } else {
+                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.getTextAttribute("name");
+            String property = n.getTextAttribute("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 = JspUtil.getCanonicalName(bean);
+                java.lang.reflect.Method meth = JspRuntimeLibrary
+                        .getReadMethod(bean, property);
+                String methodName = meth.getName();
+                out
+                        .printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString("
+                                + "((("
+                                + beanName
+                                + ")_jspx_page_context.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.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString"
+                                + "(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty"
+                                + "(_jspx_page_context.getAttribute(\""
+                                + name
+                                + "\", PageContext.PAGE_SCOPE), \""
+                                + property
+                                + "\")));");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            String name = n.getTextAttribute("name");
+            String property = n.getTextAttribute("property");
+            String param = n.getTextAttribute("param");
+            Node.JspAttribute value = n.getValue();
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            if ("*".equals(property)) {
+                out
+                        .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspect("
+                                + "_jspx_page_context.findAttribute("
+                                + "\""
+                                + name + "\"), request);");
+            } else if (value == null) {
+                if (param == null)
+                    param = property; // default to same as property
+                out
+                        .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                                + "_jspx_page_context.findAttribute(\""
+                                + name
+                                + "\"), \""
+                                + property
+                                + "\", request.getParameter(\""
+                                + param
+                                + "\"), "
+                                + "request, \""
+                                + param
+                                + "\", false);");
+            } else if (value.isExpression()) {
+                out
+                        .printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetProperty("
+                                + "_jspx_page_context.findAttribute(\""
+                                + name
+                                + "\"), \"" + property + "\",");
+                out.print(attributeValue(value, false, null));
+                out.println(");");
+            } else if (value.isELInterpreterInput()) {
+                // We've got to resolve the very call to the interpreter
+                // at runtime since we don't know what type to expect
+                // in the general case; we thus can't hard-wire the call
+                // into the generated code. (XXX We could, however,
+                // optimize the case where the bean is exposed with
+                // <jsp:useBean>, much as the code here does for
+                // getProperty.)
+
+                // The following holds true for the arguments passed to
+                // JspRuntimeLibrary.handleSetPropertyExpression():
+                // - 'pageContext' is a VariableResolver.
+                // - 'this' (either the generated Servlet or the generated tag
+                // handler for Tag files) is a FunctionMapper.
+                out
+                        .printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetPropertyExpression("
+                                + "_jspx_page_context.findAttribute(\""
+                                + name
+                                + "\"), \""
+                                + property
+                                + "\", "
+                                + quote(value.getValue())
+                                + ", "
+                                + "_jspx_page_context, "
+                                + value.getEL().getMapName() + ");");
+            } else if (value.isNamedAttribute()) {
+                // If the value for setProperty was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                String valueVarName = generateNamedAttributeValue(value
+                        .getNamedAttributeNode());
+                out
+                        .printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                                + "_jspx_page_context.findAttribute(\""
+                                + name
+                                + "\"), \""
+                                + property
+                                + "\", "
+                                + valueVarName
+                                + ", null, null, false);");
+            } else {
+                out
+                        .printin("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                                + "_jspx_page_context.findAttribute(\""
+                                + name
+                                + "\"), \"" + property + "\", ");
+                out.print(attributeValue(value, false, null));
+                out.println(", null, null, false);");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+
+            String name = n.getTextAttribute("id");
+            String scope = n.getTextAttribute("scope");
+            String klass = n.getTextAttribute("class");
+            String type = n.getTextAttribute("type");
+            Node.JspAttribute beanName = n.getBeanName();
+
+            // If "class" is specified, try an instantiation at compile time
+            boolean generateNew = false;
+            String canonicalName = null; // Canonical name for klass
+            if (klass != null) {
+                try {
+                    Class bean = ctxt.getClassLoader().loadClass(klass);
+                    if (klass.indexOf('$') >= 0) {
+                        // Obtain the canonical type name
+                        canonicalName = JspUtil.getCanonicalName(bean);
+                    } else {
+                        canonicalName = klass;
+                    }
+                    int modifiers = bean.getModifiers();
+                    if (!Modifier.isPublic(modifiers)
+                            || Modifier.isInterface(modifiers)
+                            || Modifier.isAbstract(modifiers)) {
+                        throw new Exception("Invalid bean class modifier");
+                    }
+                    // Check that there is a 0 arg constructor
+                    bean.getConstructor(new Class[] {});
+                    // At compile time, we have determined that the bean class
+                    // exists, with a public zero constructor, new() can be
+                    // used for bean instantiation.
+                    generateNew = true;
+                } catch (Exception e) {
+                    // Cannot instantiate the specified class, either a
+                    // compilation error or a runtime error will be raised,
+                    // depending on a compiler flag.
+                    if (ctxt.getOptions()
+                            .getErrorOnUseBeanInvalidClassAttribute()) {
+                        err.jspError(n, "jsp.error.invalid.bean", klass);
+                    }
+                    if (canonicalName == null) {
+                        // Doing our best here to get a canonical name
+                        // from the binary name, should work 99.99% of time.
+                        canonicalName = klass.replace('$', '.');
+                    }
+                }
+                if (type == null) {
+                    // if type is unspecified, use "class" as type of bean
+                    type = canonicalName;
+                }
+            }
+
+            String scopename = "PageContext.PAGE_SCOPE"; // Default to page
+            String lock = "_jspx_page_context";
+
+            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(") _jspx_page_context.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 the bean if it is not in the specified scope.
+                 */
+                if (!generateNew) {
+                    String binaryName;
+                    if (beanName != null) {
+                        if (beanName.isNamedAttribute()) {
+                            // If the value for beanName was specified via
+                            // jsp:attribute, first generate code to evaluate
+                            // that body.
+                            binaryName = generateNamedAttributeValue(beanName
+                                    .getNamedAttributeNode());
+                        } else {
+                            binaryName = attributeValue(beanName, false,
+                                    String.class);
+                        }
+                    } else {
+                        // Implies klass is not null
+                        binaryName = 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(binaryName);
+                    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(binaryName);
+                    out.println(", exc);");
+                    out.popIndent();
+                    out.printil("}"); // close of try
+                } else {
+                    // Implies klass is not null
+                    // Generate codes to instantiate the bean class
+                    out.printin(name);
+                    out.print(" = new ");
+                    out.print(canonicalName);
+                    out.println("();");
+                }
+                /*
+                 * Set attribute for bean in the specified scope
+                 */
+                out.printin("_jspx_page_context.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 <jsp:param> 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.getTextAttribute("name");
+                    if (name.equalsIgnoreCase("object"))
+                        name = "java_object";
+                    else if (name.equalsIgnoreCase("type"))
+                        name = "java_type";
+
+                    n.setBeginJavaLine(out.getJavaLine());
+                    // XXX - Fixed a bug here - value used to be output
+                    // inline, which is only okay if value is not an EL
+                    // expression. Also, key/value pairs for the
+                    // embed tag were not being generated correctly.
+                    // Double check that this is now the correct behavior.
+                    if (ie) {
+                        // We want something of the form
+                        // out.println( "<param name=\"blah\"
+                        // value=\"" + ... + "\">" );
+                        out.printil("out.write( \"<param name=\\\""
+                                + escape(name)
+                                + "\\\" value=\\\"\" + "
+                                + attributeValue(n.getValue(), false,
+                                        String.class) + " + \"\\\">\" );");
+                        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,
+                                        String.class) + " + \"\\\"\" );");
+                    }
+
+                    n.setEndJavaLine(out.getJavaLine());
+                }
+            }
+
+            String type = n.getTextAttribute("type");
+            String code = n.getTextAttribute("code");
+            String name = n.getTextAttribute("name");
+            Node.JspAttribute height = n.getHeight();
+            Node.JspAttribute width = n.getWidth();
+            String hspace = n.getTextAttribute("hspace");
+            String vspace = n.getTextAttribute("vspace");
+            String align = n.getTextAttribute("align");
+            String iepluginurl = n.getTextAttribute("iepluginurl");
+            String nspluginurl = n.getTextAttribute("nspluginurl");
+            String codebase = n.getTextAttribute("codebase");
+            String archive = n.getTextAttribute("archive");
+            String jreversion = n.getTextAttribute("jreversion");
+
+            String widthStr = null;
+            if (width != null) {
+                if (width.isNamedAttribute()) {
+                    widthStr = generateNamedAttributeValue(width
+                            .getNamedAttributeNode());
+                } else {
+                    widthStr = attributeValue(width, false, String.class);
+                }
+            }
+
+            String heightStr = null;
+            if (height != null) {
+                if (height.isNamedAttribute()) {
+                    heightStr = generateNamedAttributeValue(height
+                            .getNamedAttributeNode());
+                } else {
+                    heightStr = attributeValue(height, false, String.class);
+                }
+            }
+
+            if (iepluginurl == null)
+                iepluginurl = Constants.IE_PLUGIN_URL;
+            if (nspluginurl == null)
+                nspluginurl = Constants.NS_PLUGIN_URL;
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            // Look for a params node and prepare its param subelements:
+            Node.JspBody jspBody = findJspBody(n);
+            if (jspBody != null) {
+                Node.Nodes subelements = jspBody.getBody();
+                if (subelements != null) {
+                    for (int i = 0; i < subelements.size(); i++) {
+                        Node m = subelements.getNode(i);
+                        if (m instanceof Node.ParamsAction) {
+                            prepareParams(m);
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // XXX - Fixed a bug here - width and height can be set
+            // dynamically. Double-check if this generation is correct.
+
+            // IE style plugin
+            // <object ...>
+            // First compose the runtime output string
+            String s0 = "<object"
+                    + makeAttr("classid", ctxt.getOptions().getIeClassId())
+                    + makeAttr("name", name);
+
+            String s1 = "";
+            if (width != null) {
+                s1 = " + \" width=\\\"\" + " + widthStr + " + \"\\\"\"";
+            }
+
+            String s2 = "";
+            if (height != null) {
+                s2 = " + \" height=\\\"\" + " + heightStr + " + \"\\\"\"";
+            }
+
+            String s3 = makeAttr("hspace", hspace) + makeAttr("vspace", vspace)
+                    + makeAttr("align", align)
+                    + makeAttr("codebase", iepluginurl) + '>';
+
+            // Then print the output string to the java file
+            out.printil("out.write(" + quote(s0) + s1 + s2 + " + " + quote(s3)
+                    + ");");
+            out.printil("out.write(\"\\n\");");
+
+            // <param > for java_code
+            s0 = "<param name=\"java_code\"" + makeAttr("value", code) + '>';
+            out.printil("out.write(" + quote(s0) + ");");
+            out.printil("out.write(\"\\n\");");
+
+            // <param > for java_codebase
+            if (codebase != null) {
+                s0 = "<param name=\"java_codebase\""
+                        + makeAttr("value", codebase) + '>';
+                out.printil("out.write(" + quote(s0) + ");");
+                out.printil("out.write(\"\\n\");");
+            }
+
+            // <param > for java_archive
+            if (archive != null) {
+                s0 = "<param name=\"java_archive\""
+                        + makeAttr("value", archive) + '>';
+                out.printil("out.write(" + quote(s0) + ");");
+                out.printil("out.write(\"\\n\");");
+            }
+
+            // <param > for type
+            s0 = "<param name=\"type\""
+                    + makeAttr("value", "application/x-java-"
+                            + type
+                            + ((jreversion == null) ? "" : ";version="
+                                    + jreversion)) + '>';
+            out.printil("out.write(" + quote(s0) + ");");
+            out.printil("out.write(\"\\n\");");
+
+            /*
+             * generate a <param> for each <jsp:param> in the plugin body
+             */
+            if (n.getBody() != null)
+                n.getBody().visit(new ParamVisitor(true));
+
+            /*
+             * Netscape style plugin part
+             */
+            out.printil("out.write(" + quote("<comment>") + ");");
+            out.printil("out.write(\"\\n\");");
+            s0 = "<EMBED"
+                    + makeAttr("type", "application/x-java-"
+                            + type
+                            + ((jreversion == null) ? "" : ";version="
+                                    + jreversion)) + makeAttr("name", name);
+
+            // s1 and s2 are the same as before.
+
+            s3 = makeAttr("hspace", hspace) + makeAttr("vspace", vspace)
+                    + makeAttr("align", align)
+                    + makeAttr("pluginspage", nspluginurl)
+                    + makeAttr("java_code", code)
+                    + makeAttr("java_codebase", codebase)
+                    + makeAttr("java_archive", archive);
+            out.printil("out.write(" + quote(s0) + s1 + s2 + " + " + quote(s3)
+                    + ");");
+
+            /*
+             * Generate a 'attr = "value"' for each <jsp:param> in plugin body
+             */
+            if (n.getBody() != null)
+                n.getBody().visit(new ParamVisitor(false));
+
+            out.printil("out.write(" + quote("/>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("<noembed>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            /*
+             * Fallback
+             */
+            if (n.getBody() != null) {
+                visitBody(n);
+                out.printil("out.write(\"\\n\");");
+            }
+
+            out.printil("out.write(" + quote("</noembed>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("</comment>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("</object>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            // Don't visit body of this tag - we already did earlier.
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+
+            // Use plugin to generate more efficient code if there is one.
+            if (n.useTagPlugin()) {
+                generateTagPlugin(n);
+                return;
+            }
+
+            TagHandlerInfo handlerInfo = getTagHandlerInfo(n);
+
+            // Create variable names
+            String baseVar = createTagVarName(n.getQName(), n.getPrefix(), n
+                    .getLocalName());
+            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;
+            Node.ChildInfo ci = n.getChildInfo();
+            if (ci.isScriptless() && !ci.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.print("_jspx_page_context");
+                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;
+                /*
+                 * For fragments, their bodies will be generated in fragment
+                 * helper classes, and the Java line adjustments will be done
+                 * there, hence they are set to null here to avoid double
+                 * adjustments.
+                 */
+                GenBuffer genBuffer = new GenBuffer(n,
+                        n.implementsSimpleTag() ? null : n.getBody());
+                methodsBuffered.add(genBuffer);
+                out = genBuffer.getOut();
+
+                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.JspTag ");
+                    out.print(parent);
+                    out.print(", ");
+                }
+                out.print("PageContext _jspx_page_context");
+                if (pushBodyCountVar != null) {
+                    out.print(", int[] ");
+                    out.print(pushBodyCountVar);
+                }
+                out.println(")");
+                out.printil("        throws Throwable {");
+                out.pushIndent();
+
+                // Initilaize local variables used in this method.
+                if (!isTagFile) {
+                    out
+                            .printil("PageContext pageContext = _jspx_page_context;");
+                }
+                out.printil("JspWriter out = _jspx_page_context.getOut();");
+                generateLocalVariables(out, n);
+            }
+
+            if (n.implementsSimpleTag()) {
+                generateCustomDoTag(n, handlerInfo, tagHandlerVar);
+            } else {
+                /*
+                 * Classic tag handler: Generate code for start element, body,
+                 * and end element
+                 */
+                generateCustomStart(n, handlerInfo, tagHandlerVar, tagEvalVar,
+                        tagPushBodyCountVar);
+
+                // visit body
+                String tmpParent = parent;
+                parent = tagHandlerVar;
+                boolean isSimpleTagParentSave = isSimpleTagParent;
+                isSimpleTagParent = false;
+                String tmpPushBodyCountVar = null;
+                if (n.implementsTryCatchFinally()) {
+                    tmpPushBodyCountVar = pushBodyCountVar;
+                    pushBodyCountVar = tagPushBodyCountVar;
+                }
+                boolean tmpIsSimpleTagHandler = isSimpleTagHandler;
+                isSimpleTagHandler = false;
+
+                visitBody(n);
+
+                parent = tmpParent;
+                isSimpleTagParent = isSimpleTagParentSave;
+                if (n.implementsTryCatchFinally()) {
+                    pushBodyCountVar = tmpPushBodyCountVar;
+                }
+                isSimpleTagHandler = tmpIsSimpleTagHandler;
+
+                generateCustomEnd(n, tagHandlerVar, tagEvalVar,
+                        tagPushBodyCountVar);
+            }
+
+            if (ci.isScriptless() && !ci.hasScriptingVars()) {
+                // Generate end of method
+                if (methodNesting > 0) {
+                    out.printil("return false;");
+                }
+                out.popIndent();
+                out.printil("}");
+                out.popIndent();
+
+                methodNesting--;
+
+                // restore previous writer
+                out = outSave;
+            }
+        }
+
+        private static final String SINGLE_QUOTE = "'";
+
+        private static final String DOUBLE_QUOTE = "\\\"";
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            /*
+             * Write begin tag
+             */
+            out.printin("out.write(\"<");
+            out.print(n.getQName());
+
+            Attributes attrs = n.getNonTaglibXmlnsAttributes();
+            int attrsLen = (attrs == null) ? 0 : attrs.getLength();
+            for (int i = 0; i < attrsLen; i++) {
+                out.print(" ");
+                out.print(attrs.getQName(i));
+                out.print("=");
+                out.print(DOUBLE_QUOTE);
+                out.print(attrs.getValue(i).replace("\"", "&quot;"));
+                out.print(DOUBLE_QUOTE);
+            }
+
+            attrs = n.getAttributes();
+            attrsLen = (attrs == null) ? 0 : attrs.getLength();
+            Node.JspAttribute[] jspAttrs = n.getJspAttributes();
+            for (int i = 0; i < attrsLen; i++) {
+                out.print(" ");
+                out.print(attrs.getQName(i));
+                out.print("=");
+                if (jspAttrs[i].isELInterpreterInput()) {
+                    out.print("\\\"\" + ");
+                    out.print(attributeValue(jspAttrs[i], false, String.class));
+                    out.print(" + \"\\\"");
+                } else {
+                    out.print(DOUBLE_QUOTE);
+                    out.print(attrs.getValue(i).replace("\"", "&quot;"));
+                    out.print(DOUBLE_QUOTE);
+                }
+            }
+
+            if (n.getBody() != null) {
+                out.println(">\");");
+
+                // Visit tag body
+                visitBody(n);
+
+                /*
+                 * Write end tag
+                 */
+                out.printin("out.write(\"</");
+                out.print(n.getQName());
+                out.println(">\");");
+            } else {
+                out.println("/>\");");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Compute attribute value string for XML-style and named
+            // attributes
+            Hashtable map = new Hashtable();
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                String attrStr = null;
+                if (attrs[i].isNamedAttribute()) {
+                    attrStr = generateNamedAttributeValue(attrs[i]
+                            .getNamedAttributeNode());
+                } else {
+                    attrStr = attributeValue(attrs[i], false, Object.class);
+                }
+                String s = " + \" " + attrs[i].getName() + "=\\\"\" + "
+                        + attrStr + " + \"\\\"\"";
+                map.put(attrs[i].getName(), s);
+            }
+
+            // Write begin tag, using XML-style 'name' attribute as the
+            // element name
+            String elemName = attributeValue(n.getNameAttribute(), false,
+                    String.class);
+            out.printin("out.write(\"<\"");
+            out.print(" + " + elemName);
+
+            // Write remaining attributes
+            Enumeration enumeration = map.keys();
+            while (enumeration.hasMoreElements()) {
+                String attrName = (String) enumeration.nextElement();
+                out.print((String) map.get(attrName));
+            }
+
+            // Does the <jsp:element> have nested tags other than
+            // <jsp:attribute>
+            boolean hasBody = false;
+            Node.Nodes subelements = n.getBody();
+            if (subelements != null) {
+                for (int i = 0; i < subelements.size(); i++) {
+                    Node subelem = subelements.getNode(i);
+                    if (!(subelem instanceof Node.NamedAttribute)) {
+                        hasBody = true;
+                        break;
+                    }
+                }
+            }
+            if (hasBody) {
+                out.println(" + \">\");");
+
+                // Smap should not include the body
+                n.setEndJavaLine(out.getJavaLine());
+
+                // Visit tag body
+                visitBody(n);
+
+                // Write end tag
+                out.printin("out.write(\"</\"");
+                out.print(" + " + elemName);
+                out.println(" + \">\");");
+            } else {
+                out.println(" + \"/>\");");
+                n.setEndJavaLine(out.getJavaLine());
+            }
+        }
+
+        public void visit(Node.TemplateText n) throws JasperException {
+
+            String text = n.getText();
+
+            int textSize = text.length();
+            if (textSize == 0) {
+                return;
+            }
+
+            if (textSize <= 3) {
+                // Special case small text strings
+                n.setBeginJavaLine(out.getJavaLine());
+                int lineInc = 0;
+                for (int i = 0; i < textSize; i++) {
+                    char ch = text.charAt(i);
+                    out.printil("out.write(" + quote(ch) + ");");
+                    if (i > 0) {
+                        n.addSmap(lineInc);
+                    }
+                    if (ch == '\n') {
+                        lineInc++;
+                    }
+                }
+                n.setEndJavaLine(out.getJavaLine());
+                return;
+            }
+
+            if (ctxt.getOptions().genStringAsCharArray()) {
+                // Generate Strings as char arrays, for performance
+                ServletWriter caOut;
+                if (charArrayBuffer == null) {
+                    charArrayBuffer = new GenBuffer();
+                    caOut = charArrayBuffer.getOut();
+                    caOut.pushIndent();
+                    textMap = new HashMap();
+                } else {
+                    caOut = charArrayBuffer.getOut();
+                }
+                String charArrayName = (String) textMap.get(text);
+                if (charArrayName == null) {
+                    charArrayName = "_jspx_char_array_" + charArrayCount++;
+                    textMap.put(text, charArrayName);
+                    caOut.printin("static char[] ");
+                    caOut.print(charArrayName);
+                    caOut.print(" = ");
+                    caOut.print(quote(text));
+                    caOut.println(".toCharArray();");
+                }
+
+                n.setBeginJavaLine(out.getJavaLine());
+                out.printil("out.write(" + charArrayName + ");");
+                n.setEndJavaLine(out.getJavaLine());
+                return;
+            }
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            out.printin();
+            StringBuffer sb = new StringBuffer("out.write(\"");
+            int initLength = sb.length();
+            int count = JspUtil.CHUNKSIZE;
+            int srcLine = 0; // relative to starting srouce line
+            for (int i = 0; i < text.length(); i++) {
+                char ch = text.charAt(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');
+                    srcLine++;
+
+                    if (breakAtLF || count < 0) {
+                        // Generate an out.write() when see a '\n' in template
+                        sb.append("\");");
+                        out.println(sb.toString());
+                        if (i < text.length() - 1) {
+                            out.printin();
+                        }
+                        sb.setLength(initLength);
+                        count = JspUtil.CHUNKSIZE;
+                    }
+                    // add a Smap for this line
+                    n.addSmap(srcLine);
+                    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());
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            if (n.getBody() != null) {
+                if (isSimpleTagHandler) {
+                    out.printin(simpleTagHandlerVar);
+                    out.print(".setJspBody(");
+                    generateJspFragment(n, simpleTagHandlerVar);
+                    out.println(");");
+                } else {
+                    visitBody(n);
+                }
+            }
+        }
+
+        public void visit(Node.InvokeAction n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Copy virtual page scope of tag file to page scope of invoking
+            // page
+            out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();");
+            String varReaderAttr = n.getTextAttribute("varReader");
+            String varAttr = n.getTextAttribute("var");
+            if (varReaderAttr != null || varAttr != null) {
+                out.printil("_jspx_sout = new java.io.StringWriter();");
+            } else {
+                out.printil("_jspx_sout = null;");
+            }
+
+            // Invoke fragment, unless fragment is null
+            out.printin("if (");
+            out.print(toGetterMethod(n.getTextAttribute("fragment")));
+            out.println(" != null) {");
+            out.pushIndent();
+            out.printin(toGetterMethod(n.getTextAttribute("fragment")));
+            out.println(".invoke(_jspx_sout);");
+            out.popIndent();
+            out.printil("}");
+
+            // Store varReader in appropriate scope
+            if (varReaderAttr != null || varAttr != null) {
+                String scopeName = n.getTextAttribute("scope");
+                out.printin("_jspx_page_context.setAttribute(");
+                if (varReaderAttr != null) {
+                    out.print(quote(varReaderAttr));
+                    out.print(", new java.io.StringReader(_jspx_sout.toString())");
+                } else {
+                    out.print(quote(varAttr));
+                    out.print(", _jspx_sout.toString()");
+                }
+                if (scopeName != null) {
+                    out.print(", ");
+                    out.print(getScopeConstant(scopeName));
+                }
+                out.println(");");
+            }
+
+            // Restore EL context
+            out.printil("jspContext.getELContext().putContext(JspContext.class,getJspContext());");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.DoBodyAction n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Copy virtual page scope of tag file to page scope of invoking
+            // page
+            out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();");
+
+            // Invoke body
+            String varReaderAttr = n.getTextAttribute("varReader");
+            String varAttr = n.getTextAttribute("var");
+            if (varReaderAttr != null || varAttr != null) {
+                out.printil("_jspx_sout = new java.io.StringWriter();");
+            } else {
+                out.printil("_jspx_sout = null;");
+            }
+            out.printil("if (getJspBody() != null)");
+            out.pushIndent();
+            out.printil("getJspBody().invoke(_jspx_sout);");
+            out.popIndent();
+
+            // Store varReader in appropriate scope
+            if (varReaderAttr != null || varAttr != null) {
+                String scopeName = n.getTextAttribute("scope");
+                out.printin("_jspx_page_context.setAttribute(");
+                if (varReaderAttr != null) {
+                    out.print(quote(varReaderAttr));
+                    out
+                            .print(", new java.io.StringReader(_jspx_sout.toString())");
+                } else {
+                    out.print(quote(varAttr));
+                    out.print(", _jspx_sout.toString()");
+                }
+                if (scopeName != null) {
+                    out.print(", ");
+                    out.print(getScopeConstant(scopeName));
+                }
+                out.println(");");
+            }
+
+            // Restore EL context
+            out.printil("jspContext.getELContext().putContext(JspContext.class,getJspContext());");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        public void visit(Node.AttributeGenerator n) throws JasperException {
+            Node.CustomTag tag = n.getTag();
+            Node.JspAttribute[] attrs = tag.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                if (attrs[i].getName().equals(n.getName())) {
+                    out.print(evaluateAttribute(getTagHandlerInfo(tag),
+                            attrs[i], tag, null));
+                    break;
+                }
+            }
+        }
+
+        private TagHandlerInfo getTagHandlerInfo(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.getLocalName());
+            if (handlerInfo == null) {
+                handlerInfo = new TagHandlerInfo(n, n.getTagHandlerClass(), err);
+                handlerInfosByShortName.put(n.getLocalName(), handlerInfo);
+            }
+            return handlerInfo;
+        }
+
+        private void generateTagPlugin(Node.CustomTag n) throws JasperException {
+            if (n.getAtSTag() != null) {
+                n.getAtSTag().visit(this);
+            }
+            visitBody(n);
+            if (n.getAtETag() != null) {
+                n.getAtETag().visit(this);
+            }
+        }
+
+        private void generateCustomStart(Node.CustomTag n,
+                TagHandlerInfo handlerInfo, String tagHandlerVar,
+                String tagEvalVar, String tagPushBodyCountVar)
+                throws JasperException {
+
+            Class tagHandlerClass = handlerInfo.getTagHandlerClass();
+
+            out.printin("//  ");
+            out.println(n.getQName());
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Declare AT_BEGIN scripting variables
+            declareScriptingVars(n, VariableInfo.AT_BEGIN);
+            saveScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            String tagHandlerClassName = JspUtil
+                    .getCanonicalName(tagHandlerClass);
+            out.printin(tagHandlerClassName);
+            out.print(" ");
+            out.print(tagHandlerVar);
+            out.print(" = ");
+            if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                out.print("(");
+                out.print(tagHandlerClassName);
+                out.print(") ");
+                out.print(n.getTagHandlerPoolName());
+                out.print(".get(");
+                out.print(tagHandlerClassName);
+                out.println(".class);");
+            } else {
+                out.print("new ");
+                out.print(tagHandlerClassName);
+                out.println("();");
+                out.printin("org.apache.jasper.runtime.AnnotationHelper.postConstruct(");
+                out.print(VAR_ANNOTATIONPROCESSOR);
+                out.print(", ");
+                out.print(tagHandlerVar);
+                out.println(");");
+            }
+
+            // includes setting the context
+            generateSetters(n, tagHandlerVar, handlerInfo, false);
+
+            // JspIdConsumer (after context has been set)
+            if (n.implementsJspIdConsumer()) {
+                out.printin(tagHandlerVar);
+                out.print(".setJspId(\"");
+                out.print(createJspId());
+                out.println("\");");
+            }
+
+            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.hasEmptyBody()) {
+                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("out = _jspx_page_context.pushBody();");
+                    if (n.implementsTryCatchFinally()) {
+                        out.printin(tagPushBodyCountVar);
+                        out.println("[0]++;");
+                    } else if (pushBodyCountVar != null) {
+                        out.printin(pushBodyCountVar);
+                        out.println("[0]++;");
+                    }
+                    out.printin(tagHandlerVar);
+                    out
+                            .println(".setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);");
+                    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();
+                }
+            }
+            // Map the Java lines that handles start of custom tags to the
+            // JSP line for this tag
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        private void generateCustomEnd(Node.CustomTag n, String tagHandlerVar,
+                String tagEvalVar, String tagPushBodyCountVar) {
+
+            if (!n.hasEmptyBody()) {
+                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 = _jspx_page_context.popBody();");
+                    if (n.implementsTryCatchFinally()) {
+                        out.printin(tagPushBodyCountVar);
+                        out.println("[0]--;");
+                    } else if (pushBodyCountVar != null) {
+                        out.printin(pushBodyCountVar);
+                        out.println("[0]--;");
+                    }
+                    out.popIndent();
+                    out.printil("}");
+                }
+
+                out.popIndent(); // EVAL_BODY
+                out.printil("}");
+            }
+
+            out.printin("if (");
+            out.print(tagHandlerVar);
+            out
+                    .println(".doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {");
+            out.pushIndent();
+            if (!n.implementsTryCatchFinally()) {
+                if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                    out.printin(n.getTagHandlerPoolName());
+                    out.print(".reuse(");
+                    out.print(tagHandlerVar);
+                    out.println(");");
+                } else {
+                    out.printin(tagHandlerVar);
+                    out.println(".release();");
+                    out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy(");
+                    out.print(VAR_ANNOTATIONPROCESSOR);
+                    out.print(", ");
+                    out.print(tagHandlerVar);
+                    out.println(");");
+                }
+            }
+            if (isTagFile || isFragment) {
+                out.printil("throw new SkipPageException();");
+            } else {
+                out.printil((methodNesting > 0) ? "return true;" : "return;");
+            }
+            out.popIndent();
+            out.printil("}");
+            // 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 = _jspx_page_context.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 (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                out.printin(n.getTagHandlerPoolName());
+                out.print(".reuse(");
+                out.print(tagHandlerVar);
+                out.println(");");
+            } else {
+                out.printin(tagHandlerVar);
+                out.println(".release();");
+                out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy(");
+                out.print(VAR_ANNOTATIONPROCESSOR);
+                out.print(", ");
+                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);
+        }
+
+        private void generateCustomDoTag(Node.CustomTag n,
+                TagHandlerInfo handlerInfo, String tagHandlerVar)
+                throws JasperException {
+
+            Class tagHandlerClass = handlerInfo.getTagHandlerClass();
+
+            n.setBeginJavaLine(out.getJavaLine());
+            out.printin("//  ");
+            out.println(n.getQName());
+
+            // Declare AT_BEGIN scripting variables
+            declareScriptingVars(n, VariableInfo.AT_BEGIN);
+            saveScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            String tagHandlerClassName = JspUtil
+                    .getCanonicalName(tagHandlerClass);
+            out.printin(tagHandlerClassName);
+            out.print(" ");
+            out.print(tagHandlerVar);
+            out.print(" = ");
+            out.print("new ");
+            out.print(tagHandlerClassName);
+            out.println("();");
+
+            // Resource injection
+            out.printin("org.apache.jasper.runtime.AnnotationHelper.postConstruct(");
+            out.print(VAR_ANNOTATIONPROCESSOR);
+            out.print(", ");
+            out.print(tagHandlerVar);
+            out.println(");");
+            
+            generateSetters(n, tagHandlerVar, handlerInfo, true);
+
+            // JspIdConsumer (after context has been set)
+            if (n.implementsJspIdConsumer()) {
+                out.printin(tagHandlerVar);
+                out.print(".setJspId(\"");
+                out.print(createJspId());
+                out.println("\");");
+            }
+
+            // Set the body
+            if (findJspBody(n) == null) {
+                /*
+                 * Encapsulate body of custom tag invocation in JspFragment and
+                 * pass it to tag handler's setJspBody(), unless tag body is
+                 * empty
+                 */
+                if (!n.hasEmptyBody()) {
+                    out.printin(tagHandlerVar);
+                    out.print(".setJspBody(");
+                    generateJspFragment(n, tagHandlerVar);
+                    out.println(");");
+                }
+            } else {
+                /*
+                 * Body of tag is the body of the <jsp:body> element. The visit
+                 * method for that element is going to encapsulate that
+                 * element's body in a JspFragment and pass it to the tag
+                 * handler's setJspBody()
+                 */
+                String tmpTagHandlerVar = simpleTagHandlerVar;
+                simpleTagHandlerVar = tagHandlerVar;
+                boolean tmpIsSimpleTagHandler = isSimpleTagHandler;
+                isSimpleTagHandler = true;
+                visitBody(n);
+                simpleTagHandlerVar = tmpTagHandlerVar;
+                isSimpleTagHandler = tmpIsSimpleTagHandler;
+            }
+
+            out.printin(tagHandlerVar);
+            out.println(".doTag();");
+
+            restoreScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            // Synchronize AT_BEGIN scripting variables
+            syncScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            // Declare and synchronize AT_END scripting variables
+            declareScriptingVars(n, VariableInfo.AT_END);
+            syncScriptingVars(n, VariableInfo.AT_END);
+
+            // Resource injection
+            out.printin("org.apache.jasper.runtime.AnnotationHelper.preDestroy(");
+            out.print(VAR_ANNOTATIONPROCESSOR);
+            out.print(", ");
+            out.print(tagHandlerVar);
+            out.println(");");
+
+            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 < vec.size(); i++) {
+                    Object elem = vec.elementAt(i);
+                    if (elem instanceof VariableInfo) {
+                        VariableInfo varInfo = (VariableInfo) elem;
+                        if (varInfo.getDeclare()) {
+                            out.printin(varInfo.getClassName());
+                            out.print(" ");
+                            out.print(varInfo.getVarName());
+                            out.println(" = null;");
+                        }
+                    } else {
+                        TagVariableInfo tagVarInfo = (TagVariableInfo) elem;
+                        if (tagVarInfo.getDeclare()) {
+                            String varName = tagVarInfo.getNameGiven();
+                            if (varName == null) {
+                                varName = n.getTagData().getAttributeString(
+                                        tagVarInfo.getNameFromAttribute());
+                            } else if (tagVarInfo.getNameFromAttribute() != null) {
+                                // alias
+                                continue;
+                            }
+                            out.printin(tagVarInfo.getClassName());
+                            out.print(" ");
+                            out.print(varName);
+                            out.println(" = null;");
+                        }
+                    }
+                }
+            }
+        }
+
+        /*
+         * This method is called as part of the custom tag's start element.
+         * 
+         * If the given custom tag has a custom nesting level greater than 0,
+         * save the current values of its scripting variables to temporary
+         * variables, so those values may be restored in the tag's end element.
+         * This way, the scripting variables may be synchronized by the given
+         * tag without affecting their original values.
+         */
+        private void saveScriptingVars(Node.CustomTag n, int scope) {
+            if (n.getCustomNestingLevel() == 0) {
+                return;
+            }
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (n.getScriptingVars(scope).contains(varInfos[i]))
+                        continue;
+                    String varName = varInfos[i].getVarName();
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(tmpVarName);
+                    out.print(" = ");
+                    out.print(varName);
+                    out.println(";");
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (n.getScriptingVars(scope).contains(tagVarInfos[i]))
+                        continue;
+                    String varName = tagVarInfos[i].getNameGiven();
+                    if (varName == null) {
+                        varName = n.getTagData().getAttributeString(
+                                tagVarInfos[i].getNameFromAttribute());
+                    } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                        // alias
+                        continue;
+                    }
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(tmpVarName);
+                    out.print(" = ");
+                    out.print(varName);
+                    out.println(";");
+                }
+            }
+        }
+
+        /*
+         * This method is called as part of the custom tag's end element.
+         * 
+         * If the given custom tag has a custom nesting level greater than 0,
+         * restore its scripting variables to their original values that were
+         * saved in the tag's start element.
+         */
+        private void restoreScriptingVars(Node.CustomTag n, int scope) {
+            if (n.getCustomNestingLevel() == 0) {
+                return;
+            }
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (n.getScriptingVars(scope).contains(varInfos[i]))
+                        continue;
+                    String varName = varInfos[i].getVarName();
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(varName);
+                    out.print(" = ");
+                    out.print(tmpVarName);
+                    out.println(";");
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (n.getScriptingVars(scope).contains(tagVarInfos[i]))
+                        continue;
+                    String varName = tagVarInfos[i].getNameGiven();
+                    if (varName == null) {
+                        varName = n.getTagData().getAttributeString(
+                                tagVarInfos[i].getNameFromAttribute());
+                    } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                        // alias
+                        continue;
+                    }
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(varName);
+                    out.print(" = ");
+                    out.print(tmpVarName);
+                    out.println(";");
+                }
+            }
+        }
+
+        /*
+         * Synchronizes the scripting variables of the given custom tag for the
+         * given scope.
+         */
+        private void syncScriptingVars(Node.CustomTag n, int scope) {
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() == scope) {
+                        out.printin(varInfos[i].getVarName());
+                        out.print(" = (");
+                        out.print(varInfos[i].getClassName());
+                        out.print(") _jspx_page_context.findAttribute(");
+                        out.print(quote(varInfos[i].getVarName()));
+                        out.println(");");
+                    }
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() == scope) {
+                        String name = tagVarInfos[i].getNameGiven();
+                        if (name == null) {
+                            name = n.getTagData().getAttributeString(
+                                    tagVarInfos[i].getNameFromAttribute());
+                        } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                            // alias
+                            continue;
+                        }
+                        out.printin(name);
+                        out.print(" = (");
+                        out.print(tagVarInfos[i].getClassName());
+                        out.print(") _jspx_page_context.findAttribute(");
+                        out.print(quote(name));
+                        out.println(");");
+                    }
+                }
+            }
+        }
+
+        private String getJspContextVar() {
+            if (this.isTagFile) {
+                return "this.getJspContext()";
+            } else {
+                return "_jspx_page_context";
+            }
+        }
+
+        private String getExpressionFactoryVar() {
+            return VAR_EXPRESSIONFACTORY;
+        }
+
+        /*
+         * Creates a tag variable name by concatenating the given prefix and
+         * shortName and endcoded to make the resultant string a valid Java
+         * Identifier.
+         */
+        private String createTagVarName(String fullName, String prefix,
+                String shortName) {
+
+            String varName;
+            synchronized (tagVarNumbers) {
+                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));
+                } else {
+                    tagVarNumbers.put(fullName, new Integer(1));
+                    varName = varName + "0";
+                }
+            }
+            return JspUtil.makeJavaIdentifier(varName);
+        }
+
+        private String evaluateAttribute(TagHandlerInfo handlerInfo,
+                Node.JspAttribute attr, Node.CustomTag n, String tagHandlerVar)
+                throws JasperException {
+
+            String attrValue = attr.getValue();
+            if (attrValue == null) {
+                if (attr.isNamedAttribute()) {
+                    if (n.checkIfAttributeIsJspFragment(attr.getName())) {
+                        // XXX - no need to generate temporary variable here
+                        attrValue = generateNamedAttributeJspFragment(attr
+                                .getNamedAttributeNode(), tagHandlerVar);
+                    } else {
+                        attrValue = generateNamedAttributeValue(attr
+                                .getNamedAttributeNode());
+                    }
+                } else {
+                    return null;
+                }
+            }
+
+            String localName = attr.getLocalName();
+
+            Method m = null;
+            Class[] c = null;
+            if (attr.isDynamic()) {
+                c = OBJECT_CLASS;
+            } else {
+                m = handlerInfo.getSetterMethod(localName);
+                if (m == null) {
+                    err.jspError(n, "jsp.error.unable.to_find_method", attr
+                            .getName());
+                }
+                c = m.getParameterTypes();
+                // XXX assert(c.length > 0)
+            }
+
+            if (attr.isExpression()) {
+                // Do nothing
+            } else if (attr.isNamedAttribute()) {
+                if (!n.checkIfAttributeIsJspFragment(attr.getName())
+                        && !attr.isDynamic()) {
+                    attrValue = convertString(c[0], attrValue, localName,
+                            handlerInfo.getPropertyEditorClass(localName), true);
+                }
+            } else if (attr.isELInterpreterInput()) {
+
+                // results buffer
+                StringBuffer sb = new StringBuffer(64);
+
+                TagAttributeInfo tai = attr.getTagAttributeInfo();
+
+                // generate elContext reference
+                sb.append(getJspContextVar());
+                sb.append(".getELContext()");
+                String elContext = sb.toString();
+                if (attr.getEL() != null && attr.getEL().getMapName() != null) {
+                    sb.setLength(0);
+                    sb.append("new org.apache.jasper.el.ELContextWrapper(");
+                    sb.append(elContext);
+                    sb.append(',');
+                    sb.append(attr.getEL().getMapName());
+                    sb.append(')');
+                    elContext = sb.toString();
+                }
+
+                // reset buffer
+                sb.setLength(0);
+                
+                // create our mark
+                sb.append(n.getStart().toString());
+                sb.append(" '");
+                sb.append(attrValue);
+                sb.append('\'');                
+                String mark = sb.toString();
+                
+                // reset buffer
+                sb.setLength(0);
+
+                // depending on type
+                if (attr.isDeferredInput()
+                        || ((tai != null) && ValueExpression.class.getName().equals(tai.getTypeName()))) {
+                    sb.append("new org.apache.jasper.el.JspValueExpression(");
+                    sb.append(quote(mark));
+                    sb.append(',');
+                    sb.append(getExpressionFactoryVar());
+                    sb.append(".createValueExpression(");
+                    if (attr.getEL() != null) { // optimize
+                        sb.append(elContext);
+                        sb.append(',');
+                    }
+                    sb.append(quote(attrValue));
+                    sb.append(',');
+                    sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
+                    sb.append("))");
+                    // should the expression be evaluated before passing to
+                    // the setter?
+                    boolean evaluate = false;
+                    if (tai.canBeRequestTime()) {
+                        evaluate = true; // JSP.2.3.2
+                    }
+                    if (attr.isDeferredInput()) {
+                        evaluate = false; // JSP.2.3.3
+                    }
+                    if (attr.isDeferredInput() && tai.canBeRequestTime()) {
+                        evaluate = !attrValue.contains("#{"); // JSP.2.3.5
+                    }
+                    if (evaluate) {
+                        sb.append(".getValue(");
+                        sb.append(getJspContextVar());
+                        sb.append(".getELContext()");
+                        sb.append(")");
+                    }
+                    attrValue = sb.toString();
+                } else if (attr.isDeferredMethodInput()
+                        || ((tai != null) && MethodExpression.class.getName().equals(tai.getTypeName()))) {
+                    sb.append("new org.apache.jasper.el.JspMethodExpression(");
+                    sb.append(quote(mark));
+                    sb.append(',');
+                    sb.append(getExpressionFactoryVar());
+                    sb.append(".createMethodExpression(");
+                    sb.append(elContext);
+                    sb.append(',');
+                    sb.append(quote(attrValue));
+                    sb.append(',');
+                    sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
+                    sb.append(',');
+                    sb.append("new Class[] {");
+
+                    String[] p = attr.getParameterTypeNames();
+                    for (int i = 0; i < p.length; i++) {
+                        sb.append(JspUtil.toJavaSourceTypeFromTld(p[i]));
+                        sb.append(',');
+                    }
+                    if (p.length > 0) {
+                        sb.setLength(sb.length() - 1);
+                    }
+
+                    sb.append("}))");
+                    attrValue = sb.toString();
+                } else {
+                    // run attrValue through the expression interpreter
+                    String mapName = (attr.getEL() != null) ? attr.getEL()
+                            .getMapName() : null;
+                    attrValue = attributeValueWithEL(this.isTagFile,
+                            attrValue, c[0], mapName);
+                }
+            } else {
+                attrValue = convertString(c[0], attrValue, localName,
+                        handlerInfo.getPropertyEditorClass(localName), false);
+            }
+            return attrValue;
+        }
+
+        /**
+         * Generate code to create a map for the alias variables
+         * 
+         * @return the name of the map
+         */
+        private String generateAliasMap(Node.CustomTag n, String tagHandlerVar)
+                throws JasperException {
+
+            TagVariableInfo[] tagVars = n.getTagVariableInfos();
+            String aliasMapVar = null;
+
+            boolean aliasSeen = false;
+            for (int i = 0; i < tagVars.length; i++) {
+
+                String nameFrom = tagVars[i].getNameFromAttribute();
+                if (nameFrom != null) {
+                    String aliasedName = n.getAttributeValue(nameFrom);
+                    if (aliasedName == null)
+                        continue;
+
+                    if (!aliasSeen) {
+                        out.printin("java.util.HashMap ");
+                        aliasMapVar = tagHandlerVar + "_aliasMap";
+                        out.print(aliasMapVar);
+                        out.println(" = new java.util.HashMap();");
+                        aliasSeen = true;
+                    }
+                    out.printin(aliasMapVar);
+                    out.print(".put(");
+                    out.print(quote(tagVars[i].getNameGiven()));
+                    out.print(", ");
+                    out.print(quote(aliasedName));
+                    out.println(");");
+                }
+            }
+            return aliasMapVar;
+        }
+
+        private void generateSetters(Node.CustomTag n, String tagHandlerVar,
+                TagHandlerInfo handlerInfo, boolean simpleTag)
+                throws JasperException {
+
+            // Set context
+            if (simpleTag) {
+                // Generate alias map
+                String aliasMapVar = null;
+                if (n.isTagFile()) {
+                    aliasMapVar = generateAliasMap(n, tagHandlerVar);
+                }
+                out.printin(tagHandlerVar);
+                if (aliasMapVar == null) {
+                    out.println(".setJspContext(_jspx_page_context);");
+                } else {
+                    out.print(".setJspContext(_jspx_page_context, ");
+                    out.print(aliasMapVar);
+                    out.println(");");
+                }
+            } else {
+                out.printin(tagHandlerVar);
+                out.println(".setPageContext(_jspx_page_context);");
+            }
+
+            // Set parent
+            if (isTagFile && parent == null) {
+                out.printin(tagHandlerVar);
+                out.print(".setParent(");
+                out.print("new javax.servlet.jsp.tagext.TagAdapter(");
+                out.print("(javax.servlet.jsp.tagext.SimpleTag) this ));");
+            } else if (!simpleTag) {
+                out.printin(tagHandlerVar);
+                out.print(".setParent(");
+                if (parent != null) {
+                    if (isSimpleTagParent) {
+                        out.print("new javax.servlet.jsp.tagext.TagAdapter(");
+                        out.print("(javax.servlet.jsp.tagext.SimpleTag) ");
+                        out.print(parent);
+                        out.println("));");
+                    } else {
+                        out.print("(javax.servlet.jsp.tagext.Tag) ");
+                        out.print(parent);
+                        out.println(");");
+                    }
+                } else {
+                    out.println("null);");
+                }
+            } else {
+                // The setParent() method need not be called if the value being
+                // passed is null, since SimpleTag instances are not reused
+                if (parent != null) {
+                    out.printin(tagHandlerVar);
+                    out.print(".setParent(");
+                    out.print(parent);
+                    out.println(");");
+                }
+            }
+
+            // need to handle deferred values and methods
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                String attrValue = evaluateAttribute(handlerInfo, attrs[i], n,
+                        tagHandlerVar);
+                
+                Mark m = n.getStart();
+                out.printil("// "+m.getFile()+"("+m.getLineNumber()+","+m.getColumnNumber()+") "+ attrs[i].getTagAttributeInfo());
+                if (attrs[i].isDynamic()) {
+                    out.printin(tagHandlerVar);
+                    out.print(".");
+                    out.print("setDynamicAttribute(");
+                    String uri = attrs[i].getURI();
+                    if ("".equals(uri) || (uri == null)) {
+                        out.print("null");
+                    } else {
+                        out.print("\"" + attrs[i].getURI() + "\"");
+                    }
+                    out.print(", \"");
+                    out.print(attrs[i].getLocalName());
+                    out.print("\", ");
+                    out.print(attrValue);
+                    out.println(");");
+                } else {
+                    out.printin(tagHandlerVar);
+                    out.print(".");
+                    out.print(handlerInfo.getSetterMethod(
+                            attrs[i].getLocalName()).getName());
+                    out.print("(");
+                    out.print(attrValue);
+                    out.println(");");
+                }
+            }
+        }
+
+        /*
+         * @param c The target class to which to coerce the given string @param
+         * s The string value @param attrName The name of the attribute whose
+         * value is being supplied @param propEditorClass The property editor
+         * for the given attribute @param isNamedAttribute true if the given
+         * attribute is a named attribute (that is, specified using the
+         * jsp:attribute standard action), and false otherwise
+         */
+        private String convertString(Class c, String s, String attrName,
+                Class propEditorClass, boolean isNamedAttribute)
+                throws JasperException {
+
+            String quoted = s;
+            if (!isNamedAttribute) {
+                quoted = quote(s);
+            }
+
+            if (propEditorClass != null) {
+                String className = JspUtil.getCanonicalName(c);
+                return "("
+                        + className
+                        + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromBeanInfoPropertyEditor("
+                        + className + ".class, \"" + attrName + "\", " + quoted
+                        + ", " + JspUtil.getCanonicalName(propEditorClass)
+                        + ".class)";
+            } else if (c == String.class) {
+                return quoted;
+            } else if (c == boolean.class) {
+                return JspUtil.coerceToPrimitiveBoolean(s, isNamedAttribute);
+            } else if (c == Boolean.class) {
+                return JspUtil.coerceToBoolean(s, isNamedAttribute);
+            } else if (c == byte.class) {
+                return JspUtil.coerceToPrimitiveByte(s, isNamedAttribute);
+            } else if (c == Byte.class) {
+                return JspUtil.coerceToByte(s, isNamedAttribute);
+            } else if (c == char.class) {
+                return JspUtil.coerceToChar(s, isNamedAttribute);
+            } else if (c == Character.class) {
+                return JspUtil.coerceToCharacter(s, isNamedAttribute);
+            } else if (c == double.class) {
+                return JspUtil.coerceToPrimitiveDouble(s, isNamedAttribute);
+            } else if (c == Double.class) {
+                return JspUtil.coerceToDouble(s, isNamedAttribute);
+            } else if (c == float.class) {
+                return JspUtil.coerceToPrimitiveFloat(s, isNamedAttribute);
+            } else if (c == Float.class) {
+                return JspUtil.coerceToFloat(s, isNamedAttribute);
+            } else if (c == int.class) {
+                return JspUtil.coerceToInt(s, isNamedAttribute);
+            } else if (c == Integer.class) {
+                return JspUtil.coerceToInteger(s, isNamedAttribute);
+            } else if (c == short.class) {
+                return JspUtil.coerceToPrimitiveShort(s, isNamedAttribute);
+            } else if (c == Short.class) {
+                return JspUtil.coerceToShort(s, isNamedAttribute);
+            } else if (c == long.class) {
+                return JspUtil.coerceToPrimitiveLong(s, isNamedAttribute);
+            } else if (c == Long.class) {
+                return JspUtil.coerceToLong(s, isNamedAttribute);
+            } else if (c == Object.class) {
+                return "new String(" + quoted + ")";
+            } else {
+                String className = JspUtil.getCanonicalName(c);
+                return "("
+                        + className
+                        + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromPropertyEditorManager("
+                        + className + ".class, \"" + attrName + "\", " + quoted
+                        + ")";
+            }
+        }
+
+        /*
+         * Converts the scope string representation, whose possible values are
+         * "page", "request", "session", and "application", to the corresponding
+         * scope constant.
+         */
+        private String getScopeConstant(String scope) {
+            String scopeName = "PageContext.PAGE_SCOPE"; // Default to page
+
+            if ("request".equals(scope)) {
+                scopeName = "PageContext.REQUEST_SCOPE";
+            } else if ("session".equals(scope)) {
+                scopeName = "PageContext.SESSION_SCOPE";
+            } else if ("application".equals(scope)) {
+                scopeName = "PageContext.APPLICATION_SCOPE";
+            }
+
+            return scopeName;
+        }
+
+        /**
+         * Generates anonymous JspFragment inner class which is passed as an
+         * argument to SimpleTag.setJspBody().
+         */
+        private void generateJspFragment(Node n, String tagHandlerVar)
+                throws JasperException {
+            // XXX - A possible optimization here would be to check to see
+            // if the only child of the parent node is TemplateText. If so,
+            // we know there won't be any parameters, etc, so we can
+            // generate a low-overhead JspFragment that just echoes its
+            // body. The implementation of this fragment can come from
+            // the org.apache.jasper.runtime package as a support class.
+            FragmentHelperClass.Fragment fragment = fragmentHelperClass
+                    .openFragment(n, tagHandlerVar, methodNesting);
+            ServletWriter outSave = out;
+            out = fragment.getGenBuffer().getOut();
+            String tmpParent = parent;
+            parent = "_jspx_parent";
+            boolean isSimpleTagParentSave = isSimpleTagParent;
+            isSimpleTagParent = true;
+            boolean tmpIsFragment = isFragment;
+            isFragment = true;
+            String pushBodyCountVarSave = pushBodyCountVar;
+            if (pushBodyCountVar != null) {
+                // Use a fixed name for push body count, to simplify code gen
+                pushBodyCountVar = "_jspx_push_body_count";
+            }
+            visitBody(n);
+            out = outSave;
+            parent = tmpParent;
+            isSimpleTagParent = isSimpleTagParentSave;
+            isFragment = tmpIsFragment;
+            pushBodyCountVar = pushBodyCountVarSave;
+            fragmentHelperClass.closeFragment(fragment, methodNesting);
+            // XXX - Need to change pageContext to jspContext if
+            // we're not in a place where pageContext is defined (e.g.
+            // in a fragment or in a tag file.
+            out.print("new " + fragmentHelperClass.getClassName() + "( "
+                    + fragment.getId() + ", _jspx_page_context, "
+                    + tagHandlerVar + ", " + pushBodyCountVar + ")");
+        }
+
+        /**
+         * Generate the code required to obtain the runtime value of the given
+         * named attribute.
+         * 
+         * @return The name of the temporary variable the result is stored in.
+         */
+        public String generateNamedAttributeValue(Node.NamedAttribute n)
+                throws JasperException {
+
+            String varName = n.getTemporaryVariableName();
+
+            // If the only body element for this named attribute node is
+            // template text, we need not generate an extra call to
+            // pushBody and popBody. Maybe we can further optimize
+            // here by getting rid of the temporary variable, but in
+            // reality it looks like javac does this for us.
+            Node.Nodes body = n.getBody();
+            if (body != null) {
+                boolean templateTextOptimization = false;
+                if (body.size() == 1) {
+                    Node bodyElement = body.getNode(0);
+                    if (bodyElement instanceof Node.TemplateText) {
+                        templateTextOptimization = true;
+                        out.printil("String "
+                                + varName
+                                + " = "
+                                + quote(new String(
+                                        ((Node.TemplateText) bodyElement)
+                                                .getText())) + ";");
+                    }
+                }
+
+                // XXX - Another possible optimization would be for
+                // lone EL expressions (no need to pushBody here either).
+
+                if (!templateTextOptimization) {
+                    out.printil("out = _jspx_page_context.pushBody();");
+                    visitBody(n);
+                    out.printil("String " + varName + " = "
+                            + "((javax.servlet.jsp.tagext.BodyContent)"
+                            + "out).getString();");
+                    out.printil("out = _jspx_page_context.popBody();");
+                }
+            } else {
+                // Empty body must be treated as ""
+                out.printil("String " + varName + " = \"\";");
+            }
+
+            return varName;
+        }
+
+        /**
+         * Similar to generateNamedAttributeValue, but create a JspFragment
+         * instead.
+         * 
+         * @param n
+         *            The parent node of the named attribute
+         * @param tagHandlerVar
+         *            The variable the tag handler is stored in, so the fragment
+         *            knows its parent tag.
+         * @return The name of the temporary variable the fragment is stored in.
+         */
+        public String generateNamedAttributeJspFragment(Node.NamedAttribute n,
+                String tagHandlerVar) throws JasperException {
+            String varName = n.getTemporaryVariableName();
+
+            out.printin("javax.servlet.jsp.tagext.JspFragment " + varName
+                    + " = ");
+            generateJspFragment(n, tagHandlerVar);
+            out.println(";");
+
+            return varName;
+        }
+    }
+
+    private static void generateLocalVariables(ServletWriter out, Node n)
+            throws JasperException {
+        Node.ChildInfo ci;
+        if (n instanceof Node.CustomTag) {
+            ci = ((Node.CustomTag) n).getChildInfo();
+        } else if (n instanceof Node.JspBody) {
+            ci = ((Node.JspBody) n).getChildInfo();
+        } else if (n instanceof Node.NamedAttribute) {
+            ci = ((Node.NamedAttribute) n).getChildInfo();
+        } else {
+            // Cannot access err since this method is static, but at
+            // least flag an error.
+            throw new JasperException("Unexpected Node Type");
+            // err.getString(
+            // "jsp.error.internal.unexpected_node_type" ) );
+        }
+
+        if (ci.hasUseBean()) {
+            out
+                    .printil("HttpSession session = _jspx_page_context.getSession();");
+            out
+                    .printil("ServletContext application = _jspx_page_context.getServletContext();");
+        }
+        if (ci.hasUseBean() || ci.hasIncludeAction() || ci.hasSetProperty()
+                || ci.hasParamAction()) {
+            out
+                    .printil("HttpServletRequest request = (HttpServletRequest)_jspx_page_context.getRequest();");
+        }
+        if (ci.hasIncludeAction()) {
+            out
+                    .printil("HttpServletResponse response = (HttpServletResponse)_jspx_page_context.getResponse();");
+        }
+    }
+
+    /**
+     * Common part of postamble, shared by both servlets and tag files.
+     */
+    private void genCommonPostamble() {
+        // Append any methods that were generated in the buffer.
+        for (int i = 0; i < methodsBuffered.size(); i++) {
+            GenBuffer methodBuffer = (GenBuffer) methodsBuffered.get(i);
+            methodBuffer.adjustJavaLines(out.getJavaLine() - 1);
+            out.printMultiLn(methodBuffer.toString());
+        }
+
+        // Append the helper class
+        if (fragmentHelperClass.isUsed()) {
+            fragmentHelperClass.generatePostamble();
+            fragmentHelperClass.adjustJavaLines(out.getJavaLine() - 1);
+            out.printMultiLn(fragmentHelperClass.toString());
+        }
+
+        // Append char array declarations
+        if (charArrayBuffer != null) {
+            out.printMultiLn(charArrayBuffer.toString());
+        }
+
+        // Close the class definition
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /**
+     * Generates the ending part of the static portion of the servlet.
+     */
+    private void generatePostamble(Node.Nodes page) {
+        out.popIndent();
+        out.printil("} catch (Throwable t) {");
+        out.pushIndent();
+        out.printil("if (!(t instanceof SkipPageException)){");
+        out.pushIndent();
+        out.printil("out = _jspx_out;");
+        out.printil("if (out != null && out.getBufferSize() != 0)");
+        out.pushIndent();
+        out.printil("try { out.clearBuffer(); } catch (java.io.IOException e) {}");
+        out.popIndent();
+
+        out
+                .printil("if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);");
+        out.popIndent();
+        out.printil("}");
+        out.popIndent();
+        out.printil("} finally {");
+        out.pushIndent();
+
+        out
+                .printil("_jspxFactory.releasePageContext(_jspx_page_context);");
+
+        out.popIndent();
+        out.printil("}");
+
+        // Close the service method
+        out.popIndent();
+        out.printil("}");
+
+        // Generated methods, helper classes, etc.
+        genCommonPostamble();
+    }
+
+    /**
+     * Constructor.
+     */
+    Generator(ServletWriter out, Compiler compiler) {
+        this.out = out;
+        methodsBuffered = new ArrayList();
+        charArrayBuffer = null;
+        err = compiler.getErrorDispatcher();
+        ctxt = compiler.getCompilationContext();
+        fragmentHelperClass = new FragmentHelperClass("Helper");
+        pageInfo = compiler.getPageInfo();
+
+        /*
+         * Temporary hack. If a JSP page uses the "extends" attribute of the
+         * page directive, the _jspInit() method of the generated servlet class
+         * will not be called (it is only called for those generated servlets
+         * that extend HttpJspBase, the default), causing the tag handler pools
+         * not to be initialized and resulting in a NPE. The JSP spec needs to
+         * clarify whether containers can override init() and destroy(). For
+         * now, we just disable tag pooling for pages that use "extends".
+         */
+        if (pageInfo.getExtends(false) == null) {
+            isPoolingEnabled = ctxt.getOptions().isPoolingEnabled();
+        } else {
+            isPoolingEnabled = false;
+        }
+        beanInfo = pageInfo.getBeanRepository();
+        breakAtLF = ctxt.getOptions().getMappedFile();
+        if (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.isPoolingEnabled) {
+            gen.compileTagHandlerPoolList(page);
+        }
+        if (gen.ctxt.isTagFile()) {
+            JasperTagInfo tagInfo = (JasperTagInfo) gen.ctxt.getTagInfo();
+            gen.generateTagHandlerPreamble(tagInfo, page);
+
+            if (gen.ctxt.isPrototypeMode()) {
+                return;
+            }
+
+            gen.generateXmlProlog(page);
+            gen.fragmentHelperClass.generatePreamble();
+            page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out,
+                    gen.methodsBuffered, gen.fragmentHelperClass, gen.ctxt
+                            .getClassLoader(), tagInfo));
+            gen.generateTagHandlerPostamble(tagInfo);
+        } else {
+            gen.generatePreamble(page);
+            gen.generateXmlProlog(page);
+            gen.fragmentHelperClass.generatePreamble();
+            page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out,
+                    gen.methodsBuffered, gen.fragmentHelperClass, gen.ctxt
+                            .getClassLoader(), null));
+            gen.generatePostamble(page);
+        }
+    }
+
+    /*
+     * Generates tag handler preamble.
+     */
+    private void generateTagHandlerPreamble(JasperTagInfo tagInfo,
+            Node.Nodes tag) throws JasperException {
+
+        // Generate package declaration
+        String className = tagInfo.getTagClassName();
+        int lastIndex = className.lastIndexOf('.');
+        if (lastIndex != -1) {
+            String pkgName = className.substring(0, lastIndex);
+            genPreamblePackage(pkgName);
+            className = className.substring(lastIndex + 1);
+        }
+
+        // Generate imports
+        genPreambleImports();
+
+        // Generate class declaration
+        out.printin("public final class ");
+        out.println(className);
+        out.printil("    extends javax.servlet.jsp.tagext.SimpleTagSupport");
+        out.printin("    implements org.apache.jasper.runtime.JspSourceDependent");
+        if (tagInfo.hasDynamicAttributes()) {
+            out.println(",");
+            out.printin("               javax.servlet.jsp.tagext.DynamicAttributes");
+        }
+        out.println(" {");
+        out.println();
+        out.pushIndent();
+
+        /*
+         * Class body begins here
+         */
+        generateDeclarations(tag);
+
+        // Static initializations here
+        genPreambleStaticInitializers();
+
+        out.printil("private JspContext jspContext;");
+
+        // Declare writer used for storing result of fragment/body invocation
+        // if 'varReader' or 'var' attribute is specified
+        out.printil("private java.io.Writer _jspx_sout;");
+
+        // Class variable declarations
+        genPreambleClassVariableDeclarations(tagInfo.getTagName());
+
+        generateSetJspContext(tagInfo);
+
+        // Tag-handler specific declarations
+        generateTagHandlerAttributes(tagInfo);
+        if (tagInfo.hasDynamicAttributes())
+            generateSetDynamicAttribute();
+
+        // Methods here
+        genPreambleMethods();
+
+        // Now the doTag() method
+        out.printil("public void doTag() throws JspException, java.io.IOException {");
+
+        if (ctxt.isPrototypeMode()) {
+            out.printil("}");
+            out.popIndent();
+            out.printil("}");
+            return;
+        }
+
+        out.pushIndent();
+
+        /*
+         * According to the spec, 'pageContext' must not be made available as an
+         * implicit object in tag files. Declare _jspx_page_context, so we can
+         * share the code generator with JSPs.
+         */
+        out.printil("PageContext _jspx_page_context = (PageContext)jspContext;");
+        
+        // Declare implicit objects.
+        out.printil("HttpServletRequest request = "
+                + "(HttpServletRequest) _jspx_page_context.getRequest();");
+        out.printil("HttpServletResponse response = "
+                + "(HttpServletResponse) _jspx_page_context.getResponse();");
+        out.printil("HttpSession session = _jspx_page_context.getSession();");
+        out.printil("ServletContext application = _jspx_page_context.getServletContext();");
+        out.printil("ServletConfig config = _jspx_page_context.getServletConfig();");
+        out.printil("JspWriter out = jspContext.getOut();");
+        out.printil("_jspInit(config);");
+        
+        // set current JspContext on ELContext
+        out.printil("jspContext.getELContext().putContext(JspContext.class,jspContext);");
+        
+        generatePageScopedVariables(tagInfo);
+
+        declareTemporaryScriptingVars(tag);
+        out.println();
+
+        out.printil("try {");
+        out.pushIndent();
+    }
+
+    private void generateTagHandlerPostamble(TagInfo tagInfo) {
+        out.popIndent();
+
+        // Have to catch Throwable because a classic tag handler
+        // helper method is declared to throw Throwable.
+        out.printil("} catch( Throwable t ) {");
+        out.pushIndent();
+        out.printil("if( t instanceof SkipPageException )");
+        out.printil("    throw (SkipPageException) t;");
+        out.printil("if( t instanceof java.io.IOException )");
+        out.printil("    throw (java.io.IOException) t;");
+        out.printil("if( t instanceof IllegalStateException )");
+        out.printil("    throw (IllegalStateException) t;");
+        out.printil("if( t instanceof JspException )");
+        out.printil("    throw (JspException) t;");
+        out.printil("throw new JspException(t);");
+        out.popIndent();
+        out.printil("} finally {");
+        out.pushIndent();
+        
+        // handle restoring VariableMapper
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        for (int i = 0; i < attrInfos.length; i++) {
+            if (attrInfos[i].isDeferredMethod() || attrInfos[i].isDeferredValue()) {
+                out.printin("_el_variablemapper.setVariable(");
+                out.print(quote(attrInfos[i].getName()));
+                out.print(",_el_ve");
+                out.print(i);
+                out.println(");");
+            }
+        }
+        
+        // restore nested JspContext on ELContext
+        out.printil("jspContext.getELContext().putContext(JspContext.class,super.getJspContext());");
+        
+        out.printil("((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile();");
+        if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) {
+            out.printil("_jspDestroy();");
+        }
+        out.popIndent();
+        out.printil("}");
+
+        // Close the doTag method
+        out.popIndent();
+        out.printil("}");
+
+        // Generated methods, helper classes, etc.
+        genCommonPostamble();
+    }
+
+    /**
+     * Generates declarations for tag handler attributes, and defines the getter
+     * and setter methods for each.
+     */
+    private void generateTagHandlerAttributes(TagInfo tagInfo)
+            throws JasperException {
+
+        if (tagInfo.hasDynamicAttributes()) {
+            out.printil("private java.util.HashMap _jspx_dynamic_attrs = new java.util.HashMap();");
+        }
+
+        // Declare attributes
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        for (int i = 0; i < attrInfos.length; i++) {
+            out.printin("private ");
+            if (attrInfos[i].isFragment()) {
+                out.print("javax.servlet.jsp.tagext.JspFragment ");
+            } else {
+                out.print(JspUtil.toJavaSourceType(attrInfos[i].getTypeName()));
+                out.print(" ");
+            }
+            out.print(attrInfos[i].getName());
+            out.println(";");
+        }
+        out.println();
+
+        // Define attribute getter and setter methods
+        if (attrInfos != null) {
+            for (int i = 0; i < attrInfos.length; i++) {
+                // getter method
+                out.printin("public ");
+                if (attrInfos[i].isFragment()) {
+                    out.print("javax.servlet.jsp.tagext.JspFragment ");
+                } else {
+                    out.print(JspUtil.toJavaSourceType(attrInfos[i]
+                            .getTypeName()));
+                    out.print(" ");
+                }
+                out.print(toGetterMethod(attrInfos[i].getName()));
+                out.println(" {");
+                out.pushIndent();
+                out.printin("return this.");
+                out.print(attrInfos[i].getName());
+                out.println(";");
+                out.popIndent();
+                out.printil("}");
+                out.println();
+
+                // setter method
+                out.printin("public void ");
+                out.print(toSetterMethodName(attrInfos[i].getName()));
+                if (attrInfos[i].isFragment()) {
+                    out.print("(javax.servlet.jsp.tagext.JspFragment ");
+                } else {
+                    out.print("(");
+                    out.print(JspUtil.toJavaSourceType(attrInfos[i]
+                            .getTypeName()));
+                    out.print(" ");
+                }
+                out.print(attrInfos[i].getName());
+                out.println(") {");
+                out.pushIndent();
+                out.printin("this.");
+                out.print(attrInfos[i].getName());
+                out.print(" = ");
+                out.print(attrInfos[i].getName());
+                out.println(";");
+                if (ctxt.isTagFile()) {
+                    // Tag files should also set jspContext attributes
+                    out.printin("jspContext.setAttribute(\"");
+                    out.print(attrInfos[i].getName());
+                    out.print("\", ");
+                    out.print(attrInfos[i].getName());
+                    out.println(");");
+                }
+                out.popIndent();
+                out.printil("}");
+                out.println();
+            }
+        }
+    }
+
+    /*
+     * Generate setter for JspContext so we can create a wrapper and store both
+     * the original and the wrapper. We need the wrapper to mask the page
+     * context from the tag file and simulate a fresh page context. We need the
+     * original to do things like sync AT_BEGIN and AT_END scripting variables.
+     */
+    private void generateSetJspContext(TagInfo tagInfo) {
+
+        boolean nestedSeen = false;
+        boolean atBeginSeen = false;
+        boolean atEndSeen = false;
+
+        // Determine if there are any aliases
+        boolean aliasSeen = false;
+        TagVariableInfo[] tagVars = tagInfo.getTagVariableInfos();
+        for (int i = 0; i < tagVars.length; i++) {
+            if (tagVars[i].getNameFromAttribute() != null
+                    && tagVars[i].getNameGiven() != null) {
+                aliasSeen = true;
+                break;
+            }
+        }
+
+        if (aliasSeen) {
+            out
+                    .printil("public void setJspContext(JspContext ctx, java.util.Map aliasMap) {");
+        } else {
+            out.printil("public void setJspContext(JspContext ctx) {");
+        }
+        out.pushIndent();
+        out.printil("super.setJspContext(ctx);");
+        out.printil("java.util.ArrayList _jspx_nested = null;");
+        out.printil("java.util.ArrayList _jspx_at_begin = null;");
+        out.printil("java.util.ArrayList _jspx_at_end = null;");
+
+        for (int i = 0; i < tagVars.length; i++) {
+
+            switch (tagVars[i].getScope()) {
+            case VariableInfo.NESTED:
+                if (!nestedSeen) {
+                    out.printil("_jspx_nested = new java.util.ArrayList();");
+                    nestedSeen = true;
+                }
+                out.printin("_jspx_nested.add(");
+                break;
+
+            case VariableInfo.AT_BEGIN:
+                if (!atBeginSeen) {
+                    out.printil("_jspx_at_begin = new java.util.ArrayList();");
+                    atBeginSeen = true;
+                }
+                out.printin("_jspx_at_begin.add(");
+                break;
+
+            case VariableInfo.AT_END:
+                if (!atEndSeen) {
+                    out.printil("_jspx_at_end = new java.util.ArrayList();");
+                    atEndSeen = true;
+                }
+                out.printin("_jspx_at_end.add(");
+                break;
+            } // switch
+
+            out.print(quote(tagVars[i].getNameGiven()));
+            out.println(");");
+        }
+        if (aliasSeen) {
+            out
+                    .printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, aliasMap);");
+        } else {
+            out
+                    .printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, null);");
+        }
+        out.popIndent();
+        out.printil("}");
+        out.println();
+        out.printil("public JspContext getJspContext() {");
+        out.pushIndent();
+        out.printil("return this.jspContext;");
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /*
+     * Generates implementation of
+     * javax.servlet.jsp.tagext.DynamicAttributes.setDynamicAttribute() method,
+     * which saves each dynamic attribute that is passed in so that a scoped
+     * variable can later be created for it.
+     */
+    public void generateSetDynamicAttribute() {
+        out
+                .printil("public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {");
+        out.pushIndent();
+        /*
+         * According to the spec, only dynamic attributes with no uri are to be
+         * present in the Map; all other dynamic attributes are ignored.
+         */
+        out.printil("if (uri == null)");
+        out.pushIndent();
+        out.printil("_jspx_dynamic_attrs.put(localName, value);");
+        out.popIndent();
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /*
+     * Creates a page-scoped variable for each declared tag attribute. Also, if
+     * the tag accepts dynamic attributes, a page-scoped variable is made
+     * available for each dynamic attribute that was passed in.
+     */
+    private void generatePageScopedVariables(JasperTagInfo tagInfo) {
+
+        // "normal" attributes
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        boolean variableMapperVar = false;
+        for (int i = 0; i < attrInfos.length; i++) {
+            String attrName = attrInfos[i].getName();
+            
+            // handle assigning deferred vars to VariableMapper, storing
+            // previous values under '_el_ve[i]' for later re-assignment
+            if (attrInfos[i].isDeferredValue() || attrInfos[i].isDeferredMethod()) {
+                
+                // we need to scope the modified VariableMapper for consistency and performance
+                if (!variableMapperVar) {
+                    out.printil("javax.el.VariableMapper _el_variablemapper = jspContext.getELContext().getVariableMapper();");
+                    variableMapperVar = true;
+                }
+                
+                out.printin("javax.el.ValueExpression _el_ve");
+                out.print(i);
+                out.print(" = _el_variablemapper.setVariable(");
+                out.print(quote(attrName));
+                out.print(',');
+                if (attrInfos[i].isDeferredMethod()) {
+                    out.print(VAR_EXPRESSIONFACTORY);
+                    out.print(".createValueExpression(");
+                    out.print(toGetterMethod(attrName));
+                    out.print(",javax.el.MethodExpression.class)");
+                } else {
+                    out.print(toGetterMethod(attrName));
+                }
+                out.println(");");
+            } else {
+                out.printil("if( " + toGetterMethod(attrName) + " != null ) ");
+                out.pushIndent();
+                out.printin("_jspx_page_context.setAttribute(");
+                out.print(quote(attrName));
+                out.print(", ");
+                out.print(toGetterMethod(attrName));
+                out.println(");");
+                out.popIndent();
+            }
+        }
+
+        // Expose the Map containing dynamic attributes as a page-scoped var
+        if (tagInfo.hasDynamicAttributes()) {
+            out.printin("_jspx_page_context.setAttribute(\"");
+            out.print(tagInfo.getDynamicAttributesMapName());
+            out.print("\", _jspx_dynamic_attrs);");
+        }
+    }
+
+    /*
+     * Generates the getter method for the given attribute name.
+     */
+    private String toGetterMethod(String attrName) {
+        char[] attrChars = attrName.toCharArray();
+        attrChars[0] = Character.toUpperCase(attrChars[0]);
+        return "get" + new String(attrChars) + "()";
+    }
+
+    /*
+     * Generates the setter method name for the given attribute name.
+     */
+    private String toSetterMethodName(String attrName) {
+        char[] attrChars = attrName.toCharArray();
+        attrChars[0] = Character.toUpperCase(attrChars[0]);
+        return "set" + new String(attrChars);
+    }
+
+    /**
+     * 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 < pd.length; i++) {
+                    /*
+                     * FIXME: should probably be checking for things like
+                     * pageContext, bodyContent, and parent here -akv
+                     */
+                    if (pd[i].getWriteMethod() != null) {
+                        methodMaps.put(pd[i].getName(), pd[i].getWriteMethod());
+                    }
+                    if (pd[i].getPropertyEditorClass() != null)
+                        propertyEditorMaps.put(pd[i].getName(), pd[i]
+                                .getPropertyEditorClass());
+                }
+            } catch (IntrospectionException ie) {
+                err.jspError(n, "jsp.error.introspect.taghandler",
+                        tagHandlerClass.getName(), ie);
+            }
+        }
+
+        /**
+         * XXX
+         */
+        public Method getSetterMethod(String attrName) {
+            return (Method) methodMaps.get(attrName);
+        }
+
+        /**
+         * XXX
+         */
+        public Class getPropertyEditorClass(String attrName) {
+            return (Class) propertyEditorMaps.get(attrName);
+        }
+
+        /**
+         * XXX
+         */
+        public Class getTagHandlerClass() {
+            return tagHandlerClass;
+        }
+    }
+
+    /**
+     * A class for generating codes to a buffer. Included here are some support
+     * for tracking source to Java lines mapping.
+     */
+    private static class GenBuffer {
+
+        /*
+         * For a CustomTag, the codes that are generated at the beginning of the
+         * tag may not be in the same buffer as those for the body of the tag.
+         * Two fields are used here to keep this straight. For codes that do not
+         * corresponds to any JSP lines, they should be null.
+         */
+        private Node node;
+
+        private Node.Nodes body;
+
+        private java.io.CharArrayWriter charWriter;
+
+        protected ServletWriter out;
+
+        GenBuffer() {
+            this(null, null);
+        }
+
+        GenBuffer(Node n, Node.Nodes b) {
+            node = n;
+            body = b;
+            if (body != null) {
+                body.setGeneratedInBuffer(true);
+            }
+            charWriter = new java.io.CharArrayWriter();
+            out = new ServletWriter(new java.io.PrintWriter(charWriter));
+        }
+
+        public ServletWriter getOut() {
+            return out;
+        }
+
+        public String toString() {
+            return charWriter.toString();
+        }
+
+        /**
+         * Adjust the Java Lines. This is necessary because the Java lines
+         * stored with the nodes are relative the beginning of this buffer and
+         * need to be adjusted when this buffer is inserted into the source.
+         */
+        public void adjustJavaLines(final int offset) {
+
+            if (node != null) {
+                adjustJavaLine(node, offset);
+            }
+
+            if (body != null) {
+                try {
+                    body.visit(new Node.Visitor() {
+
+                        public void doVisit(Node n) {
+                            adjustJavaLine(n, offset);
+                        }
+
+                        public void visit(Node.CustomTag n)
+                                throws JasperException {
+                            Node.Nodes b = n.getBody();
+                            if (b != null && !b.isGeneratedInBuffer()) {
+                                // Don't adjust lines for the nested tags that
+                                // are also generated in buffers, because the
+                                // adjustments will be done elsewhere.
+                                b.visit(this);
+                            }
+                        }
+                    });
+                } catch (JasperException ex) {
+                }
+            }
+        }
+
+        private static void adjustJavaLine(Node n, int offset) {
+            if (n.getBeginJavaLine() > 0) {
+                n.setBeginJavaLine(n.getBeginJavaLine() + offset);
+                n.setEndJavaLine(n.getEndJavaLine() + offset);
+            }
+        }
+    }
+
+    /**
+     * Keeps track of the generated Fragment Helper Class
+     */
+    private static class FragmentHelperClass {
+
+        private static class Fragment {
+            private GenBuffer genBuffer;
+
+            private int id;
+
+            public Fragment(int id, Node node) {
+                this.id = id;
+                genBuffer = new GenBuffer(null, node.getBody());
+            }
+
+            public GenBuffer getGenBuffer() {
+                return this.genBuffer;
+            }
+
+            public int getId() {
+                return this.id;
+            }
+        }
+
+        // True if the helper class should be generated.
+        private boolean used = false;
+
+        private ArrayList fragments = new ArrayList();
+
+        private String className;
+
+        // Buffer for entire helper class
+        private GenBuffer classBuffer = new GenBuffer();
+
+        public FragmentHelperClass(String className) {
+            this.className = className;
+        }
+
+        public String getClassName() {
+            return this.className;
+        }
+
+        public boolean isUsed() {
+            return this.used;
+        }
+
+        public void generatePreamble() {
+            ServletWriter out = this.classBuffer.getOut();
+            out.println();
+            out.pushIndent();
+            // Note: cannot be static, as we need to reference things like
+            // _jspx_meth_*
+            out.printil("private class " + className);
+            out.printil("    extends "
+                    + "org.apache.jasper.runtime.JspFragmentHelper");
+            out.printil("{");
+            out.pushIndent();
+            out
+                    .printil("private javax.servlet.jsp.tagext.JspTag _jspx_parent;");
+            out.printil("private int[] _jspx_push_body_count;");
+            out.println();
+            out.printil("public " + className
+                    + "( int discriminator, JspContext jspContext, "
+                    + "javax.servlet.jsp.tagext.JspTag _jspx_parent, "
+                    + "int[] _jspx_push_body_count ) {");
+            out.pushIndent();
+            out.printil("super( discriminator, jspContext, _jspx_parent );");
+            out.printil("this._jspx_parent = _jspx_parent;");
+            out.printil("this._jspx_push_body_count = _jspx_push_body_count;");
+            out.popIndent();
+            out.printil("}");
+        }
+
+        public Fragment openFragment(Node parent, String tagHandlerVar,
+                int methodNesting) throws JasperException {
+            Fragment result = new Fragment(fragments.size(), parent);
+            fragments.add(result);
+            this.used = true;
+            parent.setInnerClassName(className);
+
+            ServletWriter out = result.getGenBuffer().getOut();
+            out.pushIndent();
+            out.pushIndent();
+            // XXX - Returns boolean because if a tag is invoked from
+            // within this fragment, the Generator sometimes might
+            // generate code like "return true". This is ignored for now,
+            // meaning only the fragment is skipped. The JSR-152
+            // expert group is currently discussing what to do in this case.
+            // See comment in closeFragment()
+            if (methodNesting > 0) {
+                out.printin("public boolean invoke");
+            } else {
+                out.printin("public void invoke");
+            }
+            out.println(result.getId() + "( " + "JspWriter out ) ");
+            out.pushIndent();
+            // Note: Throwable required because methods like _jspx_meth_*
+            // throw Throwable.
+            out.printil("throws Throwable");
+            out.popIndent();
+            out.printil("{");
+            out.pushIndent();
+            generateLocalVariables(out, parent);
+
+            return result;
+        }
+
+        public void closeFragment(Fragment fragment, int methodNesting) {
+            ServletWriter out = fragment.getGenBuffer().getOut();
+            // XXX - See comment in openFragment()
+            if (methodNesting > 0) {
+                out.printil("return false;");
+            } else {
+                out.printil("return;");
+            }
+            out.popIndent();
+            out.printil("}");
+        }
+
+        public void generatePostamble() {
+            ServletWriter out = this.classBuffer.getOut();
+            // Generate all fragment methods:
+            for (int i = 0; i < fragments.size(); i++) {
+                Fragment fragment = (Fragment) fragments.get(i);
+                fragment.getGenBuffer().adjustJavaLines(out.getJavaLine() - 1);
+                out.printMultiLn(fragment.getGenBuffer().toString());
+            }
+
+            // Generate postamble:
+            out.printil("public void invoke( java.io.Writer writer )");
+            out.pushIndent();
+            out.printil("throws JspException");
+            out.popIndent();
+            out.printil("{");
+            out.pushIndent();
+            out.printil("JspWriter out = null;");
+            out.printil("if( writer != null ) {");
+            out.pushIndent();
+            out.printil("out = this.jspContext.pushBody(writer);");
+            out.popIndent();
+            out.printil("} else {");
+            out.pushIndent();
+            out.printil("out = this.jspContext.getOut();");
+            out.popIndent();
+            out.printil("}");
+            out.printil("try {");
+            out.pushIndent();
+            out.printil("this.jspContext.getELContext().putContext(JspContext.class,this.jspContext);");
+            out.printil("switch( this.discriminator ) {");
+            out.pushIndent();
+            for (int i = 0; i < fragments.size(); i++) {
+                out.printil("case " + i + ":");
+                out.pushIndent();
+                out.printil("invoke" + i + "( out );");
+                out.printil("break;");
+                out.popIndent();
+            }
+            out.popIndent();
+            out.printil("}"); // switch
+            out.popIndent();
+            out.printil("}"); // try
+            out.printil("catch( Throwable e ) {");
+            out.pushIndent();
+            out.printil("if (e instanceof SkipPageException)");
+            out.printil("    throw (SkipPageException) e;");
+            out.printil("throw new JspException( e );");
+            out.popIndent();
+            out.printil("}"); // catch
+            out.printil("finally {");
+            out.pushIndent();
+
+            out.printil("if( writer != null ) {");
+            out.pushIndent();
+            out.printil("this.jspContext.popBody();");
+            out.popIndent();
+            out.printil("}");
+
+            out.popIndent();
+            out.printil("}"); // finally
+            out.popIndent();
+            out.printil("}"); // invoke method
+            out.popIndent();
+            out.printil("}"); // helper class
+            out.popIndent();
+        }
+
+        public String toString() {
+            return classBuffer.toString();
+        }
+
+        public void adjustJavaLines(int offset) {
+            for (int i = 0; i < fragments.size(); i++) {
+                Fragment fragment = (Fragment) fragments.get(i);
+                fragment.getGenBuffer().adjustJavaLines(offset);
+            }
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java
new file mode 100644
index 0000000..de41ab5
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ImplicitTagLibraryInfo.java
@@ -0,0 +1,218 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+
+/**
+ * Class responsible for generating an implicit tag library containing tag
+ * handlers corresponding to the tag files in "/WEB-INF/tags/" or a 
+ * subdirectory of it.
+ *
+ * @author Jan Luehe
+ */
+class ImplicitTagLibraryInfo extends TagLibraryInfo {
+
+    private static final String WEB_INF_TAGS = "/WEB-INF/tags";
+    private static final String TAG_FILE_SUFFIX = ".tag";
+    private static final String TAGX_FILE_SUFFIX = ".tagx";
+    private static final String TAGS_SHORTNAME = "tags";
+    private static final String TLIB_VERSION = "1.0";
+    private static final String JSP_VERSION = "2.0";
+    private static final String IMPLICIT_TLD = "implicit.tld";
+
+    // Maps tag names to tag file paths
+    private Hashtable tagFileMap;
+
+    private ParserController pc;
+    private PageInfo pi;
+    private Vector vec;
+
+    /**
+     * Constructor.
+     */
+    public ImplicitTagLibraryInfo(JspCompilationContext ctxt,
+            ParserController pc,
+            PageInfo pi,
+            String prefix,
+            String tagdir,
+            ErrorDispatcher err) throws JasperException {
+        super(prefix, null);
+        this.pc = pc;
+        this.pi = pi;
+        this.tagFileMap = new Hashtable();
+        this.vec = new Vector();
+
+        // Implicit tag libraries have no functions:
+        this.functions = new FunctionInfo[0];
+
+        tlibversion = TLIB_VERSION;
+        jspversion = JSP_VERSION;
+
+        if (!tagdir.startsWith(WEB_INF_TAGS)) {
+            err.jspError("jsp.error.invalid.tagdir", tagdir);
+        }
+
+        // Determine the value of the <short-name> subelement of the
+        // "imaginary" <taglib> element
+        if (tagdir.equals(WEB_INF_TAGS)
+                || tagdir.equals( WEB_INF_TAGS + "/")) {
+            shortname = TAGS_SHORTNAME;
+        } else {
+            shortname = tagdir.substring(WEB_INF_TAGS.length());
+            shortname = shortname.replace('/', '-');
+        }
+
+        // Populate mapping of tag names to tag file paths
+        Set dirList = ctxt.getResourcePaths(tagdir);
+        if (dirList != null) {
+            Iterator it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = (String) it.next();
+                if (path.endsWith(TAG_FILE_SUFFIX)
+                        || path.endsWith(TAGX_FILE_SUFFIX)) {
+                    /*
+                     * Use the filename of the tag file, without the .tag or
+                     * .tagx extension, respectively, as the <name> subelement
+                     * of the "imaginary" <tag-file> element
+                     */
+                    String suffix = path.endsWith(TAG_FILE_SUFFIX) ?
+                            TAG_FILE_SUFFIX : TAGX_FILE_SUFFIX; 
+                    String tagName = path.substring(path.lastIndexOf("/") + 1);
+                    tagName = tagName.substring(0,
+                            tagName.lastIndexOf(suffix));
+                    tagFileMap.put(tagName, path);
+                } else if (path.endsWith(IMPLICIT_TLD)) {
+                    InputStream in = null;
+                    try {
+                        in = ctxt.getResourceAsStream(path);
+                        if (in != null) {
+                            
+                            // Add implicit TLD to dependency list
+                            if (pi != null) {
+                                pi.addDependant(path);
+                            }
+                            
+                            ParserUtils pu = new ParserUtils();
+                            TreeNode tld = pu.parseXMLDocument(uri, in);
+
+                            if (tld.findAttribute("version") != null) {
+                                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)) {
+                                    // Ignore
+                                } else {
+                                    // All other elements are invalid
+                                    err.jspError("jsp.error.invalid.implicit", path);
+                                }
+                            }
+                            try {
+                                double version = Double.parseDouble(this.jspversion);
+                                if (version < 2.0) {
+                                    err.jspError("jsp.error.invalid.implicit.version", path);
+                                }
+                            } catch (NumberFormatException e) {
+                                err.jspError("jsp.error.invalid.implicit.version", path);
+                            }
+                        }
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (Throwable t) {
+                            }
+                        }
+                    }
+                }
+            }
+        }        
+        
+    }
+
+    /**
+     * Checks to see if the given tag name maps to a tag file path,
+     * and if so, parses the corresponding tag file.
+     *
+     * @return The TagFileInfo corresponding to the given tag name, or null if
+     * the given tag name is not implemented as a tag file
+     */
+    public TagFileInfo getTagFile(String shortName) {
+
+        TagFileInfo tagFile = super.getTagFile(shortName);
+        if (tagFile == null) {
+            String path = (String) tagFileMap.get(shortName);
+            if (path == null) {
+                return null;
+            }
+
+            TagInfo tagInfo = null;
+            try {
+                tagInfo = TagFileProcessor.parseTagFileDirectives(pc,
+                        shortName,
+                        path,
+                        pc.getJspCompilationContext().getTagFileJarUrl(path),
+                        this);
+            } catch (JasperException je) {
+                throw new RuntimeException(je.toString(), je);
+            }
+
+            tagFile = new TagFileInfo(shortName, path, tagInfo);
+            vec.addElement(tagFile);
+
+            this.tagFiles = new TagFileInfo[vec.size()];
+            vec.copyInto(this.tagFiles);
+        }
+
+        return tagFile;
+    }
+
+    public TagLibraryInfo[] getTagLibraryInfos() {
+        Collection coll = pi.getTaglibs();
+        return (TagLibraryInfo[]) coll.toArray(new TagLibraryInfo[0]);
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java
new file mode 100644
index 0000000..fea1147
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JDTCompiler.java
@@ -0,0 +1,460 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.jasper.JasperException;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+
+/**
+ * JDT class compiler. This compiler will load source dependencies from the
+ * context classloader, reducing dramatically disk access during 
+ * the compilation process.
+ *
+ * @author Cocoon2
+ * @author Remy Maucherat
+ */
+public class JDTCompiler extends org.apache.jasper.compiler.Compiler {
+
+    
+    /** 
+     * Compile the servlet from .java file to .class file
+     */
+    protected void generateClass(String[] smap)
+        throws FileNotFoundException, JasperException, Exception {
+
+        long t1 = 0;
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+        
+        final String sourceFile = ctxt.getServletJavaFileName();
+        final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
+        String packageName = ctxt.getServletPackageName();
+        final String targetClassName = 
+            ((packageName.length() != 0) ? (packageName + ".") : "") 
+                    + ctxt.getServletClassName();
+        final ClassLoader classLoader = ctxt.getJspLoader();
+        String[] fileNames = new String[] {sourceFile};
+        String[] classNames = new String[] {targetClassName};
+        final ArrayList problemList = new ArrayList();
+        
+        class CompilationUnit implements ICompilationUnit {
+
+            String className;
+            String sourceFile;
+
+            CompilationUnit(String sourceFile, String className) {
+                this.className = className;
+                this.sourceFile = sourceFile;
+            }
+
+            public char[] getFileName() {
+                return sourceFile.toCharArray();
+            }
+            
+            public char[] getContents() {
+                char[] result = null;
+                FileInputStream is = null;
+                try {
+                    is = new FileInputStream(sourceFile);
+                    Reader reader = 
+                        new BufferedReader(new InputStreamReader(is, ctxt.getOptions().getJavaEncoding()));
+                    if (reader != null) {
+                        char[] chars = new char[8192];
+                        StringBuffer buf = new StringBuffer();
+                        int count;
+                        while ((count = reader.read(chars, 0, 
+                                                    chars.length)) > 0) {
+                            buf.append(chars, 0, count);
+                        }
+                        result = new char[buf.length()];
+                        buf.getChars(0, result.length, result, 0);
+                    }
+                } catch (IOException e) {
+                    log.error("Compilation error", e);
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException exc) {
+                            // Ignore
+                        }
+                    }
+                }
+                return result;
+            }
+            
+            public char[] getMainTypeName() {
+                int dot = className.lastIndexOf('.');
+                if (dot > 0) {
+                    return className.substring(dot + 1).toCharArray();
+                }
+                return className.toCharArray();
+            }
+            
+            public char[][] getPackageName() {
+                StringTokenizer izer = 
+                    new StringTokenizer(className, ".");
+                char[][] result = new char[izer.countTokens()-1][];
+                for (int i = 0; i < result.length; i++) {
+                    String tok = izer.nextToken();
+                    result[i] = tok.toCharArray();
+                }
+                return result;
+            }
+        }
+
+        final INameEnvironment env = new INameEnvironment() {
+
+                public NameEnvironmentAnswer 
+                    findType(char[][] compoundTypeName) {
+                    String result = "";
+                    String sep = "";
+                    for (int i = 0; i < compoundTypeName.length; i++) {
+                        result += sep;
+                        result += new String(compoundTypeName[i]);
+                        sep = ".";
+                    }
+                    return findType(result);
+                }
+
+                public NameEnvironmentAnswer 
+                    findType(char[] typeName, 
+                             char[][] packageName) {
+                        String result = "";
+                        String sep = "";
+                        for (int i = 0; i < packageName.length; i++) {
+                            result += sep;
+                            result += new String(packageName[i]);
+                            sep = ".";
+                        }
+                        result += sep;
+                        result += new String(typeName);
+                        return findType(result);
+                }
+                
+                private NameEnvironmentAnswer findType(String className) {
+
+                    InputStream is = null;
+                    try {
+                        if (className.equals(targetClassName)) {
+                            ICompilationUnit compilationUnit = 
+                                new CompilationUnit(sourceFile, className);
+                            return 
+                                new NameEnvironmentAnswer(compilationUnit, null);
+                        }
+                        String resourceName = 
+                            className.replace('.', '/') + ".class";
+                        is = classLoader.getResourceAsStream(resourceName);
+                        if (is != null) {
+                            byte[] classBytes;
+                            byte[] buf = new byte[8192];
+                            ByteArrayOutputStream baos = 
+                                new ByteArrayOutputStream(buf.length);
+                            int count;
+                            while ((count = is.read(buf, 0, buf.length)) > 0) {
+                                baos.write(buf, 0, count);
+                            }
+                            baos.flush();
+                            classBytes = baos.toByteArray();
+                            char[] fileName = className.toCharArray();
+                            ClassFileReader classFileReader = 
+                                new ClassFileReader(classBytes, fileName, 
+                                                    true);
+                            return 
+                                new NameEnvironmentAnswer(classFileReader, null);
+                        }
+                    } catch (IOException exc) {
+                        log.error("Compilation error", exc);
+                    } catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException exc) {
+                        log.error("Compilation error", exc);
+                    } finally {
+                        if (is != null) {
+                            try {
+                                is.close();
+                            } catch (IOException exc) {
+                                // Ignore
+                            }
+                        }
+                    }
+                    return null;
+                }
+
+                private boolean isPackage(String result) {
+                    if (result.equals(targetClassName)) {
+                        return false;
+                    }
+                    String resourceName = result.replace('.', '/') + ".class";
+                    InputStream is = 
+                        classLoader.getResourceAsStream(resourceName);
+                    return is == null;
+                }
+
+                public boolean isPackage(char[][] parentPackageName, 
+                                         char[] packageName) {
+                    String result = "";
+                    String sep = "";
+                    if (parentPackageName != null) {
+                        for (int i = 0; i < parentPackageName.length; i++) {
+                            result += sep;
+                            String str = new String(parentPackageName[i]);
+                            result += str;
+                            sep = ".";
+                        }
+                    }
+                    String str = new String(packageName);
+                    if (Character.isUpperCase(str.charAt(0))) {
+                        if (!isPackage(result)) {
+                            return false;
+                        }
+                    }
+                    result += sep;
+                    result += str;
+                    return isPackage(result);
+                }
+
+                public void cleanup() {
+                }
+
+            };
+
+        final IErrorHandlingPolicy policy = 
+            DefaultErrorHandlingPolicies.proceedWithAllProblems();
+
+        final Map settings = new HashMap();
+        settings.put(CompilerOptions.OPTION_LineNumberAttribute,
+                     CompilerOptions.GENERATE);
+        settings.put(CompilerOptions.OPTION_SourceFileAttribute,
+                     CompilerOptions.GENERATE);
+        settings.put(CompilerOptions.OPTION_ReportDeprecation,
+                     CompilerOptions.IGNORE);
+        if (ctxt.getOptions().getJavaEncoding() != null) {
+            settings.put(CompilerOptions.OPTION_Encoding,
+                    ctxt.getOptions().getJavaEncoding());
+        }
+        if (ctxt.getOptions().getClassDebugInfo()) {
+            settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
+                         CompilerOptions.GENERATE);
+        }
+
+        // Source JVM
+        if(ctxt.getOptions().getCompilerSourceVM() != null) {
+            String opt = ctxt.getOptions().getCompilerSourceVM();
+            if(opt.equals("1.1")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_1);
+            } else if(opt.equals("1.2")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_2);
+            } else if(opt.equals("1.3")) { 
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_3);
+            } else if(opt.equals("1.4")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_4);
+            } else if(opt.equals("1.5")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_5);
+            } else if(opt.equals("1.6")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_6);
+            } else if(opt.equals("1.7")) {
+                settings.put(CompilerOptions.OPTION_Source,
+                             CompilerOptions.VERSION_1_7);
+            } else {
+                log.warn("Unknown source VM " + opt + " ignored.");
+                settings.put(CompilerOptions.OPTION_Source,
+                        CompilerOptions.VERSION_1_5);
+            }
+        } else {
+            // Default to 1.5
+            settings.put(CompilerOptions.OPTION_Source,
+                    CompilerOptions.VERSION_1_5);
+        }
+        
+        // Target JVM
+        if(ctxt.getOptions().getCompilerTargetVM() != null) {
+            String opt = ctxt.getOptions().getCompilerTargetVM();
+            if(opt.equals("1.1")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_1);
+            } else if(opt.equals("1.2")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_2);
+            } else if(opt.equals("1.3")) { 
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_3);
+            } else if(opt.equals("1.4")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_4);
+            } else if(opt.equals("1.5")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_5);
+                settings.put(CompilerOptions.OPTION_Compliance,
+                        CompilerOptions.VERSION_1_5);
+            } else if(opt.equals("1.6")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_6);
+                settings.put(CompilerOptions.OPTION_Compliance,
+                        CompilerOptions.VERSION_1_6);
+            } else if(opt.equals("1.7")) {
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                             CompilerOptions.VERSION_1_7);
+                settings.put(CompilerOptions.OPTION_Compliance,
+                        CompilerOptions.VERSION_1_7);
+            } else {
+                log.warn("Unknown target VM " + opt + " ignored.");
+                settings.put(CompilerOptions.OPTION_TargetPlatform,
+                        CompilerOptions.VERSION_1_5);
+            }
+        } else {
+            // Default to 1.5
+            settings.put(CompilerOptions.OPTION_TargetPlatform,
+                    CompilerOptions.VERSION_1_5);
+            settings.put(CompilerOptions.OPTION_Compliance,
+                    CompilerOptions.VERSION_1_5);
+        }
+
+        final IProblemFactory problemFactory = 
+            new DefaultProblemFactory(Locale.getDefault());
+        
+        final ICompilerRequestor requestor = new ICompilerRequestor() {
+                public void acceptResult(CompilationResult result) {
+                    try {
+                        if (result.hasProblems()) {
+                            IProblem[] problems = result.getProblems();
+                            for (int i = 0; i < problems.length; i++) {
+                                IProblem problem = problems[i];
+                                if (problem.isError()) {
+                                    String name = 
+                                        new String(problems[i].getOriginatingFileName());
+                                    try {
+                                        problemList.add(ErrorDispatcher.createJavacError
+                                                (name, pageNodes, new StringBuffer(problem.getMessage()), 
+                                                        problem.getSourceLineNumber(), ctxt));
+                                    } catch (JasperException e) {
+                                        log.error("Error visiting node", e);
+                                    }
+                                }
+                            }
+                        }
+                        if (problemList.isEmpty()) {
+                            ClassFile[] classFiles = result.getClassFiles();
+                            for (int i = 0; i < classFiles.length; i++) {
+                                ClassFile classFile = classFiles[i];
+                                char[][] compoundName = 
+                                    classFile.getCompoundName();
+                                String className = "";
+                                String sep = "";
+                                for (int j = 0; 
+                                     j < compoundName.length; j++) {
+                                    className += sep;
+                                    className += new String(compoundName[j]);
+                                    sep = ".";
+                                }
+                                byte[] bytes = classFile.getBytes();
+                                String outFile = outputDir + "/" + 
+                                    className.replace('.', '/') + ".class";
+                                FileOutputStream fout = 
+                                    new FileOutputStream(outFile);
+                                BufferedOutputStream bos = 
+                                    new BufferedOutputStream(fout);
+                                bos.write(bytes);
+                                bos.close();
+                            }
+                        }
+                    } catch (IOException exc) {
+                        log.error("Compilation error", exc);
+                    }
+                }
+            };
+
+        ICompilationUnit[] compilationUnits = 
+            new ICompilationUnit[classNames.length];
+        for (int i = 0; i < compilationUnits.length; i++) {
+            String className = classNames[i];
+            compilationUnits[i] = new CompilationUnit(fileNames[i], className);
+        }
+        Compiler compiler = new Compiler(env,
+                                         policy,
+                                         settings,
+                                         requestor,
+                                         problemFactory,
+                                         true);
+        compiler.compile(compilationUnits);
+
+        if (!ctxt.keepGenerated()) {
+            File javaFile = new File(ctxt.getServletJavaFileName());
+            javaFile.delete();
+        }
+    
+        if (!problemList.isEmpty()) {
+            JavacErrorDetail[] jeds = 
+                (JavacErrorDetail[]) problemList.toArray(new JavacErrorDetail[0]);
+            errDispatcher.javacError(jeds);
+        }
+        
+        if( log.isDebugEnabled() ) {
+            long t2=System.currentTimeMillis();
+            log.debug("Compiled " + ctxt.getServletJavaFileName() + " "
+                      + (t2-t1) + "ms");
+        }
+
+        if (ctxt.isPrototypeMode()) {
+            return;
+        }
+
+        // JSR45 Support
+        if (! options.isSmapSuppressed()) {
+            SmapUtil.installSmap(smap);
+        }
+        
+    }
+    
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java
new file mode 100644
index 0000000..8586d5e
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JasperTagInfo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.jasper.compiler;
+
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * TagInfo extension used by tag handlers that are implemented via tag files.
+ * This class provides access to the name of the Map used to store the
+ * dynamic attribute names and values passed to the custom action invocation.
+ * This information is used by the code generator.
+ */
+class JasperTagInfo extends TagInfo {
+
+    private String dynamicAttrsMapName;
+
+    public JasperTagInfo(String tagName,
+			 String tagClassName,
+			 String bodyContent,
+			 String infoString,
+			 TagLibraryInfo taglib,
+			 TagExtraInfo tagExtraInfo,
+			 TagAttributeInfo[] attributeInfo,
+			 String displayName,
+			 String smallIcon,
+			 String largeIcon,
+			 TagVariableInfo[] tvi,
+			 String mapName) {
+
+	super(tagName, tagClassName, bodyContent, infoString, taglib,
+	      tagExtraInfo, attributeInfo, displayName, smallIcon, largeIcon,
+	      tvi);
+	this.dynamicAttrsMapName = mapName;
+    }
+
+    public String getDynamicAttributesMapName() {
+	return dynamicAttrsMapName;
+    }
+
+    public boolean hasDynamicAttributes() {
+        return dynamicAttrsMapName != null;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java
new file mode 100644
index 0000000..cf2daff
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JavacErrorDetail.java
@@ -0,0 +1,232 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Class providing details about a javac compilation error.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public class JavacErrorDetail {
+
+    private String javaFileName;
+    private int javaLineNum;
+    private String jspFileName;
+    private int jspBeginLineNum;
+    private StringBuffer errMsg;
+    private String jspExtract = null;
+
+    /**
+     * Constructor.
+     *
+     * @param javaFileName The name of the Java file in which the 
+     * compilation error occurred
+     * @param javaLineNum The compilation error line number
+     * @param errMsg The compilation error message
+     */
+    public JavacErrorDetail(String javaFileName,
+                            int javaLineNum,
+                            StringBuffer errMsg) {
+
+        this.javaFileName = javaFileName;
+        this.javaLineNum = javaLineNum;
+        this.errMsg = errMsg;
+        this.jspBeginLineNum = -1;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param javaFileName The name of the Java file in which the 
+     * compilation error occurred
+     * @param javaLineNum The compilation error line number
+     * @param jspFileName The name of the JSP file from which the Java source
+     * file was generated
+     * @param jspBeginLineNum The start line number of the JSP element
+     * responsible for the compilation error
+     * @param errMsg The compilation error message
+     */
+    public JavacErrorDetail(String javaFileName,
+                            int javaLineNum,
+                            String jspFileName,
+                            int jspBeginLineNum,
+                            StringBuffer errMsg) {
+
+        this(javaFileName, javaLineNum, jspFileName, jspBeginLineNum, errMsg,
+                null);
+    }
+
+    public JavacErrorDetail(String javaFileName,
+            int javaLineNum,
+            String jspFileName,
+            int jspBeginLineNum,
+            StringBuffer errMsg,
+            JspCompilationContext ctxt) {
+        
+        this(javaFileName, javaLineNum, errMsg);
+        this.jspFileName = jspFileName;
+        this.jspBeginLineNum = jspBeginLineNum;
+        
+        if (jspBeginLineNum > 0 && ctxt != null) {
+            InputStream is = null;
+            FileInputStream  fis = null;
+            
+            try {
+                // Read both files in, so we can inspect them
+                is = ctxt.getResourceAsStream(jspFileName);
+                String[] jspLines = readFile(is);
+    
+                fis = new FileInputStream(ctxt.getServletJavaFileName());
+                String[] javaLines = readFile(fis);
+    
+                // If the line contains the opening of a multi-line scriptlet
+                // block, then the JSP line number we got back is probably
+                // faulty.  Scan forward to match the java line...
+                if (jspLines[jspBeginLineNum-1].lastIndexOf("<%") >
+                    jspLines[jspBeginLineNum-1].lastIndexOf("%>")) {
+                    String javaLine = javaLines[javaLineNum-1].trim();
+    
+                    for (int i=jspBeginLineNum-1; i<jspLines.length; i++) {
+                        if (jspLines[i].indexOf(javaLine) != -1) {
+                            // Update jsp line number
+                            this.jspBeginLineNum = i+1;
+                            break;
+                        }
+                    }
+                }
+    
+                // copy out a fragment of JSP to display to the user
+                StringBuffer fragment = new StringBuffer(1024);
+                int startIndex = Math.max(0, this.jspBeginLineNum-1-3);
+                int endIndex = Math.min(
+                        jspLines.length-1, this.jspBeginLineNum-1+3);
+    
+                for (int i=startIndex;i<=endIndex; ++i) {
+                    fragment.append(i+1);
+                    fragment.append(": ");
+                    fragment.append(jspLines[i]);
+                    fragment.append("\n");
+                }
+                jspExtract = fragment.toString();
+    
+            } catch (IOException ioe) {
+                // Can't read files - ignore
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException ioe) {
+                        // Ignore
+                    }
+                }
+                if (fis != null) {
+                    try {
+                        fis.close();
+                    } catch (IOException ioe) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the name of the Java source file in which the compilation error
+     * occurred.
+     *
+     * @return Java source file name
+     */
+    public String getJavaFileName() {
+        return this.javaFileName;
+    }
+
+    /**
+     * Gets the compilation error line number.
+     * 
+     * @return Compilation error line number
+     */
+    public int getJavaLineNumber() {
+        return this.javaLineNum;
+    }
+
+    /**
+     * Gets the name of the JSP file from which the Java source file was
+     * generated.
+     *
+     * @return JSP file from which the Java source file was generated.
+     */
+    public String getJspFileName() {
+        return this.jspFileName;
+    }
+
+    /**
+     * Gets the start line number (in the JSP file) of the JSP element
+     * responsible for the compilation error.
+     *
+     * @return Start line number of the JSP element responsible for the
+     * compilation error
+     */
+    public int getJspBeginLineNumber() {
+        return this.jspBeginLineNum;
+    }
+
+    /**
+     * Gets the compilation error message.
+     *
+     * @return Compilation error message
+     */
+    public String getErrorMessage() {
+        return this.errMsg.toString();
+    }
+    
+    /**
+     * Gets the extract of the JSP that corresponds to this message.
+     *
+     * @return Extract of JSP where error occurred
+     */
+    public String getJspExtract() {
+        return this.jspExtract;
+    }
+    
+    /**
+     * Reads a text file from an input stream into a String[]. Used to read in
+     * the JSP and generated Java file when generating error messages.
+     */
+    private String[] readFile(InputStream s) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(s));
+        List lines = new ArrayList();
+        String line;
+
+        while ( (line = reader.readLine()) != null ) {
+            lines.add(line);
+        }
+
+        return (String[]) lines.toArray( new String[lines.size()] );
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspConfig.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspConfig.java
new file mode 100644
index 0000000..7a9542f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspConfig.java
@@ -0,0 +1,531 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Vector;
+import java.net.URL;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.xml.sax.InputSource;
+
+/**
+ * Handles the jsp-config element in WEB_INF/web.xml.  This is used
+ * for specifying the JSP configuration information on a JSP page
+ *
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ */
+
+public class JspConfig {
+
+    private static final String WEB_XML = "/WEB-INF/web.xml";
+
+    // Logger
+    private Log log = LogFactory.getLog(JspConfig.class);
+
+    private Vector jspProperties = null;
+    private ServletContext ctxt;
+    private boolean initialized = false;
+
+    private String defaultIsXml = null;		// unspecified
+    private String defaultIsELIgnored = null;	// unspecified
+    private String defaultIsScriptingInvalid = null;
+    private String defaultDeferedSyntaxAllowedAsLiteral = null;
+    private String defaultTrimDirectiveWhitespaces = null;
+    private JspProperty defaultJspProperty;
+
+    public JspConfig(ServletContext ctxt) {
+        this.ctxt = ctxt;
+    }
+
+    private double getVersion(TreeNode webApp) {
+        String v = webApp.findAttribute("version");
+        if (v != null) {
+            try {
+                return Double.parseDouble(v);
+            } catch (NumberFormatException e) {
+            }
+        }
+        return 2.3;
+    }
+
+    private void processWebDotXml(ServletContext ctxt) throws JasperException {
+
+        InputStream is = null;
+
+        try {
+            URL uri = ctxt.getResource(WEB_XML);
+            if (uri == null) {
+                // no web.xml
+                return;
+            }
+
+            is = uri.openStream();
+            InputSource ip = new InputSource(is);
+            ip.setSystemId(uri.toExternalForm()); 
+
+            ParserUtils pu = new ParserUtils();
+            TreeNode webApp = pu.parseXMLDocument(WEB_XML, ip);
+
+            if (webApp == null
+                    || getVersion(webApp) < 2.4) {
+                defaultIsELIgnored = "true";
+                return;
+            }
+            TreeNode jspConfig = webApp.findChild("jsp-config");
+            if (jspConfig == null) {
+                return;
+            }
+
+            jspProperties = new Vector();
+            Iterator jspPropertyList = jspConfig.findChildren("jsp-property-group");
+            while (jspPropertyList.hasNext()) {
+
+                TreeNode element = (TreeNode) jspPropertyList.next();
+                Iterator list = element.findChildren();
+
+                Vector urlPatterns = new Vector();
+                String pageEncoding = null;
+                String scriptingInvalid = null;
+                String elIgnored = null;
+                String isXml = null;
+                Vector includePrelude = new Vector();
+                Vector includeCoda = new Vector();
+                String deferredSyntaxAllowedAsLiteral = null;
+                String trimDirectiveWhitespaces = null;
+
+                while (list.hasNext()) {
+
+                    element = (TreeNode) list.next();
+                    String tname = element.getName();
+
+                    if ("url-pattern".equals(tname))
+                        urlPatterns.addElement( element.getBody() );
+                    else if ("page-encoding".equals(tname))
+                        pageEncoding = element.getBody();
+                    else if ("is-xml".equals(tname))
+                        isXml = element.getBody();
+                    else if ("el-ignored".equals(tname))
+                        elIgnored = element.getBody();
+                    else if ("scripting-invalid".equals(tname))
+                        scriptingInvalid = element.getBody();
+                    else if ("include-prelude".equals(tname))
+                        includePrelude.addElement(element.getBody());
+                    else if ("include-coda".equals(tname))
+                        includeCoda.addElement(element.getBody());
+                    else if ("deferred-syntax-allowed-as-literal".equals(tname))
+                        deferredSyntaxAllowedAsLiteral = element.getBody();
+                    else if ("trim-directive-whitespaces".equals(tname))
+                        trimDirectiveWhitespaces = element.getBody();
+                }
+
+                if (urlPatterns.size() == 0) {
+                    continue;
+                }
+
+                // Add one JspPropertyGroup for each URL Pattern.  This makes
+                // the matching logic easier.
+                for( int p = 0; p < urlPatterns.size(); p++ ) {
+                    String urlPattern = (String)urlPatterns.elementAt( p );
+                    String path = null;
+                    String extension = null;
+
+                    if (urlPattern.indexOf('*') < 0) {
+                        // Exact match
+                        path = urlPattern;
+                    } else {
+                        int i = urlPattern.lastIndexOf('/');
+                        String file;
+                        if (i >= 0) {
+                            path = urlPattern.substring(0,i+1);
+                            file = urlPattern.substring(i+1);
+                        } else {
+                            file = urlPattern;
+                        }
+
+                        // pattern must be "*", or of the form "*.jsp"
+                        if (file.equals("*")) {
+                            extension = "*";
+                        } else if (file.startsWith("*.")) {
+                            extension = file.substring(file.indexOf('.')+1);
+                        }
+
+                        // The url patterns are reconstructed as the follwoing:
+                        // path != null, extension == null:  / or /foo/bar.ext
+                        // path == null, extension != null:  *.ext
+                        // path != null, extension == "*":   /foo/*
+                        boolean isStar = "*".equals(extension);
+                        if ((path == null && (extension == null || isStar))
+                                || (path != null && !isStar)) {
+                            if (log.isWarnEnabled()) {
+                                log.warn(Localizer.getMessage(
+                                        "jsp.warning.bad.urlpattern.propertygroup",
+                                        urlPattern));
+                            }
+                            continue;
+                        }
+                    }
+
+                    JspProperty property = new JspProperty(isXml,
+                            elIgnored,
+                            scriptingInvalid,
+                            pageEncoding,
+                            includePrelude,
+                            includeCoda,
+                            deferredSyntaxAllowedAsLiteral,
+                            trimDirectiveWhitespaces);
+                    JspPropertyGroup propertyGroup =
+                        new JspPropertyGroup(path, extension, property);
+
+                    jspProperties.addElement(propertyGroup);
+                }
+            }
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (Throwable t) {}
+            }
+        }
+    }
+
+    private void init() throws JasperException {
+
+        if (!initialized) {
+            processWebDotXml(ctxt);
+            defaultJspProperty = new JspProperty(defaultIsXml,
+                    defaultIsELIgnored,
+                    defaultIsScriptingInvalid,
+                    null, null, null, defaultDeferedSyntaxAllowedAsLiteral, 
+                    defaultTrimDirectiveWhitespaces);
+            initialized = true;
+        }
+    }
+
+    /**
+     * Select the property group that has more restrictive url-pattern.
+     * In case of tie, select the first.
+     */
+    private JspPropertyGroup selectProperty(JspPropertyGroup prev,
+            JspPropertyGroup curr) {
+        if (prev == null) {
+            return curr;
+        }
+        if (prev.getExtension() == null) {
+            // exact match
+            return prev;
+        }
+        if (curr.getExtension() == null) {
+            // exact match
+            return curr;
+        }
+        String prevPath = prev.getPath();
+        String currPath = curr.getPath();
+        if (prevPath == null && currPath == null) {
+            // Both specifies a *.ext, keep the first one
+            return prev;
+        }
+        if (prevPath == null && currPath != null) {
+            return curr;
+        }
+        if (prevPath != null && currPath == null) {
+            return prev;
+        }
+        if (prevPath.length() >= currPath.length()) {
+            return prev;
+        }
+        return curr;
+    }
+
+
+    /**
+     * Find a property that best matches the supplied resource.
+     * @param uri the resource supplied.
+     * @return a JspProperty indicating the best match, or some default.
+     */
+    public JspProperty findJspProperty(String uri) throws JasperException {
+
+        init();
+
+        // JSP Configuration settings do not apply to tag files	    
+        if (jspProperties == null || uri.endsWith(".tag")
+                || uri.endsWith(".tagx")) {
+            return defaultJspProperty;
+        }
+
+        String uriPath = null;
+        int index = uri.lastIndexOf('/');
+        if (index >=0 ) {
+            uriPath = uri.substring(0, index+1);
+        }
+        String uriExtension = null;
+        index = uri.lastIndexOf('.');
+        if (index >=0) {
+            uriExtension = uri.substring(index+1);
+        }
+
+        Vector includePreludes = new Vector();
+        Vector includeCodas = new Vector();
+
+        JspPropertyGroup isXmlMatch = null;
+        JspPropertyGroup elIgnoredMatch = null;
+        JspPropertyGroup scriptingInvalidMatch = null;
+        JspPropertyGroup pageEncodingMatch = null;
+        JspPropertyGroup deferedSyntaxAllowedAsLiteralMatch = null;
+        JspPropertyGroup trimDirectiveWhitespacesMatch = null;
+
+        Iterator iter = jspProperties.iterator();
+        while (iter.hasNext()) {
+
+            JspPropertyGroup jpg = (JspPropertyGroup) iter.next();
+            JspProperty jp = jpg.getJspProperty();
+
+            // (arrays will be the same length)
+            String extension = jpg.getExtension();
+            String path = jpg.getPath();
+
+            if (extension == null) {
+                // exact match pattern: /a/foo.jsp
+                if (!uri.equals(path)) {
+                    // not matched;
+                    continue;
+                }
+            } else {
+                // Matching patterns *.ext or /p/*
+                if (path != null && uriPath != null &&
+                        ! uriPath.startsWith(path)) {
+                    // not matched
+                    continue;
+                }
+                if (!extension.equals("*") &&
+                        !extension.equals(uriExtension)) {
+                    // not matched
+                    continue;
+                }
+            }
+            // We have a match
+            // Add include-preludes and include-codas
+            if (jp.getIncludePrelude() != null) {
+                includePreludes.addAll(jp.getIncludePrelude());
+            }
+            if (jp.getIncludeCoda() != null) {
+                includeCodas.addAll(jp.getIncludeCoda());
+            }
+
+            // If there is a previous match for the same property, remember
+            // the one that is more restrictive.
+            if (jp.isXml() != null) {
+                isXmlMatch = selectProperty(isXmlMatch, jpg);
+            }
+            if (jp.isELIgnored() != null) {
+                elIgnoredMatch = selectProperty(elIgnoredMatch, jpg);
+            }
+            if (jp.isScriptingInvalid() != null) {
+                scriptingInvalidMatch =
+                    selectProperty(scriptingInvalidMatch, jpg);
+            }
+            if (jp.getPageEncoding() != null) {
+                pageEncodingMatch = selectProperty(pageEncodingMatch, jpg);
+            }
+            if (jp.isDeferedSyntaxAllowedAsLiteral() != null) {
+                deferedSyntaxAllowedAsLiteralMatch =
+                    selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg);
+            }
+            if (jp.isTrimDirectiveWhitespaces() != null) {
+                trimDirectiveWhitespacesMatch =
+                    selectProperty(trimDirectiveWhitespacesMatch, jpg);
+            }
+        }
+
+
+        String isXml = defaultIsXml;
+        String isELIgnored = defaultIsELIgnored;
+        String isScriptingInvalid = defaultIsScriptingInvalid;
+        String pageEncoding = null;
+        String isDeferedSyntaxAllowedAsLiteral = defaultDeferedSyntaxAllowedAsLiteral;
+        String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces;
+
+        if (isXmlMatch != null) {
+            isXml = isXmlMatch.getJspProperty().isXml();
+        }
+        if (elIgnoredMatch != null) {
+            isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored();
+        }
+        if (scriptingInvalidMatch != null) {
+            isScriptingInvalid =
+                scriptingInvalidMatch.getJspProperty().isScriptingInvalid();
+        }
+        if (pageEncodingMatch != null) {
+            pageEncoding = pageEncodingMatch.getJspProperty().getPageEncoding();
+        }
+        if (deferedSyntaxAllowedAsLiteralMatch != null) {
+            isDeferedSyntaxAllowedAsLiteral =
+                deferedSyntaxAllowedAsLiteralMatch.getJspProperty().isDeferedSyntaxAllowedAsLiteral();
+        }
+        if (trimDirectiveWhitespacesMatch != null) {
+            isTrimDirectiveWhitespaces =
+                trimDirectiveWhitespacesMatch.getJspProperty().isTrimDirectiveWhitespaces();
+        }
+
+        return new JspProperty(isXml, isELIgnored, isScriptingInvalid,
+                pageEncoding, includePreludes, includeCodas, 
+                isDeferedSyntaxAllowedAsLiteral, isTrimDirectiveWhitespaces);
+    }
+
+    /**
+     * To find out if an uri matches an url pattern in jsp config.  If so,
+     * then the uri is a JSP page.  This is used primarily for jspc.
+     */
+    public boolean isJspPage(String uri) throws JasperException {
+
+        init();
+        if (jspProperties == null) {
+            return false;
+        }
+
+        String uriPath = null;
+        int index = uri.lastIndexOf('/');
+        if (index >=0 ) {
+            uriPath = uri.substring(0, index+1);
+        }
+        String uriExtension = null;
+        index = uri.lastIndexOf('.');
+        if (index >=0) {
+            uriExtension = uri.substring(index+1);
+        }
+
+        Iterator iter = jspProperties.iterator();
+        while (iter.hasNext()) {
+
+            JspPropertyGroup jpg = (JspPropertyGroup) iter.next();
+            JspProperty jp = jpg.getJspProperty();
+
+            String extension = jpg.getExtension();
+            String path = jpg.getPath();
+
+            if (extension == null) {
+                if (uri.equals(path)) {
+                    // There is an exact match
+                    return true;
+                }
+            } else {
+                if ((path == null || path.equals(uriPath)) &&
+                        (extension.equals("*") || extension.equals(uriExtension))) {
+                    // Matches *, *.ext, /p/*, or /p/*.ext
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    static class JspPropertyGroup {
+        private String path;
+        private String extension;
+        private JspProperty jspProperty;
+
+        JspPropertyGroup(String path, String extension,
+                JspProperty jspProperty) {
+            this.path = path;
+            this.extension = extension;
+            this.jspProperty = jspProperty;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+
+        public JspProperty getJspProperty() {
+            return jspProperty;
+        }
+    }
+
+    static public class JspProperty {
+
+        private String isXml;
+        private String elIgnored;
+        private String scriptingInvalid;
+        private String pageEncoding;
+        private Vector includePrelude;
+        private Vector includeCoda;
+        private String deferedSyntaxAllowedAsLiteral;
+        private String trimDirectiveWhitespaces;
+
+        public JspProperty(String isXml, String elIgnored,
+                String scriptingInvalid, String pageEncoding,
+                Vector includePrelude, Vector includeCoda,
+                String deferedSyntaxAllowedAsLiteral, 
+                String trimDirectiveWhitespaces) {
+
+            this.isXml = isXml;
+            this.elIgnored = elIgnored;
+            this.scriptingInvalid = scriptingInvalid;
+            this.pageEncoding = pageEncoding;
+            this.includePrelude = includePrelude;
+            this.includeCoda = includeCoda;
+            this.deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral;
+            this.trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+        }
+
+        public String isXml() {
+            return isXml;
+        }
+
+        public String isELIgnored() {
+            return elIgnored;
+        }
+
+        public String isScriptingInvalid() {
+            return scriptingInvalid;
+        }
+
+        public String getPageEncoding() {
+            return pageEncoding;
+        }
+
+        public Vector getIncludePrelude() {
+            return includePrelude;
+        }
+
+        public Vector getIncludeCoda() {
+            return includeCoda;
+        }
+        
+        public String isDeferedSyntaxAllowedAsLiteral() {
+            return deferedSyntaxAllowedAsLiteral;
+        }
+        
+        public String isTrimDirectiveWhitespaces() {
+            return trimDirectiveWhitespaces;
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java
new file mode 100644
index 0000000..3acd9d5
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspDocumentParser.java
@@ -0,0 +1,1453 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarFile;
+
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Class implementing a parser for a JSP document, that is, a JSP page in XML
+ * syntax.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+
+class JspDocumentParser
+    extends DefaultHandler
+    implements LexicalHandler, TagConstants {
+
+    private static final String JSP_VERSION = "version";
+    private static final String LEXICAL_HANDLER_PROPERTY =
+        "http://xml.org/sax/properties/lexical-handler";
+    private static final String JSP_URI = "http://java.sun.com/JSP/Page";
+
+    private static final EnableDTDValidationException ENABLE_DTD_VALIDATION_EXCEPTION =
+        new EnableDTDValidationException(
+            "jsp.error.enable_dtd_validation",
+            null);
+
+    private ParserController parserController;
+    private JspCompilationContext ctxt;
+    private PageInfo pageInfo;
+    private String path;
+    private StringBuffer charBuffer;
+
+    // Node representing the XML element currently being parsed
+    private Node current;
+
+    /*
+     * Outermost (in the nesting hierarchy) node whose body is declared to be
+     * scriptless. If a node's body is declared to be scriptless, all its
+     * nested nodes must be scriptless, too.
+     */ 
+    private Node scriptlessBodyNode;
+
+    private Locator locator;
+
+    //Mark representing the start of the current element.  Note
+    //that locator.getLineNumber() and locator.getColumnNumber()
+    //return the line and column numbers for the character
+    //immediately _following_ the current element.  The underlying
+    //XMl parser eats white space that is not part of character
+    //data, so for Nodes that are not created from character data,
+    //this is the best we can do.  But when we parse character data,
+    //we get an accurate starting location by starting with startMark
+    //as set by the previous element, and updating it as we advance
+    //through the characters.
+    private Mark startMark;
+
+    // Flag indicating whether we are inside DTD declarations
+    private boolean inDTD;
+
+    private boolean isValidating;
+
+    private ErrorDispatcher err;
+    private boolean isTagFile;
+    private boolean directivesOnly;
+    private boolean isTop;
+
+    // Nesting level of Tag dependent bodies
+    private int tagDependentNesting = 0;
+    // Flag set to delay incrmenting tagDependentNesting until jsp:body
+    // is first encountered
+    private boolean tagDependentPending = false;
+
+    /*
+     * Constructor
+     */
+    public JspDocumentParser(
+        ParserController pc,
+        String path,
+        boolean isTagFile,
+        boolean directivesOnly) {
+        this.parserController = pc;
+        this.ctxt = pc.getJspCompilationContext();
+        this.pageInfo = pc.getCompiler().getPageInfo();
+        this.err = pc.getCompiler().getErrorDispatcher();
+        this.path = path;
+        this.isTagFile = isTagFile;
+        this.directivesOnly = directivesOnly;
+        this.isTop = true;
+    }
+
+    /*
+     * Parses a JSP document by responding to SAX events.
+     *
+     * @throws JasperException
+     */
+    public static Node.Nodes parse(
+        ParserController pc,
+        String path,
+        JarFile jarFile,
+        Node parent,
+        boolean isTagFile,
+        boolean directivesOnly,
+        String pageEnc,
+        String jspConfigPageEnc,
+        boolean isEncodingSpecifiedInProlog,
+        boolean isBomPresent)
+        throws JasperException {
+
+        JspDocumentParser jspDocParser =
+            new JspDocumentParser(pc, path, isTagFile, directivesOnly);
+        Node.Nodes pageNodes = null;
+
+        try {
+
+            // Create dummy root and initialize it with given page encodings
+            Node.Root dummyRoot = new Node.Root(null, parent, true);
+            dummyRoot.setPageEncoding(pageEnc);
+            dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc);
+            dummyRoot.setIsEncodingSpecifiedInProlog(
+                isEncodingSpecifiedInProlog);
+            dummyRoot.setIsBomPresent(isBomPresent);
+            jspDocParser.current = dummyRoot;
+            if (parent == null) {
+                jspDocParser.addInclude(
+                    dummyRoot,
+                    jspDocParser.pageInfo.getIncludePrelude());
+            } else {
+                jspDocParser.isTop = false;
+            }
+
+            // Parse the input
+            SAXParser saxParser = getSAXParser(false, jspDocParser);
+            InputStream inStream = null;
+            try {
+                inStream = JspUtil.getInputStream(path, jarFile,
+                                                  jspDocParser.ctxt,
+                                                  jspDocParser.err);
+                saxParser.parse(new InputSource(inStream), jspDocParser);
+            } catch (EnableDTDValidationException e) {
+                saxParser = getSAXParser(true, jspDocParser);
+                jspDocParser.isValidating = true;
+                if (inStream != null) {
+                    try {
+                        inStream.close();
+                    } catch (Exception any) {
+                    }
+                }
+                inStream = JspUtil.getInputStream(path, jarFile,
+                                                  jspDocParser.ctxt,
+                                                  jspDocParser.err);
+                saxParser.parse(new InputSource(inStream), jspDocParser);
+            } finally {
+                if (inStream != null) {
+                    try {
+                        inStream.close();
+                    } catch (Exception any) {
+                    }
+                }
+            }
+
+            if (parent == null) {
+                jspDocParser.addInclude(
+                    dummyRoot,
+                    jspDocParser.pageInfo.getIncludeCoda());
+            }
+
+            // Create Node.Nodes from dummy root
+            pageNodes = new Node.Nodes(dummyRoot);
+
+        } catch (IOException ioe) {
+            jspDocParser.err.jspError("jsp.error.data.file.read", path, ioe);
+        } catch (SAXParseException e) {
+            jspDocParser.err.jspError
+                (new Mark(jspDocParser.ctxt, path, e.getLineNumber(),
+                          e.getColumnNumber()),
+                 e.getMessage());
+        } catch (Exception e) {
+            jspDocParser.err.jspError(e);
+        }
+
+        return pageNodes;
+    }
+
+    /*
+     * Processes the given list of included files.
+     *
+     * This is used to implement the include-prelude and include-coda
+     * subelements of the jsp-config element in web.xml
+     */
+    private void addInclude(Node parent, List files) throws SAXException {
+        if (files != null) {
+            Iterator iter = files.iterator();
+            while (iter.hasNext()) {
+                String file = (String)iter.next();
+                AttributesImpl attrs = new AttributesImpl();
+                attrs.addAttribute("", "file", "file", "CDATA", file);
+
+                // Create a dummy Include directive node
+                    Node includeDir =
+                        new Node.IncludeDirective(attrs, null, // XXX
+    parent);
+                processIncludeDirective(file, includeDir);
+            }
+        }
+    }
+
+    /*
+     * Receives notification of the start of an element.
+     *
+     * This method assigns the given tag attributes to one of 3 buckets:
+     * 
+     * - "xmlns" attributes that represent (standard or custom) tag libraries.
+     * - "xmlns" attributes that do not represent tag libraries.
+     * - all remaining attributes.
+     *
+     * For each "xmlns" attribute that represents a custom tag library, the
+     * corresponding TagLibraryInfo object is added to the set of custom
+     * tag libraries.
+     */
+    public void startElement(
+        String uri,
+        String localName,
+        String qName,
+        Attributes attrs)
+        throws SAXException {
+
+        AttributesImpl taglibAttrs = null;
+        AttributesImpl nonTaglibAttrs = null;
+        AttributesImpl nonTaglibXmlnsAttrs = null;
+
+        processChars();
+
+        checkPrefixes(uri, qName, attrs);
+
+        if (directivesOnly &&
+            !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
+            return;
+        }
+
+        String currentPrefix = getPrefix(current.getQName());
+        
+        // jsp:text must not have any subelements
+        if (JSP_URI.equals(uri) && TEXT_ACTION.equals(current.getLocalName())
+                && "jsp".equals(currentPrefix)) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.text.has_subelement"),
+                locator);
+        }
+
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+
+        if (attrs != null) {
+            /*
+             * Notice that due to a bug in the underlying SAX parser, the
+             * attributes must be enumerated in descending order. 
+             */
+            boolean isTaglib = false;
+            for (int i = attrs.getLength() - 1; i >= 0; i--) {
+                isTaglib = false;
+                String attrQName = attrs.getQName(i);
+                if (!attrQName.startsWith("xmlns")) {
+                    if (nonTaglibAttrs == null) {
+                        nonTaglibAttrs = new AttributesImpl();
+                    }
+                    nonTaglibAttrs.addAttribute(
+                        attrs.getURI(i),
+                        attrs.getLocalName(i),
+                        attrs.getQName(i),
+                        attrs.getType(i),
+                        attrs.getValue(i));
+                } else {
+                    if (attrQName.startsWith("xmlns:jsp")) {
+                        isTaglib = true;
+                    } else {
+                        String attrUri = attrs.getValue(i);
+                        // TaglibInfo for this uri already established in
+                        // startPrefixMapping
+                        isTaglib = pageInfo.hasTaglib(attrUri);
+                    }
+                    if (isTaglib) {
+                        if (taglibAttrs == null) {
+                            taglibAttrs = new AttributesImpl();
+                        }
+                        taglibAttrs.addAttribute(
+                            attrs.getURI(i),
+                            attrs.getLocalName(i),
+                            attrs.getQName(i),
+                            attrs.getType(i),
+                            attrs.getValue(i));
+                    } else {
+                        if (nonTaglibXmlnsAttrs == null) {
+                            nonTaglibXmlnsAttrs = new AttributesImpl();
+                        }
+                        nonTaglibXmlnsAttrs.addAttribute(
+                            attrs.getURI(i),
+                            attrs.getLocalName(i),
+                            attrs.getQName(i),
+                            attrs.getType(i),
+                            attrs.getValue(i));
+                    }
+                }
+            }
+        }
+
+        Node node = null;
+
+        if (tagDependentPending && JSP_URI.equals(uri) &&
+                     localName.equals(BODY_ACTION)) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+            current =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+            return;
+        }
+
+        if (tagDependentPending && JSP_URI.equals(uri) &&
+                     localName.equals(ATTRIBUTE_ACTION)) {
+            current =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+            return;
+        }
+
+        if (tagDependentPending) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+        }
+
+        if (tagDependentNesting > 0) {
+            node =
+                new Node.UninterpretedTag(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+        } else if (JSP_URI.equals(uri)) {
+            node =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+        } else {
+            node =
+                parseCustomAction(
+                    qName,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+            if (node == null) {
+                node =
+                    new Node.UninterpretedTag(
+                        qName,
+                        localName,
+                        nonTaglibAttrs,
+                        nonTaglibXmlnsAttrs,
+                        taglibAttrs,
+                        startMark,
+                        current);
+            } else {
+                // custom action
+                String bodyType = getBodyType((Node.CustomTag) node);
+
+                if (scriptlessBodyNode == null
+                        && bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                    scriptlessBodyNode = node;
+                }
+                else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType)) {
+                    tagDependentPending = true;
+                }
+            }
+        }
+
+        current = node;
+    }
+
+    /*
+     * Receives notification of character data inside an element.
+     *
+     * The SAX does not call this method with all of the template text, but may
+     * invoke this method with chunks of it.  This is a problem when we try
+     * to determine if the text contains only whitespaces, or when we are
+     * looking for an EL expression string.  Therefore it is necessary to
+     * buffer and concatenate the chunks and process the concatenated text 
+     * later (at beginTag and endTag)
+     *
+     * @param buf The characters
+     * @param offset The start position in the character array
+     * @param len The number of characters to use from the character array
+     *
+     * @throws SAXException
+     */
+    public void characters(char[] buf, int offset, int len) {
+
+        if (charBuffer == null) {
+            charBuffer = new StringBuffer();
+        }
+        charBuffer.append(buf, offset, len);
+    }
+
+    private void processChars() throws SAXException {
+
+        if (charBuffer == null || directivesOnly) {
+            return;
+        }
+
+        /*
+         * JSP.6.1.1: All textual nodes that have only white space are to be
+         * dropped from the document, except for nodes in a jsp:text element,
+         * and any leading and trailing white-space-only textual nodes in a
+         * jsp:attribute whose 'trim' attribute is set to FALSE, which are to
+         * be kept verbatim.
+         * JSP.6.2.3 defines white space characters.
+         */
+        boolean isAllSpace = true;
+        if (!(current instanceof Node.JspText)
+            && !(current instanceof Node.NamedAttribute)) {
+            for (int i = 0; i < charBuffer.length(); i++) {
+                if (!(charBuffer.charAt(i) == ' '
+                    || charBuffer.charAt(i) == '\n'
+                    || charBuffer.charAt(i) == '\r'
+                    || charBuffer.charAt(i) == '\t')) {
+                    isAllSpace = false;
+                    break;
+                }
+            }
+        }
+
+        if (!isAllSpace && tagDependentPending) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+        }
+
+        if (tagDependentNesting > 0) {
+            if (charBuffer.length() > 0) {
+                new Node.TemplateText(charBuffer.toString(), startMark, current);
+            }
+            startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                                 locator.getColumnNumber());
+            charBuffer = null;
+            return;
+        }
+
+        if ((current instanceof Node.JspText)
+            || (current instanceof Node.NamedAttribute)
+            || !isAllSpace) {
+
+            int line = startMark.getLineNumber();
+            int column = startMark.getColumnNumber();
+
+            CharArrayWriter ttext = new CharArrayWriter();
+            int lastCh = 0, elType = 0;
+            for (int i = 0; i < charBuffer.length(); i++) {
+
+                int ch = charBuffer.charAt(i);
+                if (ch == '\n') {
+                    column = 1;
+                    line++;
+                } else {
+                    column++;
+                }
+                if ((lastCh == '$' || lastCh == '#') && ch == '{') {
+                    elType = lastCh;
+                    if (ttext.size() > 0) {
+                        new Node.TemplateText(
+                            ttext.toString(),
+                            startMark,
+                            current);
+                        ttext = new CharArrayWriter();
+                        //We subtract two from the column number to
+                        //account for the '[$,#]{' that we've already parsed
+                        startMark = new Mark(ctxt, path, line, column - 2);
+                    }
+                    // following "${" || "#{" to first unquoted "}"
+                    i++;
+                    boolean singleQ = false;
+                    boolean doubleQ = false;
+                    lastCh = 0;
+                    for (;; i++) {
+                        if (i >= charBuffer.length()) {
+                            throw new SAXParseException(
+                                Localizer.getMessage(
+                                    "jsp.error.unterminated",
+                                    (char) elType + "{"),
+                                locator);
+
+                        }
+                        ch = charBuffer.charAt(i);
+                        if (ch == '\n') {
+                            column = 1;
+                            line++;
+                        } else {
+                            column++;
+                        }
+                        if (lastCh == '\\' && (singleQ || doubleQ)) {
+                            ttext.write(ch);
+                            lastCh = 0;
+                            continue;
+                        }
+                        if (ch == '}') {
+                            new Node.ELExpression((char) elType,
+                                ttext.toString(),
+                                startMark,
+                                current);
+                            ttext = new CharArrayWriter();
+                            startMark = new Mark(ctxt, path, line, column);
+                            break;
+                        }
+                        if (ch == '"')
+                            doubleQ = !doubleQ;
+                        else if (ch == '\'')
+                            singleQ = !singleQ;
+
+                        ttext.write(ch);
+                        lastCh = ch;
+                    }
+                } else if (lastCh == '\\' && (ch == '$' || ch == '#')) {
+                    if (pageInfo.isELIgnored()) {
+                        ttext.write('\\');
+                    }
+                    ttext.write(ch);
+                    ch = 0;  // Not start of EL anymore
+                } else {
+                    if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
+                        ttext.write(lastCh);
+                    }
+                    if (ch != '$' && ch != '#' && ch != '\\') {
+                        ttext.write(ch);
+                    }
+                }
+                lastCh = ch;
+            }
+            if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
+                ttext.write(lastCh);
+            }
+            if (ttext.size() > 0) {
+                new Node.TemplateText(ttext.toString(), startMark, current);
+            }
+        }
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+
+        charBuffer = null;
+    }
+
+    /*
+     * Receives notification of the end of an element.
+     */
+    public void endElement(String uri, String localName, String qName)
+        throws SAXException {
+
+        processChars();
+
+        if (directivesOnly &&
+            !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
+            return;
+        }
+
+        if (current instanceof Node.NamedAttribute) {
+            boolean isTrim = ((Node.NamedAttribute)current).isTrim();
+            Node.Nodes subElems = ((Node.NamedAttribute)current).getBody();
+            for (int i = 0; subElems != null && i < subElems.size(); i++) {
+                Node subElem = subElems.getNode(i);
+                if (!(subElem instanceof Node.TemplateText)) {
+                    continue;
+                }
+                // Ignore any whitespace (including spaces, carriage returns,
+                // line feeds, and tabs, that appear at the beginning and at
+                // the end of the body of the <jsp:attribute> action, if the
+                // action's 'trim' attribute is set to TRUE (default).
+                // In addition, any textual nodes in the <jsp:attribute> that
+                // have only white space are dropped from the document, with
+                // the exception of leading and trailing white-space-only
+                // textual nodes in a <jsp:attribute> whose 'trim' attribute
+                // is set to FALSE, which must be kept verbatim.
+                if (i == 0) {
+                    if (isTrim) {
+                        ((Node.TemplateText)subElem).ltrim();
+                    }
+                } else if (i == subElems.size() - 1) {
+                    if (isTrim) {
+                        ((Node.TemplateText)subElem).rtrim();
+                    }
+                } else {
+                    if (((Node.TemplateText)subElem).isAllSpace()) {
+                        subElems.remove(subElem);
+                    }
+                }
+            }
+        } else if (current instanceof Node.ScriptingElement) {
+            checkScriptingBody((Node.ScriptingElement)current);
+        }
+
+        if ( isTagDependent(current)) {
+            tagDependentNesting--;
+        }
+
+        if (scriptlessBodyNode != null
+                && current.equals(scriptlessBodyNode)) {
+            scriptlessBodyNode = null;
+        }
+
+        if (current.getParent() != null) {
+            current = current.getParent();
+        }
+    }
+
+    /*
+     * Receives the document locator.
+     *
+     * @param locator the document locator
+     */
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void comment(char[] buf, int offset, int len) throws SAXException {
+
+        processChars();  // Flush char buffer and remove white spaces
+
+        // ignore comments in the DTD
+        if (!inDTD) {
+            startMark =
+                new Mark(
+                    ctxt,
+                    path,
+                    locator.getLineNumber(),
+                    locator.getColumnNumber());
+            new Node.Comment(new String(buf, offset, len), startMark, current);
+        }
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void startCDATA() throws SAXException {
+
+        processChars();  // Flush char buffer and remove white spaces
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void endCDATA() throws SAXException {
+        processChars();  // Flush char buffer and remove white spaces
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void startEntity(String name) throws SAXException {
+        // do nothing
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void endEntity(String name) throws SAXException {
+        // do nothing
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void startDTD(String name, String publicId, String systemId)
+        throws SAXException {
+        if (!isValidating) {
+            fatalError(ENABLE_DTD_VALIDATION_EXCEPTION);
+        }
+
+        inDTD = true;
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    public void endDTD() throws SAXException {
+        inDTD = false;
+    }
+
+    /*
+     * Receives notification of a non-recoverable error.
+     */
+    public void fatalError(SAXParseException e) throws SAXException {
+        throw e;
+    }
+
+    /*
+     * Receives notification of a recoverable error.
+     */
+    public void error(SAXParseException e) throws SAXException {
+        throw e;
+    }
+
+    /*
+     * Receives notification of the start of a Namespace mapping. 
+     */
+    public void startPrefixMapping(String prefix, String uri)
+        throws SAXException {
+        TagLibraryInfo taglibInfo;
+
+        if (directivesOnly && !(JSP_URI.equals(uri))) {
+            return;
+        }
+        
+        try {
+            taglibInfo = getTaglibInfo(prefix, uri);
+        } catch (JasperException je) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.could.not.add.taglibraries"),
+                locator,
+                je);
+        }
+
+        if (taglibInfo != null) {
+            if (pageInfo.getTaglib(uri) == null) {
+                pageInfo.addTaglib(uri, taglibInfo);
+            }
+            pageInfo.pushPrefixMapping(prefix, uri);
+        } else {
+            pageInfo.pushPrefixMapping(prefix, null);
+        }
+    }
+
+    /*
+     * Receives notification of the end of a Namespace mapping. 
+     */
+    public void endPrefixMapping(String prefix) throws SAXException {
+
+        if (directivesOnly) {
+            String uri = pageInfo.getURI(prefix);
+            if (!JSP_URI.equals(uri)) {
+                return;
+            }
+        }
+
+        pageInfo.popPrefixMapping(prefix);
+    }
+
+    //*********************************************************************
+    // Private utility methods
+
+    private Node parseStandardAction(
+        String qName,
+        String localName,
+        Attributes nonTaglibAttrs,
+        Attributes nonTaglibXmlnsAttrs,
+        Attributes taglibAttrs,
+        Mark start,
+        Node parent)
+        throws SAXException {
+
+        Node node = null;
+
+        if (localName.equals(ROOT_ACTION)) {
+            if (!(current instanceof Node.Root)) {
+                throw new SAXParseException(
+                    Localizer.getMessage("jsp.error.nested_jsproot"),
+                    locator);
+            }
+            node =
+                new Node.JspRoot(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            if (isTop) {
+                pageInfo.setHasJspRoot(true);
+            }
+        } else if (localName.equals(PAGE_DIRECTIVE_ACTION)) {
+            if (isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.istagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.PageDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            String imports = nonTaglibAttrs.getValue("import");
+            // There can only be one 'import' attribute per page directive
+            if (imports != null) {
+                ((Node.PageDirective)node).addImport(imports);
+            }
+        } else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) {
+            node =
+                new Node.IncludeDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            processIncludeDirective(nonTaglibAttrs.getValue("file"), node);
+        } else if (localName.equals(DECLARATION_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Declaration(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(SCRIPTLET_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Scriptlet(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(EXPRESSION_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Expression(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(USE_BEAN_ACTION)) {
+            node =
+                new Node.UseBean(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(SET_PROPERTY_ACTION)) {
+            node =
+                new Node.SetProperty(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(GET_PROPERTY_ACTION)) {
+            node =
+                new Node.GetProperty(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(INCLUDE_ACTION)) {
+            node =
+                new Node.IncludeAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(FORWARD_ACTION)) {
+            node =
+                new Node.ForwardAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PARAM_ACTION)) {
+            node =
+                new Node.ParamAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PARAMS_ACTION)) {
+            node =
+                new Node.ParamsAction(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PLUGIN_ACTION)) {
+            node =
+                new Node.PlugIn(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(TEXT_ACTION)) {
+            node =
+                new Node.JspText(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(BODY_ACTION)) {
+            node =
+                new Node.JspBody(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(ATTRIBUTE_ACTION)) {
+            node =
+                new Node.NamedAttribute(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(OUTPUT_ACTION)) {
+            node =
+                new Node.JspOutput(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(TAG_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.TagDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            String imports = nonTaglibAttrs.getValue("import");
+            // There can only be one 'import' attribute per tag directive
+            if (imports != null) {
+                ((Node.TagDirective)node).addImport(imports);
+            }
+        } else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.AttributeDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.VariableDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(INVOKE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.InvokeAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(DOBODY_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.DoBodyAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(ELEMENT_ACTION)) {
+            node =
+                new Node.JspElement(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(FALLBACK_ACTION)) {
+            node =
+                new Node.FallBackAction(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else {
+            throw new SAXParseException(
+                Localizer.getMessage(
+                    "jsp.error.xml.badStandardAction",
+                    localName),
+                locator);
+        }
+
+        return node;
+    }
+
+    /*
+     * Checks if the XML element with the given tag name is a custom action,
+     * and returns the corresponding Node object.
+     */
+    private Node parseCustomAction(
+        String qName,
+        String localName,
+        String uri,
+        Attributes nonTaglibAttrs,
+        Attributes nonTaglibXmlnsAttrs,
+        Attributes taglibAttrs,
+        Mark start,
+        Node parent)
+        throws SAXException {
+
+        // Check if this is a user-defined (custom) tag
+        TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
+        if (tagLibInfo == null) {
+            return null;
+        }
+
+        TagInfo tagInfo = tagLibInfo.getTag(localName);
+        TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName);
+        if (tagInfo == null && tagFileInfo == null) {
+            throw new SAXException(
+                Localizer.getMessage("jsp.error.xml.bad_tag", localName, uri));
+        }
+        Class tagHandlerClass = null;
+        if (tagInfo != null) {
+            String handlerClassName = tagInfo.getTagClassName();
+            try {
+                tagHandlerClass =
+                    ctxt.getClassLoader().loadClass(handlerClassName);
+            } catch (Exception e) {
+                throw new SAXException(
+                    Localizer.getMessage("jsp.error.loadclass.taghandler",
+                                         handlerClassName,
+                                         qName),
+                    e);
+            }
+        }
+
+        String prefix = getPrefix(qName);
+
+        Node.CustomTag ret = null;
+        if (tagInfo != null) {
+            ret =
+                new Node.CustomTag(
+                    qName,
+                    prefix,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    parent,
+                    tagInfo,
+                    tagHandlerClass);
+        } else {
+            ret =
+                new Node.CustomTag(
+                    qName,
+                    prefix,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    parent,
+                    tagFileInfo);
+        }
+
+        return ret;
+    }
+
+    /*
+     * Creates the tag library associated with the given uri namespace, and
+     * returns it.
+     *
+     * @param prefix The prefix of the xmlns attribute
+     * @param uri The uri namespace (value of the xmlns attribute)
+     *
+     * @return The tag library associated with the given uri namespace
+     */
+    private TagLibraryInfo getTaglibInfo(String prefix, String uri)
+        throws JasperException {
+
+        TagLibraryInfo result = null;
+
+        if (uri.startsWith(URN_JSPTAGDIR)) {
+            // uri (of the form "urn:jsptagdir:path") references tag file dir
+            String tagdir = uri.substring(URN_JSPTAGDIR.length());
+            result =
+                new ImplicitTagLibraryInfo(
+                    ctxt,
+                    parserController,
+                    pageInfo,
+                    prefix,
+                    tagdir,
+                    err);
+        } else {
+            // uri references TLD file
+            boolean isPlainUri = false;
+            if (uri.startsWith(URN_JSPTLD)) {
+                // uri is of the form "urn:jsptld:path"
+                uri = uri.substring(URN_JSPTLD.length());
+            } else {
+                isPlainUri = true;
+            }
+
+            String[] location = ctxt.getTldLocation(uri);
+            if (location != null || !isPlainUri) {
+                if (ctxt.getOptions().isCaching()) {
+                    result = (TagLibraryInfoImpl) ctxt.getOptions().getCache().get(uri);
+                }
+                if (result == null) {
+                    /*
+                     * If the uri value is a plain uri, a translation error must
+                     * not be generated if the uri is not found in the taglib map.
+                     * Instead, any actions in the namespace defined by the uri
+                     * value must be treated as uninterpreted.
+                     */
+                    result =
+                        new TagLibraryInfoImpl(
+                            ctxt,
+                            parserController,
+                            pageInfo,
+                            prefix,
+                            uri,
+                            location,
+                            err);
+                    if (ctxt.getOptions().isCaching()) {
+                        ctxt.getOptions().getCache().put(uri, result);
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /*
+     * Ensures that the given body only contains nodes that are instances of
+     * TemplateText.
+     *
+     * This check is performed only for the body of a scripting (that is:
+     * declaration, scriptlet, or expression) element, after the end tag of a
+     * scripting element has been reached.
+     */
+    private void checkScriptingBody(Node.ScriptingElement scriptingElem)
+        throws SAXException {
+        Node.Nodes body = scriptingElem.getBody();
+        if (body != null) {
+            int size = body.size();
+            for (int i = 0; i < size; i++) {
+                Node n = body.getNode(i);
+                if (!(n instanceof Node.TemplateText)) {
+                    String elemType = SCRIPTLET_ACTION;
+                    if (scriptingElem instanceof Node.Declaration)
+                        elemType = DECLARATION_ACTION;
+                    if (scriptingElem instanceof Node.Expression)
+                        elemType = EXPRESSION_ACTION;
+                    String msg =
+                        Localizer.getMessage(
+                            "jsp.error.parse.xml.scripting.invalid.body",
+                            elemType);
+                    throw new SAXException(msg);
+                }
+            }
+        }
+    }
+
+    /*
+     * Parses the given file included via an include directive.
+     *
+     * @param fname The path to the included resource, as specified by the
+     * 'file' attribute of the include directive
+     * @param parent The Node representing the include directive
+     */
+    private void processIncludeDirective(String fname, Node parent)
+        throws SAXException {
+
+        if (fname == null) {
+            return;
+        }
+
+        try {
+            parserController.parse(fname, parent, null);
+        } catch (FileNotFoundException fnfe) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.file.not.found", fname),
+                locator,
+                fnfe);
+        } catch (Exception e) {
+            throw new SAXException(e);
+        }
+    }
+
+    /*
+     * Checks an element's given URI, qname, and attributes to see if any
+     * of them hijack the 'jsp' prefix, that is, bind it to a namespace other
+     * than http://java.sun.com/JSP/Page.
+     *
+     * @param uri The element's URI
+     * @param qName The element's qname
+     * @param attrs The element's attributes
+     */
+    private void checkPrefixes(String uri, String qName, Attributes attrs) {
+
+        checkPrefix(uri, qName);
+
+        int len = attrs.getLength();
+        for (int i = 0; i < len; i++) {
+            checkPrefix(attrs.getURI(i), attrs.getQName(i));
+        }
+    }
+
+    /*
+     * Checks the given URI and qname to see if they hijack the 'jsp' prefix,
+     * which would be the case if qName contained the 'jsp' prefix and
+     * uri was different from http://java.sun.com/JSP/Page.
+     *
+     * @param uri The URI to check
+     * @param qName The qname to check
+     */
+    private void checkPrefix(String uri, String qName) {
+
+        String prefix = getPrefix(qName);
+        if (prefix.length() > 0) {
+            pageInfo.addPrefix(prefix);
+            if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) {
+                pageInfo.setIsJspPrefixHijacked(true);
+            }
+        }
+    }
+
+    private String getPrefix(String qName) {
+        int index = qName.indexOf(':');
+        if (index != -1) {
+            return qName.substring(0, index);
+        }
+        return "";
+    }
+
+    /*
+     * Gets SAXParser.
+     *
+     * @param validating Indicates whether the requested SAXParser should
+     * be validating
+     * @param jspDocParser The JSP document parser
+     *
+     * @return The SAXParser
+     */
+    private static SAXParser getSAXParser(
+        boolean validating,
+        JspDocumentParser jspDocParser)
+        throws Exception {
+
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+
+        // Preserve xmlns attributes
+        factory.setFeature(
+            "http://xml.org/sax/features/namespace-prefixes",
+            true);
+        factory.setValidating(validating);
+        //factory.setFeature(
+        //    "http://xml.org/sax/features/validation",
+        //    validating);
+        
+        // Configure the parser
+        SAXParser saxParser = factory.newSAXParser();
+        XMLReader xmlReader = saxParser.getXMLReader();
+        xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser);
+        xmlReader.setErrorHandler(jspDocParser);
+
+        return saxParser;
+    }
+
+    /*
+     * Exception indicating that a DOCTYPE declaration is present, but
+     * validation is turned off.
+     */
+    private static class EnableDTDValidationException
+        extends SAXParseException {
+
+        EnableDTDValidationException(String message, Locator loc) {
+            super(message, loc);
+        }
+    }
+
+    private static String getBodyType(Node.CustomTag custom) {
+
+        if (custom.getTagInfo() != null) {
+            return custom.getTagInfo().getBodyContent();
+        }
+
+        return custom.getTagFileInfo().getTagInfo().getBodyContent();
+    }
+
+    private boolean isTagDependent(Node n) {
+
+        if (n instanceof Node.CustomTag) {
+            String bodyType = getBodyType((Node.CustomTag) n);
+            return
+                TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType);
+        }
+        return false;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java
new file mode 100644
index 0000000..4df569f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java
@@ -0,0 +1,657 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * JspReader is an input buffer for the JSP parser. It should allow
+ * unlimited lookahead and pushback. It also has a bunch of parsing
+ * utility methods for understanding htmlesque thingies.
+ *
+ * @author Anil K. Vijendran
+ * @author Anselm Baird-Smith
+ * @author Harish Prabandham
+ * @author Rajiv Mordani
+ * @author Mandar Raje
+ * @author Danno Ferrin
+ * @author Kin-man Chung
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+class JspReader {
+
+    /**
+     * Logger.
+     */
+    private Log log = LogFactory.getLog(JspReader.class);
+
+    /**
+     * The current spot in the file.
+     */
+    private Mark current;
+
+    /**
+     * What is this?
+     */
+    private String master;
+
+    /**
+     * The list of source files.
+     */
+    private List sourceFiles;
+
+    /**
+     * The current file ID (-1 indicates an error or no file).
+     */
+    private int currFileId;
+
+    /**
+     * Seems redundant.
+     */
+    private int size;
+
+    /**
+     * The compilation context.
+     */
+    private JspCompilationContext context;
+
+    /**
+     * The Jasper error dispatcher.
+     */
+    private ErrorDispatcher err;
+
+    /**
+     * Set to true when using the JspReader on a single file where we read up
+     * to the end and reset to the beginning many times.
+     * (as in ParserController.figureOutJspDocument()).
+     */
+    private boolean singleFile;
+
+    /**
+     * Constructor.
+     *
+     * @param ctxt The compilation context
+     * @param fname The file name
+     * @param encoding The file encoding
+     * @param jarFile ?
+     * @param err The error dispatcher
+     * @throws JasperException If a Jasper-internal error occurs
+     * @throws FileNotFoundException If the JSP file is not found (or is unreadable)
+     * @throws IOException If an IO-level error occurs, e.g. reading the file
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     JarFile jarFile,
+                     ErrorDispatcher err)
+            throws JasperException, FileNotFoundException, IOException {
+
+        this(ctxt, fname, encoding,
+             JspUtil.getReader(fname, encoding, jarFile, ctxt, err),
+             err);
+    }
+
+    /**
+     * Constructor: same as above constructor but with initialized reader
+     * to the file given.
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     InputStreamReader reader,
+                     ErrorDispatcher err)
+            throws JasperException, FileNotFoundException {
+
+        this.context = ctxt;
+        this.err = err;
+        sourceFiles = new Vector();
+        currFileId = 0;
+        size = 0;
+        singleFile = false;
+        pushFile(fname, encoding, reader);
+    }
+
+    /**
+     * @return JSP compilation context with which this JspReader is 
+     * associated
+     */
+    JspCompilationContext getJspCompilationContext() {
+        return context;
+    }
+    
+    /**
+     * Returns the file at the given position in the list.
+     *
+     * @param fileid The file position in the list
+     * @return The file at that position, if found, null otherwise
+     */
+    String getFile(final int fileid) {
+        return (String) sourceFiles.get(fileid);
+    }
+       
+    /**
+     * Checks if the current file has more input.
+     *
+     * @return True if more reading is possible
+     * @throws JasperException if an error occurs
+     */ 
+    boolean hasMoreInput() throws JasperException {
+        if (current.cursor >= current.stream.length) {
+            if (singleFile) return false; 
+            while (popFile()) {
+                if (current.cursor < current.stream.length) return true;
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    int nextChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        
+        int ch = current.stream[current.cursor];
+
+        current.cursor++;
+        
+        if (ch == '\n') {
+            current.line++;
+            current.col = 0;
+        } else {
+            current.col++;
+        }
+        return ch;
+    }
+
+    /**
+     * Back up the current cursor by one char, assumes current.cursor > 0,
+     * and that the char to be pushed back is not '\n'.
+     */
+    void pushChar() {
+        current.cursor--;
+        current.col--;
+    }
+
+    String getText(Mark start, Mark stop) throws JasperException {
+        Mark oldstart = mark();
+        reset(start);
+        CharArrayWriter caw = new CharArrayWriter();
+        while (!stop.equals(mark()))
+            caw.write(nextChar());
+        caw.close();
+        reset(oldstart);
+        return caw.toString();
+    }
+
+    int peekChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        return current.stream[current.cursor];
+    }
+
+    Mark mark() {
+        return new Mark(current);
+    }
+
+    void reset(Mark mark) {
+        current = new Mark(mark);
+    }
+
+    boolean matchesIgnoreCase(String string) throws JasperException {
+        Mark mark = mark();
+        int ch = 0;
+        int i = 0;
+        do {
+            ch = nextChar();
+            if (Character.toLowerCase((char) ch) != string.charAt(i++)) {
+                reset(mark);
+                return false;
+            }
+        } while (i < string.length());
+        reset(mark);
+        return true;
+    }
+
+    /**
+     * search the stream for a match to a string
+     * @param string The string to match
+     * @return <strong>true</strong> is one is found, the current position
+     *         in stream is positioned after the search string, <strong>
+     *               false</strong> otherwise, position in stream unchanged.
+     */
+    boolean matches(String string) throws JasperException {
+        Mark mark = mark();
+        int ch = 0;
+        int i = 0;
+        do {
+            ch = nextChar();
+            if (((char) ch) != string.charAt(i++)) {
+                reset(mark);
+                return false;
+            }
+        } while (i < string.length());
+        return true;
+    }
+
+    boolean matchesETag(String tagName) throws JasperException {
+        Mark mark = mark();
+
+        if (!matches("</" + tagName))
+            return false;
+        skipSpaces();
+        if (nextChar() == '>')
+            return true;
+
+        reset(mark);
+        return false;
+    }
+
+    boolean matchesETagWithoutLessThan(String tagName)
+        throws JasperException
+    {
+       Mark mark = mark();
+
+       if (!matches("/" + tagName))
+           return false;
+       skipSpaces();
+       if (nextChar() == '>')
+           return true;
+
+       reset(mark);
+       return false;
+    }
+
+
+    /**
+     * Looks ahead to see if there are optional spaces followed by
+     * the given String.  If so, true is returned and those spaces and
+     * characters are skipped.  If not, false is returned and the
+     * position is restored to where we were before.
+     */
+    boolean matchesOptionalSpacesFollowedBy( String s )
+        throws JasperException
+    {
+        Mark mark = mark();
+
+        skipSpaces();
+        boolean result = matches( s );
+        if( !result ) {
+            reset( mark );
+        }
+
+        return result;
+    }
+
+    int skipSpaces() throws JasperException {
+        int i = 0;
+        while (hasMoreInput() && isSpace()) {
+            i++;
+            nextChar();
+        }
+        return i;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntil(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), ch = nextChar()) {
+            if (ch == limit.charAt(0)) {
+                Mark restart = mark();
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else {
+                        reset(restart);
+                        continue skip;
+                    }
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream, but ignoring
+     * chars initially escaped by a '\'.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntilIgnoreEsc(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+        int prev = 'x';        // Doesn't matter
+        
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), prev = ch, ch = nextChar()) {            
+            if (ch == '\\' && prev == '\\') {
+                ch = 0;                // Double \ is not an escape char anymore
+            }
+            else if (ch == limit.charAt(0) && prev != '\\') {
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else
+                        continue skip;
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Skip until the given end tag is matched in the stream.
+     * When returned, the context is positioned past the end of the tag.
+     *
+     * @param tag The name of the tag whose ETag (</tag>) to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *               before the ETag) if found, <strong>null</strong> otherwise.
+     */
+    Mark skipUntilETag(String tag) throws JasperException {
+        Mark ret = skipUntil("</" + tag);
+        if (ret != null) {
+            skipSpaces();
+            if (nextChar() != '>')
+                ret = null;
+        }
+        return ret;
+    }
+
+    final boolean isSpace() throws JasperException {
+        // Note: If this logic changes, also update Node.TemplateText.rtrim()
+        return peekChar() <= ' ';
+    }
+
+    /**
+     * Parse a space delimited token.
+     * If quoted the token will consume all characters up to a matching quote,
+     * otherwise, it consumes up to the first delimiter character.
+     *
+     * @param quoted If <strong>true</strong> accept quoted strings.
+     */
+    String parseToken(boolean quoted) throws JasperException {
+        StringBuffer stringBuffer = new StringBuffer();
+        skipSpaces();
+        stringBuffer.setLength(0);
+        
+        if (!hasMoreInput()) {
+            return "";
+        }
+
+        int ch = peekChar();
+        
+        if (quoted) {
+            if (ch == '"' || ch == '\'') {
+
+                char endQuote = ch == '"' ? '"' : '\'';
+                // Consume the open quote: 
+                ch = nextChar();
+                for (ch = nextChar(); ch != -1 && ch != endQuote;
+                         ch = nextChar()) {
+                    if (ch == '\\') 
+                        ch = nextChar();
+                    stringBuffer.append((char) ch);
+                }
+                // Check end of quote, skip closing quote:
+                if (ch == -1) {
+                    err.jspError(mark(), "jsp.error.quotes.unterminated");
+                }
+            } else {
+                err.jspError(mark(), "jsp.error.attr.quoted");
+            }
+        } else {
+            if (!isDelimiter()) {
+                // Read value until delimiter is found:
+                do {
+                    ch = nextChar();
+                    // Take care of the quoting here.
+                    if (ch == '\\') {
+                        if (peekChar() == '"' || peekChar() == '\'' ||
+                               peekChar() == '>' || peekChar() == '%')
+                            ch = nextChar();
+                    }
+                    stringBuffer.append((char) ch);
+                } while (!isDelimiter());
+            }
+        }
+
+        return stringBuffer.toString();
+    }
+
+    void setSingleFile(boolean val) {
+        singleFile = val;
+    }
+
+
+    /**
+     * Gets the URL for the given path name.
+     *
+     * @param path Path name
+     *
+     * @return URL for the given path name.
+     *
+     * @exception MalformedURLException if the path name is not given in 
+     * the correct form
+     */
+    URL getResource(String path) throws MalformedURLException {
+        return context.getResource(path);
+    }
+
+
+    /**
+     * Parse utils - Is current character a token delimiter ?
+     * Delimiters are currently defined to be =, &gt;, &lt;, ", and ' or any
+     * any space character as defined by <code>isSpace</code>.
+     *
+     * @return A boolean.
+     */
+    private boolean isDelimiter() throws JasperException {
+        if (! isSpace()) {
+            int ch = peekChar();
+            // Look for a single-char work delimiter:
+            if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
+                    || ch == '/') {
+                return true;
+            }
+            // Look for an end-of-comment or end-of-tag:                
+            if (ch == '-') {
+                Mark mark = mark();
+                if (((ch = nextChar()) == '>')
+                        || ((ch == '-') && (nextChar() == '>'))) {
+                    reset(mark);
+                    return true;
+                } else {
+                    reset(mark);
+                    return false;
+                }
+            }
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Register a new source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a unique identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int registerSourceFile(final String file) {
+        if (sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.add(file);
+        this.size++;
+
+        return sourceFiles.size() - 1;
+    }
+    
+
+    /**
+     * Unregister the source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a uniq identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int unregisterSourceFile(final String file) {
+        if (!sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.remove(file);
+        this.size--;
+        return sourceFiles.size() - 1;
+    }
+
+    /**
+     * Push a file (and its associated Stream) on the file stack.  THe
+     * current position in the current file is remembered.
+     */
+    private void pushFile(String file, String encoding, 
+                           InputStreamReader reader) 
+                throws JasperException, FileNotFoundException {
+
+        // Register the file
+        String longName = file;
+
+        int fileid = registerSourceFile(longName);
+
+        if (fileid == -1) {
+            // Bugzilla 37407: http://issues.apache.org/bugzilla/show_bug.cgi?id=37407
+            if(reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+
+            err.jspError("jsp.error.file.already.registered", file);
+        }
+
+        currFileId = fileid;
+
+        try {
+            CharArrayWriter caw = new CharArrayWriter();
+            char buf[] = new char[1024];
+            for (int i = 0 ; (i = reader.read(buf)) != -1 ;)
+                caw.write(buf, 0, i);
+            caw.close();
+            if (current == null) {
+                current = new Mark(this, caw.toCharArray(), fileid, 
+                                   getFile(fileid), master, encoding);
+            } else {
+                current.pushStream(caw.toCharArray(), fileid, getFile(fileid),
+                                   longName, encoding);
+            }
+        } catch (Throwable ex) {
+            log.error("Exception parsing file ", ex);
+            // Pop state being constructed:
+            popFile();
+            err.jspError("jsp.error.file.cannot.read", file);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Pop a file from the file stack.  The field "current" is retored
+     * to the value to point to the previous files, if any, and is set
+     * to null otherwise.
+     * @return true is there is a previous file on the stack.
+     *         false otherwise.
+     */
+    private boolean popFile() throws JasperException {
+
+        // Is stack created ? (will happen if the Jsp file we're looking at is
+        // missing.
+        if (current == null || currFileId < 0) {
+            return false;
+        }
+
+        // Restore parser state:
+        String fName = getFile(currFileId);
+        currFileId = unregisterSourceFile(fName);
+        if (currFileId < -1) {
+            err.jspError("jsp.error.file.not.registered", fName);
+        }
+
+        Mark previous = current.popStream();
+        if (previous != null) {
+            master = current.baseDir;
+            current = previous;
+            return true;
+        }
+        // Note that although the current file is undefined here, "current"
+        // is not set to null just for convience, for it maybe used to
+        // set the current (undefined) position.
+        return false;
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java
new file mode 100644
index 0000000..4245a00
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java
@@ -0,0 +1,446 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilePermission;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.cert.Certificate;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspFactory;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.runtime.JspFactoryImpl;
+import org.apache.jasper.security.SecurityClassLoad;
+import org.apache.jasper.servlet.JspServletWrapper;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Class for tracking JSP compile time file dependencies when the
+ * &060;%@include file="..."%&062; directive is used.
+ *
+ * A background thread periodically checks the files a JSP page
+ * is dependent upon.  If a dpendent file changes the JSP page
+ * which included it is recompiled.
+ *
+ * Only used if a web application context is a directory.
+ *
+ * @author Glenn L. Nielsen
+ * @version $Revision$
+ */
+public final class JspRuntimeContext {
+
+    // Logger
+    private Log log = LogFactory.getLog(JspRuntimeContext.class);
+
+    /*
+     * Counts how many times the webapp's JSPs have been reloaded.
+     */
+    private int jspReloadCount;
+
+    /**
+     * Preload classes required at runtime by a JSP servlet so that
+     * we don't get a defineClassInPackage security exception.
+     */
+    static {
+        JspFactoryImpl factory = new JspFactoryImpl();
+        SecurityClassLoad.securityClassLoad(factory.getClass().getClassLoader());
+        if( System.getSecurityManager() != null ) {
+            String basePackage = "org.apache.jasper.";
+            try {
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspFactoryImpl$PrivilegedGetPageContext");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspRuntimeLibrary");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.ServletResponseWrapperInclude");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "servlet.JspServletWrapper");
+            } catch (ClassNotFoundException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        JspFactory.setDefaultFactory(factory);
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Create a JspRuntimeContext for a web application context.
+     *
+     * Loads in any previously generated dependencies from file.
+     *
+     * @param context ServletContext for web application
+     */
+    public JspRuntimeContext(ServletContext context, Options options) {
+
+        this.context = context;
+        this.options = options;
+
+        // Get the parent class loader
+        parentClassLoader =
+            (URLClassLoader) Thread.currentThread().getContextClassLoader();
+        if (parentClassLoader == null) {
+            parentClassLoader =
+                (URLClassLoader)this.getClass().getClassLoader();
+        }
+
+	if (log.isDebugEnabled()) {
+	    if (parentClassLoader != null) {
+		log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+					       parentClassLoader.toString()));
+	    } else {
+		log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+					       "<none>"));
+	    }
+        }
+
+        initClassPath();
+
+	if (context instanceof org.apache.jasper.servlet.JspCServletContext) {
+	    return;
+	}
+
+        if (Constants.IS_SECURITY_ENABLED) {
+            initSecurity();
+        }
+
+        // If this web application context is running from a
+        // directory, start the background compilation thread
+        String appBase = context.getRealPath("/");         
+        if (!options.getDevelopment()
+                && appBase != null
+                && options.getCheckInterval() > 0) {
+            lastCheck = System.currentTimeMillis();
+        }                                            
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * This web applications ServletContext
+     */
+    private ServletContext context;
+    private Options options;
+    private URLClassLoader parentClassLoader;
+    private PermissionCollection permissionCollection;
+    private CodeSource codeSource;                    
+    private String classpath;
+    private long lastCheck = -1L;
+
+    /**
+     * Maps JSP pages to their JspServletWrapper's
+     */
+    private Map<String, JspServletWrapper> jsps = new ConcurrentHashMap<String, JspServletWrapper>();
+ 
+
+    // ------------------------------------------------------ Public Methods
+
+    /**
+     * Add a new JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @param jsw Servlet wrapper for JSP
+     */
+    public void addWrapper(String jspUri, JspServletWrapper jsw) {
+        jsps.put(jspUri, jsw);
+    }
+
+    /**
+     * Get an already existing JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @return JspServletWrapper for JSP
+     */
+    public JspServletWrapper getWrapper(String jspUri) {
+        return jsps.get(jspUri);
+    }
+
+    /**
+     * Remove a  JspServletWrapper.
+     *
+     * @param jspUri JSP URI of JspServletWrapper to remove
+     */
+    public void removeWrapper(String jspUri) {
+        jsps.remove(jspUri);
+    }
+
+    /**
+     * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
+     * the number of JSPs that have been loaded into the webapp.
+     *
+     * @return The number of JSPs that have been loaded into the webapp
+     */
+    public int getJspCount() {
+        return jsps.size();
+    }
+
+    /**
+     * Get the SecurityManager Policy CodeSource for this web
+     * applicaiton context.
+     *
+     * @return CodeSource for JSP
+     */
+    public CodeSource getCodeSource() {
+        return codeSource;
+    }
+
+    /**
+     * Get the parent URLClassLoader.
+     *
+     * @return URLClassLoader parent
+     */
+    public URLClassLoader getParentClassLoader() {
+        return parentClassLoader;
+    }
+
+    /**
+     * Get the SecurityManager PermissionCollection for this
+     * web application context.
+     *
+     * @return PermissionCollection permissions
+     */
+    public PermissionCollection getPermissionCollection() {
+        return permissionCollection;
+    }
+
+    /**
+     * Process a "destory" event for this web application context.
+     */                                                        
+    public void destroy() {
+        Iterator servlets = jsps.values().iterator();
+        while (servlets.hasNext()) {
+            ((JspServletWrapper) servlets.next()).destroy();
+        }
+    }
+
+    /**
+     * Increments the JSP reload counter.
+     */
+    public synchronized void incrementJspReloadCount() {
+        jspReloadCount++;
+    }
+
+    /**
+     * Resets the JSP reload counter.
+     *
+     * @param count Value to which to reset the JSP reload counter
+     */
+    public synchronized void setJspReloadCount(int count) {
+        this.jspReloadCount = count;
+    }
+
+    /**
+     * Gets the current value of the JSP reload counter.
+     *
+     * @return The current value of the JSP reload counter
+     */
+    public int getJspReloadCount() {
+        return jspReloadCount;
+    }
+
+
+    /**
+     * Method used by background thread to check the JSP dependencies
+     * registered with this class for JSP's.
+     */
+    public void checkCompile() {
+
+        if (lastCheck < 0) {
+            // Checking was disabled
+            return;
+        }
+        long now = System.currentTimeMillis();
+        if (now > (lastCheck + (options.getCheckInterval() * 1000L))) {
+            lastCheck = now;
+        } else {
+            return;
+        }
+        
+        Object [] wrappers = jsps.values().toArray();
+        for (int i = 0; i < wrappers.length; i++ ) {
+            JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
+            JspCompilationContext ctxt = jsw.getJspEngineContext();
+            // JspServletWrapper also synchronizes on this when
+            // it detects it has to do a reload
+            synchronized(jsw) {
+                try {
+                    ctxt.compile();
+                } catch (FileNotFoundException ex) {
+                    ctxt.incrementRemoved();
+                } catch (Throwable t) {
+                    jsw.getServletContext().log("Background compile failed",
+						t);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * The classpath that is passed off to the Java compiler.
+     */
+    public String getClassPath() {
+        return classpath;
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Method used to initialize classpath for compiles.
+     */
+    private void initClassPath() {
+
+        URL [] urls = parentClassLoader.getURLs();
+        StringBuffer cpath = new StringBuffer();
+        String sep = System.getProperty("path.separator");
+
+        for(int i = 0; i < urls.length; i++) {
+            // Tomcat 4 can use URL's other than file URL's,
+            // a protocol other than file: will generate a
+            // bad file system path, so only add file:
+            // protocol URL's to the classpath.
+            
+            if( urls[i].getProtocol().equals("file") ) {
+                cpath.append((String)urls[i].getFile()+sep);
+            }
+        }    
+
+	cpath.append(options.getScratchDir() + sep);
+
+        String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH);
+        if (cp == null || cp.equals("")) {
+            cp = options.getClassPath();
+        }
+
+        classpath = cpath.toString() + cp;
+
+        if(log.isDebugEnabled()) {
+            log.debug("Compilation classpath initialized: " + getClassPath());
+        }
+    }
+
+    /**
+     * Method used to initialize SecurityManager data.
+     */
+    private void initSecurity() {
+
+        // Setup the PermissionCollection for this web app context
+        // based on the permissions configured for the root of the
+        // web app context directory, then add a file read permission
+        // for that directory.
+        Policy policy = Policy.getPolicy();
+        if( policy != null ) {
+            try {          
+                // Get the permissions for the web app context
+                String docBase = context.getRealPath("/");
+                if( docBase == null ) {
+                    docBase = options.getScratchDir().toString();
+                }
+                String codeBase = docBase;
+                if (!codeBase.endsWith(File.separator)){
+                    codeBase = codeBase + File.separator;
+                }
+                File contextDir = new File(codeBase);
+                URL url = contextDir.getCanonicalFile().toURL();
+                codeSource = new CodeSource(url,(Certificate[])null);
+                permissionCollection = policy.getPermissions(codeSource);
+
+                // Create a file read permission for web app context directory
+                if (!docBase.endsWith(File.separator)){
+                    permissionCollection.add
+                        (new FilePermission(docBase,"read"));
+                    docBase = docBase + File.separator;
+                } else {
+                    permissionCollection.add
+                        (new FilePermission
+                            (docBase.substring(0,docBase.length() - 1),"read"));
+                }
+                docBase = docBase + "-";
+                permissionCollection.add(new FilePermission(docBase,"read"));
+
+                // Create a file read permission for web app tempdir (work)
+                // directory
+                String workDir = options.getScratchDir().toString();
+                if (!workDir.endsWith(File.separator)){
+                    permissionCollection.add
+                        (new FilePermission(workDir,"read"));
+                    workDir = workDir + File.separator;
+                }
+                workDir = workDir + "-";
+                permissionCollection.add(new FilePermission(workDir,"read"));
+
+                // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase
+                permissionCollection.add( new RuntimePermission(
+                    "accessClassInPackage.org.apache.jasper.runtime") );
+
+                if (parentClassLoader instanceof URLClassLoader) {
+                    URL [] urls = parentClassLoader.getURLs();
+                    String jarUrl = null;
+                    String jndiUrl = null;
+                    for (int i=0; i<urls.length; i++) {
+                        if (jndiUrl == null
+                                && urls[i].toString().startsWith("jndi:") ) {
+                            jndiUrl = urls[i].toString() + "-";
+                        }
+                        if (jarUrl == null
+                                && urls[i].toString().startsWith("jar:jndi:")
+                                ) {
+                            jarUrl = urls[i].toString();
+                            jarUrl = jarUrl.substring(0,jarUrl.length() - 2);
+                            jarUrl = jarUrl.substring(0,
+                                     jarUrl.lastIndexOf('/')) + "/-";
+                        }
+                    }
+                    if (jarUrl != null) {
+                        permissionCollection.add(
+                                new FilePermission(jarUrl,"read"));
+                        permissionCollection.add(
+                                new FilePermission(jarUrl.substring(4),"read"));
+                    }
+                    if (jndiUrl != null)
+                        permissionCollection.add(
+                                new FilePermission(jndiUrl,"read") );
+                }
+            } catch(Exception e) {
+                context.log("Security Init for context failed",e);
+            }
+        }
+    }
+
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java
new file mode 100644
index 0000000..c1665d1
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java
@@ -0,0 +1,1176 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import javax.el.FunctionMapper;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+
+
+import org.apache.el.ExpressionFactoryImpl;
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.el.ExpressionEvaluatorImpl;
+import org.xml.sax.Attributes;
+
+/** 
+ * This class has all the utility method(s).
+ * Ideally should move all the bean containers here.
+ *
+ * @author Mandar Raje.
+ * @author Rajiv Mordani.
+ * @author Danno Ferrin
+ * @author Pierre Delisle
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+public class JspUtil {
+
+    private static final String WEB_INF_TAGS = "/WEB-INF/tags/";
+    private static final String META_INF_TAGS = "/META-INF/tags/";
+
+    // Delimiters for request-time expressions (JSP and XML syntax)
+    private static final String OPEN_EXPR  = "<%=";
+    private static final String CLOSE_EXPR = "%>";
+    private static final String OPEN_EXPR_XML  = "%=";
+    private static final String CLOSE_EXPR_XML = "%";
+
+    private static int tempSequenceNumber = 0;
+    
+    //private static ExpressionEvaluatorImpl expressionEvaluator
+    //= new ExpressionEvaluatorImpl();
+    
+    //tc6
+    private final static ExpressionEvaluator expressionEvaluator =
+        new ExpressionEvaluatorImpl(new ExpressionFactoryImpl());
+
+    private static final String javaKeywords[] = {
+        "abstract", "assert", "boolean", "break", "byte", "case",
+        "catch", "char", "class", "const", "continue",
+        "default", "do", "double", "else", "enum", "extends",
+        "final", "finally", "float", "for", "goto",
+        "if", "implements", "import", "instanceof", "int",
+        "interface", "long", "native", "new", "package",
+        "private", "protected", "public", "return", "short",
+        "static", "strictfp", "super", "switch", "synchronized",
+        "this", "throws", "transient", "try", "void",
+        "volatile", "while" };
+
+    public static final int CHUNKSIZE = 1024;
+        
+    public static char[] removeQuotes(char []chars) {
+        CharArrayWriter caw = new CharArrayWriter();
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] == '%' && chars[i+1] == '\\' &&
+                chars[i+2] == '>') {
+                caw.write('%');
+                caw.write('>');
+                i = i + 2;
+            } else {
+                caw.write(chars[i]);
+            }
+        }
+        return caw.toCharArray();
+    }
+
+    public static char[] escapeQuotes (char []chars) {
+        // Prescan to convert %\> to %>
+        String s = new String(chars);
+        while (true) {
+            int n = s.indexOf("%\\>");
+            if (n < 0)
+                break;
+            StringBuffer sb = new StringBuffer(s.substring(0, n));
+            sb.append("%>");
+            sb.append(s.substring(n + 3));
+            s = sb.toString();
+        }
+        chars = s.toCharArray();
+        return (chars);
+
+
+        // Escape all backslashes not inside a Java string literal
+        /*
+        CharArrayWriter caw = new CharArrayWriter();
+        boolean inJavaString = false;
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] == '"') inJavaString = !inJavaString;
+            // escape out the escape character
+            if (!inJavaString && (chars[i] == '\\')) caw.write('\\');
+            caw.write(chars[i]);
+        }
+        return caw.toCharArray();
+        */
+    }
+
+    /**
+     * Checks if the token is a runtime expression.
+     * In standard JSP syntax, a runtime expression starts with '<%' and
+     * ends with '%>'. When the JSP document is in XML syntax, a runtime
+     * expression starts with '%=' and ends with '%'.
+     *
+     * @param token The token to be checked
+     * return whether the token is a runtime expression or not.
+     */
+    public static boolean isExpression(String token, boolean isXml) {
+    String openExpr;
+    String closeExpr;
+    if (isXml) {
+        openExpr = OPEN_EXPR_XML;
+        closeExpr = CLOSE_EXPR_XML;
+    } else {
+        openExpr = OPEN_EXPR;
+        closeExpr = CLOSE_EXPR;
+    }
+    if (token.startsWith(openExpr) && token.endsWith(closeExpr)) {
+        return true;
+    } else {
+        return false;
+    }
+    }
+
+    /**
+     * @return the "expression" part of a runtime expression, 
+     * taking the delimiters out.
+     */
+    public static String getExpr (String expression, boolean isXml) {
+    String returnString;
+    String openExpr;
+    String closeExpr;
+    if (isXml) {
+        openExpr = OPEN_EXPR_XML;
+        closeExpr = CLOSE_EXPR_XML;
+    } else {
+        openExpr = OPEN_EXPR;
+        closeExpr = CLOSE_EXPR;
+    }
+    int length = expression.length();
+    if (expression.startsWith(openExpr) && 
+                expression.endsWith(closeExpr)) {
+        returnString = expression.substring(
+                               openExpr.length(), length - closeExpr.length());
+    } else {
+        returnString = "";
+    }
+    return returnString;
+    }
+
+    /**
+     * Takes a potential expression and converts it into XML form
+     */
+    public static String getExprInXml(String expression) {
+        String returnString;
+        int length = expression.length();
+
+        if (expression.startsWith(OPEN_EXPR) 
+                && expression.endsWith(CLOSE_EXPR)) {
+            returnString = expression.substring (1, length - 1);
+        } else {
+            returnString = expression;
+        }
+
+        return escapeXml(returnString.replace(Constants.ESC, '$'));
+    }
+
+    /**
+     * Checks to see if the given scope is valid.
+     *
+     * @param scope The scope to be checked
+     * @param n The Node containing the 'scope' attribute whose value is to be
+     * checked
+     * @param err error dispatcher
+     *
+     * @throws JasperException if scope is not null and different from
+     * &quot;page&quot;, &quot;request&quot;, &quot;session&quot;, and
+     * &quot;application&quot;
+     */
+    public static void checkScope(String scope, Node n, ErrorDispatcher err)
+            throws JasperException {
+    if (scope != null && !scope.equals("page") && !scope.equals("request")
+        && !scope.equals("session") && !scope.equals("application")) {
+        err.jspError(n, "jsp.error.invalid.scope", scope);
+    }
+    }
+
+    /**
+     * Checks if all mandatory attributes are present and if all attributes
+     * present have valid names.  Checks attributes specified as XML-style
+     * attributes as well as attributes specified using the jsp:attribute
+     * standard action. 
+     */
+    public static void checkAttributes(String typeOfTag,
+                       Node n,
+                       ValidAttribute[] validAttributes,
+                       ErrorDispatcher err)
+                throws JasperException {
+        Attributes attrs = n.getAttributes();
+        Mark start = n.getStart();
+    boolean valid = true;
+
+        // AttributesImpl.removeAttribute is broken, so we do this...
+        int tempLength = (attrs == null) ? 0 : attrs.getLength();
+    Vector temp = new Vector(tempLength, 1);
+        for (int i = 0; i < tempLength; i++) {
+            String qName = attrs.getQName(i);
+            if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:")))
+                temp.addElement(qName);
+        }
+
+        // Add names of attributes specified using jsp:attribute
+        Node.Nodes tagBody = n.getBody();
+        if( tagBody != null ) {
+            int numSubElements = tagBody.size();
+            for( int i = 0; i < numSubElements; i++ ) {
+                Node node = tagBody.getNode( i );
+                if( node instanceof Node.NamedAttribute ) {
+                    String attrName = node.getAttributeValue( "name" );
+                    temp.addElement( attrName );
+            // Check if this value appear in the attribute of the node
+            if (n.getAttributeValue(attrName) != null) {
+            err.jspError(n, "jsp.error.duplicate.name.jspattribute",
+                    attrName);
+            }
+                }
+                else {
+                    // Nothing can come before jsp:attribute, and only
+                    // jsp:body can come after it.
+                    break;
+                }
+            }
+        }
+
+    /*
+     * First check to see if all the mandatory attributes are present.
+     * If so only then proceed to see if the other attributes are valid
+     * for the particular tag.
+     */
+    String missingAttribute = null;
+
+    for (int i = 0; i < validAttributes.length; i++) {
+        int attrPos;    
+        if (validAttributes[i].mandatory) {
+                attrPos = temp.indexOf(validAttributes[i].name);
+        if (attrPos != -1) {
+            temp.remove(attrPos);
+            valid = true;
+        } else {
+            valid = false;
+            missingAttribute = validAttributes[i].name;
+            break;
+        }
+        }
+    }
+
+    // If mandatory attribute is missing then the exception is thrown
+    if (!valid)
+        err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag,
+             missingAttribute);
+
+    // Check to see if there are any more attributes for the specified tag.
+        int attrLeftLength = temp.size();
+    if (attrLeftLength == 0)
+        return;
+
+    // Now check to see if the rest of the attributes are valid too.
+    String attribute = null;
+
+    for (int j = 0; j < attrLeftLength; j++) {
+        valid = false;
+        attribute = (String) temp.elementAt(j);
+        for (int i = 0; i < validAttributes.length; i++) {
+        if (attribute.equals(validAttributes[i].name)) {
+            valid = true;
+            break;
+        }
+        }
+        if (!valid)
+        err.jspError(start, "jsp.error.invalid.attribute", typeOfTag,
+                 attribute);
+    }
+    // XXX *could* move EL-syntax validation here... (sb)
+    }
+    
+    public static String escapeQueryString(String unescString) {
+    if ( unescString == null )
+        return null;
+    
+    String escString    = "";
+    String shellSpChars = "\\\"";
+    
+    for(int index=0; index<unescString.length(); index++) {
+        char nextChar = unescString.charAt(index);
+        
+        if( shellSpChars.indexOf(nextChar) != -1 )
+        escString += "\\";
+        
+        escString += nextChar;
+    }
+    return escString;
+    }
+ 
+    /**
+     *  Escape the 5 entities defined by XML.
+     */
+    public static String escapeXml(String s) {
+        if (s == null) return null;
+        StringBuffer sb = new StringBuffer();
+        for(int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '<') {
+                sb.append("&lt;");
+            } else if (c == '>') {
+                sb.append("&gt;");
+            } else if (c == '\'') {
+                sb.append("&apos;");
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&quot;");
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Replaces any occurrences of the character <tt>replace</tt> with the
+     * string <tt>with</tt>.
+     */
+    public static String replace(String name, char replace, String with) {
+    StringBuffer buf = new StringBuffer();
+    int begin = 0;
+    int end;
+    int last = name.length();
+
+    while (true) {
+        end = name.indexOf(replace, begin);
+        if (end < 0) {
+        end = last;
+        }
+        buf.append(name.substring(begin, end));
+        if (end == last) {
+        break;
+        }
+        buf.append(with);
+        begin = end + 1;
+    }
+    
+    return buf.toString();
+    }
+
+    public static class ValidAttribute {
+    String name;
+    boolean mandatory;
+    boolean rtexprvalue;    // not used now
+
+    public ValidAttribute (String name, boolean mandatory,
+            boolean rtexprvalue )
+        {
+        this.name = name;
+        this.mandatory = mandatory;
+            this.rtexprvalue = rtexprvalue;
+        }
+
+       public ValidAttribute (String name, boolean mandatory) {
+            this( name, mandatory, false );
+    }
+
+    public ValidAttribute (String name) {
+        this (name, false);
+    }
+    }
+    
+    /**
+     * Convert a String value to 'boolean'.
+     * Besides the standard conversions done by
+     * Boolean.valueOf(s).booleanValue(), the value "yes"
+     * (ignore case) is also converted to 'true'. 
+     * If 's' is null, then 'false' is returned.
+     *
+     * @param s the string to be converted
+     * @return the boolean value associated with the string s
+     */
+    public static boolean booleanValue(String s) {
+    boolean b = false;
+    if (s != null) {
+        if (s.equalsIgnoreCase("yes")) {
+        b = true;
+        } else {
+        b = Boolean.valueOf(s).booleanValue();
+        }
+    }
+    return b;
+    }
+
+    /**
+     * Returns the <tt>Class</tt> object associated with the class or
+     * interface with the given string name.
+     *
+     * <p> The <tt>Class</tt> object is determined by passing the given string
+     * name to the <tt>Class.forName()</tt> method, unless the given string
+     * name represents a primitive type, in which case it is converted to a
+     * <tt>Class</tt> object by appending ".class" to it (e.g., "int.class").
+     */
+    public static Class toClass(String type, ClassLoader loader)
+        throws ClassNotFoundException {
+
+    Class c = null;
+    int i0 = type.indexOf('[');
+    int dims = 0;
+    if (i0 > 0) {
+        // This is an array.  Count the dimensions
+        for (int i = 0; i < type.length(); i++) {
+        if (type.charAt(i) == '[')
+            dims++;
+        }
+        type = type.substring(0, i0);
+    }
+
+    if ("boolean".equals(type))
+        c = boolean.class;
+    else if ("char".equals(type))
+        c = char.class;
+    else if ("byte".equals(type))
+        c =  byte.class;
+    else if ("short".equals(type))
+        c = short.class;
+    else if ("int".equals(type))
+        c = int.class;
+    else if ("long".equals(type))
+        c = long.class;
+    else if ("float".equals(type))
+        c = float.class;
+    else if ("double".equals(type))
+        c = double.class;
+    else if (type.indexOf('[') < 0)
+        c = loader.loadClass(type);
+
+    if (dims == 0)
+        return c;
+
+    if (dims == 1)
+        return java.lang.reflect.Array.newInstance(c, 1).getClass();
+
+    // Array of more than i dimension
+    return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass();
+    }
+    
+    /**
+     * Produces a String representing a call to the EL interpreter.
+     * @param expression a String containing zero or more "${}" expressions
+     * @param expectedType the expected type of the interpreted result
+     * @param fnmapvar Variable pointing to a function map.
+     * @param XmlEscape True if the result should do XML escaping
+     * @return a String representing a call to the EL interpreter.
+     */
+    public static String interpreterCall(boolean isTagFile,
+                     String expression,
+                                         Class expectedType,
+                                         String fnmapvar,
+                                         boolean XmlEscape ) 
+    {
+        /*
+         * Determine which context object to use.
+         */
+    String jspCtxt = null;
+    if (isTagFile)
+        jspCtxt = "this.getJspContext()";
+    else
+        jspCtxt = "_jspx_page_context";
+
+    /*
+         * Determine whether to use the expected type's textual name
+     * or, if it's a primitive, the name of its correspondent boxed
+     * type.
+         */
+    String targetType = expectedType.getName();
+    String primitiveConverterMethod = null;
+    if (expectedType.isPrimitive()) {
+        if (expectedType.equals(Boolean.TYPE)) {
+        targetType = Boolean.class.getName();
+        primitiveConverterMethod = "booleanValue";
+        } else if (expectedType.equals(Byte.TYPE)) {
+        targetType = Byte.class.getName();
+        primitiveConverterMethod = "byteValue";
+        } else if (expectedType.equals(Character.TYPE)) {
+        targetType = Character.class.getName();
+        primitiveConverterMethod = "charValue";
+        } else if (expectedType.equals(Short.TYPE)) {
+        targetType = Short.class.getName();
+        primitiveConverterMethod = "shortValue";
+        } else if (expectedType.equals(Integer.TYPE)) {
+        targetType = Integer.class.getName();
+        primitiveConverterMethod = "intValue";
+        } else if (expectedType.equals(Long.TYPE)) {
+        targetType = Long.class.getName();
+        primitiveConverterMethod = "longValue";
+        } else if (expectedType.equals(Float.TYPE)) {
+        targetType = Float.class.getName();
+        primitiveConverterMethod = "floatValue";
+        } else if (expectedType.equals(Double.TYPE)) { 
+        targetType = Double.class.getName();
+        primitiveConverterMethod = "doubleValue";
+        }
+    }
+ 
+    if (primitiveConverterMethod != null) {
+        XmlEscape = false;
+    }
+
+    /*
+         * Build up the base call to the interpreter.
+         */
+        // XXX - We use a proprietary call to the interpreter for now
+        // as the current standard machinery is inefficient and requires
+        // lots of wrappers and adapters.  This should all clear up once
+        // the EL interpreter moves out of JSTL and into its own project.
+        // In the future, this should be replaced by code that calls
+        // ExpressionEvaluator.parseExpression() and then cache the resulting
+        // expression objects.  The interpreterCall would simply select
+        // one of the pre-cached expressions and evaluate it.
+        // Note that PageContextImpl implements VariableResolver and
+        // the generated Servlet/SimpleTag implements FunctionMapper, so
+        // that machinery is already in place (mroth).
+    targetType = toJavaSourceType(targetType);
+    StringBuffer call = new StringBuffer(
+             "(" + targetType + ") "
+               + "org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate"
+               + "(" + Generator.quote(expression) + ", "
+               +       targetType + ".class, "
+           +       "(PageContext)" + jspCtxt 
+               +       ", " + fnmapvar
+           + ", " + XmlEscape
+               + ")");
+ 
+    /*
+         * Add the primitive converter method if we need to.
+         */
+    if (primitiveConverterMethod != null) {
+        call.insert(0, "(");
+        call.append(")." + primitiveConverterMethod + "()");
+    }
+ 
+    return call.toString();
+    }
+
+    /**
+     * Validates the syntax of all ${} expressions within the given string.
+     * @param where the approximate location of the expressions in the JSP page
+     * @param expressions a string containing zero or more "${}" expressions
+     * @param err an error dispatcher to use
+     * @deprecated now delegated to the org.apache.el Package
+     */
+    public static void validateExpressions(Mark where,
+                                           String expressions,
+                                           Class expectedType,
+                                           FunctionMapper functionMapper,
+                                           ErrorDispatcher err)
+            throws JasperException {
+
+//        try {
+//            
+//            JspUtil.expressionEvaluator.parseExpression( expressions, 
+//                expectedType, functionMapper );
+//        }
+//        catch( ELParseException e ) {
+//            err.jspError(where, "jsp.error.invalid.expression", expressions,
+//                e.toString() );
+//        }
+//        catch( ELException e ) {
+//            err.jspError(where, "jsp.error.invalid.expression", expressions,
+//                e.toString() );
+//        }
+    }
+
+    /**
+     * Resets the temporary variable name.
+     * (not thread-safe)
+     * @deprecated
+     */
+    public static void resetTemporaryVariableName() {
+        tempSequenceNumber = 0;
+    }
+
+    /**
+     * Generates a new temporary variable name.
+     * (not thread-safe)
+     * @deprecated
+     */
+    public static String nextTemporaryVariableName() {
+        return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+    }
+
+    public static String coerceToPrimitiveBoolean(String s,
+                          boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToBoolean(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "false";
+        else
+        return Boolean.valueOf(s).toString();
+    }
+    }
+
+    public static String coerceToBoolean(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Boolean) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Boolean.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Boolean(false)";
+        } else {
+        // Detect format error at translation time
+        return "new Boolean(" + Boolean.valueOf(s).toString() + ")";
+        }
+    }
+    }
+
+    public static String coerceToPrimitiveByte(String s,
+                           boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToByte(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "(byte) 0";
+        else
+        return "((byte)" + Byte.valueOf(s).toString() + ")";
+    }
+    }
+
+    public static String coerceToByte(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Byte) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Byte.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Byte((byte) 0)";
+        } else {
+        // Detect format error at translation time
+        return "new Byte((byte)" + Byte.valueOf(s).toString() + ")";
+        }
+    }
+    }
+
+    public static String coerceToChar(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToChar(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "(char) 0";
+        } else {
+        char ch = s.charAt(0);
+        // this trick avoids escaping issues
+        return "((char) " + (int) ch + ")";
+        }
+    }
+    }
+
+    public static String coerceToCharacter(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Character) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Character.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Character((char) 0)";
+        } else {
+        char ch = s.charAt(0);
+        // this trick avoids escaping issues
+        return "new Character((char) " + (int) ch + ")";
+        }
+    }
+    }
+
+    public static String coerceToPrimitiveDouble(String s,
+                         boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToDouble(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "(double) 0";
+        else
+        return Double.valueOf(s).toString();
+    }
+    }
+
+    public static String coerceToDouble(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Double) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Double.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Double(0)";
+        } else {
+        // Detect format error at translation time
+        return "new Double(" + Double.valueOf(s).toString() + ")";
+        }
+    }
+    }
+
+    public static String coerceToPrimitiveFloat(String s,
+                        boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToFloat(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "(float) 0";
+        else
+        return Float.valueOf(s).toString() + "f";
+    }
+    }
+
+    public static String coerceToFloat(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Float) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Float.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Float(0)";
+        } else {
+        // Detect format error at translation time
+        return "new Float(" + Float.valueOf(s).toString() + "f)";
+        }
+    }
+    }
+
+    public static String coerceToInt(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToInt(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "0";
+        else
+        return Integer.valueOf(s).toString();
+    }
+    }
+
+    public static String coerceToInteger(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Integer) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Integer.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Integer(0)";
+        } else {
+        // Detect format error at translation time
+        return "new Integer(" + Integer.valueOf(s).toString() + ")";
+        }
+    }
+    }
+
+    public static String coerceToPrimitiveShort(String s,
+                        boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToShort(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "(short) 0";
+        else
+        return "((short) " + Short.valueOf(s).toString() + ")";
+    }
+    }
+    
+    public static String coerceToShort(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Short) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Short.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Short((short) 0)";
+        } else {
+        // Detect format error at translation time
+        return "new Short(\"" + Short.valueOf(s).toString() + "\")";
+        }
+    }
+    }
+    
+    public static String coerceToPrimitiveLong(String s,
+                           boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToLong(" + s + ")";
+    } else {
+        if (s == null || s.length() == 0)
+        return "(long) 0";
+        else
+        return Long.valueOf(s).toString() + "l";
+    }
+    }
+
+    public static String coerceToLong(String s, boolean isNamedAttribute) {
+    if (isNamedAttribute) {
+        return "(Long) org.apache.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Long.class)";
+    } else {
+        if (s == null || s.length() == 0) {
+        return "new Long(0)";
+        } else {
+        // Detect format error at translation time
+        return "new Long(" + Long.valueOf(s).toString() + "l)";
+        }
+    }
+    }
+
+    public static InputStream getInputStream(String fname, JarFile jarFile,
+                         JspCompilationContext ctxt,
+                         ErrorDispatcher err)
+        throws JasperException, IOException {
+
+        InputStream in = null;
+
+    if (jarFile != null) {
+        String jarEntryName = fname.substring(1, fname.length());
+        ZipEntry jarEntry = jarFile.getEntry(jarEntryName);
+        if (jarEntry == null) {
+        err.jspError("jsp.error.file.not.found", fname);
+        }
+        in = jarFile.getInputStream(jarEntry);
+    } else {
+        in = ctxt.getResourceAsStream(fname);
+    }
+
+    if (in == null) {
+        err.jspError("jsp.error.file.not.found", fname);
+    }
+
+    return in;
+    }
+
+    /**
+     * Gets the fully-qualified class name of the tag handler corresponding to
+     * the given tag file path.
+     *
+     * @param path Tag file path
+     * @param err Error dispatcher
+     *
+     * @return Fully-qualified class name of the tag handler corresponding to 
+     * the given tag file path
+     * 
+     * @deprecated Use {@link #getTagHandlerClassName(String, String,
+     *             ErrorDispatcher)
+     *             See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+     */
+    public static String getTagHandlerClassName(String path,
+                        ErrorDispatcher err)
+                throws JasperException {
+        return getTagHandlerClassName(path, null, err);
+    }
+    
+    /**
+     * Gets the fully-qualified class name of the tag handler corresponding to
+     * the given tag file path.
+     * 
+     * @param path
+     *            Tag file path
+     * @param err
+     *            Error dispatcher
+     * 
+     * @return Fully-qualified class name of the tag handler corresponding to
+     *         the given tag file path
+     */
+    public static String getTagHandlerClassName(String path, String urn,
+            ErrorDispatcher err) throws JasperException {
+
+        String className = null;
+        int begin = 0;
+        int index;
+        
+        index = path.lastIndexOf(".tag");
+        if (index == -1) {
+            err.jspError("jsp.error.tagfile.badSuffix", path);
+        }
+
+        //It's tempting to remove the ".tag" suffix here, but we can't.
+        //If we remove it, the fully-qualified class name of this tag
+        //could conflict with the package name of other tags.
+        //For instance, the tag file
+        //    /WEB-INF/tags/foo.tag
+        //would have fully-qualified class name
+        //    org.apache.jsp.tag.web.foo
+        //which would conflict with the package name of the tag file
+        //    /WEB-INF/tags/foo/bar.tag
+
+        index = path.indexOf(WEB_INF_TAGS);
+        if (index != -1) {
+            className = "org.apache.jsp.tag.web.";
+            begin = index + WEB_INF_TAGS.length();
+        } else {
+            index = path.indexOf(META_INF_TAGS);
+            if (index != -1) {
+                className = getClassNameBase(urn);
+                begin = index + META_INF_TAGS.length();
+            } else {
+                err.jspError("jsp.error.tagfile.illegalPath", path);
+            }
+        }
+
+        className += makeJavaPackage(path.substring(begin));
+  
+       return className;
+    }
+
+    private static String getClassNameBase(String urn) {
+        StringBuffer base = new StringBuffer("org.apache.jsp.tag.meta.");
+        if (urn != null) {
+            base.append(makeJavaPackage(urn));
+            base.append('.');
+        }
+        return base.toString();
+    }
+
+    /**
+     * Converts the given path to a Java package or fully-qualified class name
+     *
+     * @param path Path to convert
+     *
+     * @return Java package corresponding to the given path
+     */
+    public static final String makeJavaPackage(String path) {
+        String classNameComponents[] = split(path,"/");
+        StringBuffer legalClassNames = new StringBuffer();
+        for (int i = 0; i < classNameComponents.length; i++) {
+            legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
+            if (i < classNameComponents.length - 1) {
+                legalClassNames.append('.');
+            }
+        }
+        return legalClassNames.toString();
+    }
+
+    /**
+     * Splits a string into it's components.
+     * @param path String to split
+     * @param pat Pattern to split at
+     * @return the components of the path
+     */
+    private static final String [] split(String path, String pat) {
+        Vector comps = new Vector();
+        int pos = path.indexOf(pat);
+        int start = 0;
+        while( pos >= 0 ) {
+            if(pos > start ) {
+                String comp = path.substring(start,pos);
+                comps.add(comp);
+            }
+            start = pos + pat.length();
+            pos = path.indexOf(pat,start);
+        }
+        if( start < path.length()) {
+            comps.add(path.substring(start));
+        }
+        String [] result = new String[comps.size()];
+        for(int i=0; i < comps.size(); i++) {
+            result[i] = (String)comps.elementAt(i);
+        }
+        return result;
+    }
+            
+    /**
+     * Converts the given identifier to a legal Java identifier
+     *
+     * @param identifier Identifier to convert
+     *
+     * @return Legal Java identifier corresponding to the given identifier
+     */
+    public static final String makeJavaIdentifier(String identifier) {
+        StringBuffer modifiedIdentifier = 
+            new StringBuffer(identifier.length());
+        if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
+            modifiedIdentifier.append('_');
+        }
+        for (int i = 0; i < identifier.length(); i++) {
+            char ch = identifier.charAt(i);
+            if (Character.isJavaIdentifierPart(ch) && ch != '_') {
+                modifiedIdentifier.append(ch);
+            } else if (ch == '.') {
+                modifiedIdentifier.append('_');
+            } else {
+                modifiedIdentifier.append(mangleChar(ch));
+            }
+        }
+        if (isJavaKeyword(modifiedIdentifier.toString())) {
+            modifiedIdentifier.append('_');
+        }
+        return modifiedIdentifier.toString();
+    }
+    
+    /**
+     * Mangle the specified character to create a legal Java class name.
+     */
+    public static final String mangleChar(char ch) {
+        char[] result = new char[5];
+        result[0] = '_';
+        result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
+        result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
+        result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
+        result[4] = Character.forDigit(ch & 0xf, 16);
+        return new String(result);
+    }
+
+    /**
+     * Test whether the argument is a Java keyword
+     */
+    public static boolean isJavaKeyword(String key) {
+        int i = 0;
+        int j = javaKeywords.length;
+        while (i < j) {
+            int k = (i+j)/2;
+            int result = javaKeywords[k].compareTo(key);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k+1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Converts the given Xml name to a legal Java identifier.  This is
+     * slightly more efficient than makeJavaIdentifier in that we only need
+     * to worry about '.', '-', and ':' in the string.  We also assume that
+     * the resultant string is further concatenated with some prefix string
+     * so that we don't have to worry about it being a Java key word.
+     *
+     * @param name Identifier to convert
+     *
+     * @return Legal Java identifier corresponding to the given identifier
+     */
+    public static final String makeXmlJavaIdentifier(String name) {
+        if (name.indexOf('-') >= 0)
+            name = replace(name, '-', "$1");
+        if (name.indexOf('.') >= 0)
+            name = replace(name, '.', "$2");
+        if (name.indexOf(':') >= 0)
+            name = replace(name, ':', "$3");
+        return name;
+    }
+
+    static InputStreamReader getReader(String fname, String encoding,
+            JarFile jarFile,
+            JspCompilationContext ctxt,
+            ErrorDispatcher err)
+    throws JasperException, IOException {
+
+        return getReader(fname, encoding, jarFile, ctxt, err, 0);
+    }
+
+    static InputStreamReader getReader(String fname, String encoding,
+            JarFile jarFile,
+            JspCompilationContext ctxt,
+            ErrorDispatcher err, int skip)
+    throws JasperException, IOException {
+
+        InputStreamReader reader = null;
+        InputStream in = getInputStream(fname, jarFile, ctxt, err);
+        for (int i = 0; i < skip; i++) {
+            in.read();
+        }
+        try {
+            reader = new InputStreamReader(in, encoding);
+        } catch (UnsupportedEncodingException ex) {
+            err.jspError("jsp.error.unsupported.encoding", encoding);
+        }
+
+        return reader;
+    }
+    
+    /**
+     * Handles taking input from TLDs
+     * 'java.lang.Object' -> 'java.lang.Object.class'
+     * 'int' -> 'int.class'
+     * 'void' -> 'Void.TYPE'
+     * 'int[]' -> 'int[].class'
+     * 
+     * @param type
+     * @return
+     */
+    public static String toJavaSourceTypeFromTld(String type) {
+        if (type == null || "void".equals(type)) {
+            return "Void.TYPE";
+        }
+        return type + ".class";
+    }
+
+    /**
+     * Class.getName() return arrays in the form "[[[<et>", where et,
+     * the element type can be one of ZBCDFIJS or L<classname>;
+     * It is converted into forms that can be understood by javac.
+     */
+    public static String toJavaSourceType(String type) {
+
+    if (type.charAt(0) != '[') {
+        return type;
+    }
+
+    int dims = 1;
+    String t = null;
+    for (int i = 1; i < type.length(); i++) {
+        if (type.charAt(i) == '[') {
+        dims++;
+        } else {
+        switch (type.charAt(i)) {
+        case 'Z': t = "boolean"; break;
+        case 'B': t = "byte"; break;
+        case 'C': t = "char"; break;
+        case 'D': t = "double"; break;
+        case 'F': t = "float"; break;
+        case 'I': t = "int"; break;
+        case 'J': t = "long"; break;
+        case 'S': t = "short"; break;
+        case 'L': t = type.substring(i+1, type.indexOf(';')); break;
+        }
+        break;
+        }
+    }
+    StringBuffer resultType = new StringBuffer(t);
+    for (; dims > 0; dims--) {
+        resultType.append("[]");
+    }
+    return resultType.toString();
+    }
+
+    /**
+     * Compute the canonical name from a Class instance.  Note that a
+     * simple replacment of '$' with '.' of a binary name would not work,
+     * as '$' is a legal Java Identifier character.
+     * @param c A instance of java.lang.Class
+     * @return  The canonical name of c.
+     */
+    public static String getCanonicalName(Class c) {
+
+        String binaryName = c.getName();
+        c = c.getDeclaringClass();
+
+        if (c == null) {
+            return binaryName;
+        }
+
+        StringBuffer buf = new StringBuffer(binaryName);
+        do {
+            buf.setCharAt(c.getName().length(), '.');
+            c = c.getDeclaringClass();
+        } while ( c != null);
+
+        return buf.toString();
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java
new file mode 100644
index 0000000..260ac51
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java
@@ -0,0 +1,160 @@
+/*
+ * 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.jasper.compiler;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Class responsible for converting error codes to corresponding localized
+ * error messages.
+ *
+ * @author Jan Luehe
+ */
+public class Localizer {
+
+    private static ResourceBundle bundle = null;
+    
+    static {
+        try {
+        bundle = ResourceBundle.getBundle(
+            "org.apache.jasper.resources.LocalStrings");
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * 
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode) {
+	String errMsg = errCode;
+	try {
+	    errMsg = bundle.getString(errCode);
+	} catch (MissingResourceException e) {
+	}
+	return errMsg;
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg Argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg) {
+	return getMessage(errCode, new Object[] {arg});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2) {
+	return getMessage(errCode, new Object[] {arg1, arg2});
+    }
+    
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+				    String arg3) {
+	return getMessage(errCode, new Object[] {arg1, arg2, arg3});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     * @param arg4 Fourth argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+				    String arg3, String arg4) {
+	return getMessage(errCode, new Object[] {arg1, arg2, arg3, arg4});
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param args Arguments for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, Object[] args) {
+	String errMsg = errCode;
+	try {
+	    errMsg = bundle.getString(errCode);
+	    if (args != null) {
+		MessageFormat formatter = new MessageFormat(errMsg);
+		errMsg = formatter.format(args);
+	    }
+	} catch (MissingResourceException e) {
+	}
+	
+	return errMsg;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Mark.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Mark.java
new file mode 100644
index 0000000..85c942b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Mark.java
@@ -0,0 +1,282 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.Stack;
+import java.net.URL;
+import java.net.MalformedURLException;
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Mark represents a point in the JSP input. 
+ *
+ * @author Anil K. Vijendran
+ */
+final class Mark {
+
+    // position within current stream
+    int cursor, line, col;
+
+    // directory of file for current stream
+    String baseDir;
+
+    // current stream
+    char[] stream = null;
+
+    // fileid of current stream
+    private int fileId;
+
+    // name of the current file
+    private String fileName;
+
+    /*
+     * stack of stream and stream state of streams that have included
+     * current stream
+     */
+    private Stack includeStack = null;
+
+    // encoding of current file
+    private String encoding = null;
+
+    // reader that owns this mark (so we can look up fileid's)
+    private JspReader reader;
+
+    private JspCompilationContext ctxt;
+
+    /**
+     * Constructor
+     *
+     * @param reader JspReader this mark belongs to
+     * @param inStream current stream for this mark
+     * @param fileId id of requested jsp file
+     * @param name JSP file name
+     * @param inBaseDir base directory of requested jsp file
+     * @param inEncoding encoding of current file
+     */
+    Mark(JspReader reader, char[] inStream, int fileId, String name,
+         String inBaseDir, String inEncoding) {
+
+        this.reader = reader;
+        this.ctxt = reader.getJspCompilationContext();
+        this.stream = inStream;
+        this.cursor = 0;
+        this.line = 1;
+        this.col = 1;
+        this.fileId = fileId;
+        this.fileName = name;
+        this.baseDir = inBaseDir;
+        this.encoding = inEncoding;
+        this.includeStack = new Stack();
+    }
+
+
+    /**
+     * Constructor
+     */
+    Mark(Mark other) {
+
+        this.reader = other.reader;
+        this.ctxt = other.reader.getJspCompilationContext();
+        this.stream = other.stream;
+        this.fileId = other.fileId;
+        this.fileName = other.fileName;
+        this.cursor = other.cursor;
+        this.line = other.line;
+        this.col = other.col;
+        this.baseDir = other.baseDir;
+        this.encoding = other.encoding;
+
+        // clone includeStack without cloning contents
+        includeStack = new Stack();
+        for ( int i=0; i < other.includeStack.size(); i++ ) {
+            includeStack.addElement( other.includeStack.elementAt(i) );
+        }
+    }
+
+
+    /**
+     * Constructor
+     */    
+    Mark(JspCompilationContext ctxt, String filename, int line, int col) {
+
+        this.reader = null;
+        this.ctxt = ctxt;
+        this.stream = null;
+        this.cursor = 0;
+        this.line = line;
+        this.col = col;
+        this.fileId = -1;
+        this.fileName = filename;
+        this.baseDir = "le-basedir";
+        this.encoding = "le-endocing";
+        this.includeStack = null;
+    }
+
+
+    /**
+     * Sets this mark's state to a new stream.
+     * It will store the current stream in it's includeStack.
+     *
+     * @param inStream new stream for mark
+     * @param inFileId id of new file from which stream comes from
+     * @param inBaseDir directory of file
+     * @param inEncoding encoding of new file
+     */
+    public void pushStream(char[] inStream, int inFileId, String name,
+                           String inBaseDir, String inEncoding) 
+    {
+        // store current state in stack
+        includeStack.push(new IncludeState(cursor, line, col, fileId,
+                                           fileName, baseDir, 
+					   encoding, stream) );
+
+        // set new variables
+        cursor = 0;
+        line = 1;
+        col = 1;
+        fileId = inFileId;
+        fileName = name;
+        baseDir = inBaseDir;
+        encoding = inEncoding;
+        stream = inStream;
+    }
+
+
+    /**
+     * Restores this mark's state to a previously stored stream.
+     * @return The previous Mark instance when the stream was pushed, or null
+     * if there is no previous stream
+     */
+    public Mark popStream() {
+        // make sure we have something to pop
+        if ( includeStack.size() <= 0 ) {
+            return null;
+        }
+
+        // get previous state in stack
+        IncludeState state = (IncludeState) includeStack.pop( );
+
+        // set new variables
+        cursor = state.cursor;
+        line = state.line;
+        col = state.col;
+        fileId = state.fileId;
+        fileName = state.fileName;
+        baseDir = state.baseDir;
+        stream = state.stream;
+        return this;
+    }
+
+
+    // -------------------- Locator interface --------------------
+
+    public int getLineNumber() {
+        return line;
+    }
+
+    public int getColumnNumber() {
+        return col;
+    }
+
+    public String getSystemId() {
+        return getFile();
+    }
+
+    public String getPublicId() {
+        return null;
+    }
+
+    public String toString() {
+	return getFile()+"("+line+","+col+")";
+    }
+
+    public String getFile() {
+        return this.fileName;
+    }
+
+    /**
+     * Gets the URL of the resource with which this Mark is associated
+     *
+     * @return URL of the resource with which this Mark is associated
+     *
+     * @exception MalformedURLException if the resource pathname is incorrect
+     */
+    public URL getURL() throws MalformedURLException {
+        return ctxt.getResource(getFile());
+    }
+
+    public String toShortString() {
+        return "("+line+","+col+")";
+    }
+
+    public boolean equals(Object other) {
+	if (other instanceof Mark) {
+	    Mark m = (Mark) other;
+	    return this.reader == m.reader && this.fileId == m.fileId 
+		&& this.cursor == m.cursor && this.line == m.line 
+		&& this.col == m.col;
+	} 
+	return false;
+    }
+
+    /**
+     * @return true if this Mark is greather than the <code>other</code>
+     * Mark, false otherwise.
+     */
+    public boolean isGreater(Mark other) {
+
+        boolean greater = false;
+
+        if (this.line > other.line) {
+            greater = true;
+        } else if (this.line == other.line && this.col > other.col) {
+            greater = true;
+        }
+
+        return greater;
+    }
+
+    /**
+     * Keep track of parser before parsing an included file.
+     * This class keeps track of the parser before we switch to parsing an
+     * included file. In other words, it's the parser's continuation to be
+     * reinstalled after the included file parsing is done.
+     */
+    class IncludeState {
+        int cursor, line, col;
+        int fileId;
+        String fileName;
+        String baseDir;
+        String encoding;
+        char[] stream = null;
+
+        IncludeState(int inCursor, int inLine, int inCol, int inFileId, 
+                     String name, String inBaseDir, String inEncoding,
+                     char[] inStream) {
+            cursor = inCursor;
+            line = inLine;
+            col = inCol;
+            fileId = inFileId;
+            fileName = name;
+            baseDir = inBaseDir;
+            encoding = inEncoding;
+            stream = inStream;
+        }
+    }
+
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Node.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Node.java
new file mode 100644
index 0000000..2aa504f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Node.java
@@ -0,0 +1,2567 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.ArrayList;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.DynamicAttributes;
+import javax.servlet.jsp.tagext.IterationTag;
+import javax.servlet.jsp.tagext.JspIdConsumer;
+import javax.servlet.jsp.tagext.SimpleTag;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.TryCatchFinally;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.xml.sax.Attributes;
+
+/**
+ * An internal data representation of a JSP page or a JSP docuement (XML). Also
+ * included here is a visitor class for tranversing nodes.
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+abstract class Node implements TagConstants {
+
+    private static final VariableInfo[] ZERO_VARIABLE_INFO = {};
+
+    protected Attributes attrs;
+
+    // xmlns attributes that represent tag libraries (only in XML syntax)
+    protected Attributes taglibAttrs;
+
+    /*
+     * xmlns attributes that do not represent tag libraries (only in XML syntax)
+     */
+    protected Attributes nonTaglibXmlnsAttrs;
+
+    protected Nodes body;
+
+    protected String text;
+
+    protected Mark startMark;
+
+    protected int beginJavaLine;
+
+    protected int endJavaLine;
+
+    protected Node parent;
+
+    protected Nodes namedAttributeNodes; // cached for performance
+
+    protected String qName;
+
+    protected String localName;
+
+    /*
+     * The name of the inner class to which the codes for this node and its body
+     * are generated. For instance, for <jsp:body> in foo.jsp, this is
+     * "foo_jspHelper". This is primarily used for communicating such info from
+     * Generator to Smap generator.
+     */
+    protected String innerClassName;
+
+    private boolean isDummy;
+
+    /**
+     * Zero-arg Constructor.
+     */
+    public Node() {
+        this.isDummy = true;
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(Mark start, Node parent) {
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param qName
+     *            The action's qualified name
+     * @param localName
+     *            The action's local name
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(String qName, String localName, Mark start, Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /**
+     * Constructor for Nodes parsed from standard syntax.
+     * 
+     * @param qName
+     *            The action's qualified name
+     * @param localName
+     *            The action's local name
+     * @param attrs
+     *            The attributes for this node
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(String qName, String localName, Attributes attrs, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.attrs = attrs;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /**
+     * Constructor for Nodes parsed from XML syntax.
+     * 
+     * @param qName
+     *            The action's qualified name
+     * @param localName
+     *            The action's local name
+     * @param attrs
+     *            The action's attributes whose name does not start with xmlns
+     * @param nonTaglibXmlnsAttrs
+     *            The action's xmlns attributes that do not represent tag
+     *            libraries
+     * @param taglibAttrs
+     *            The action's xmlns attributes that represent tag libraries
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(String qName, String localName, Attributes attrs,
+            Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.attrs = attrs;
+        this.nonTaglibXmlnsAttrs = nonTaglibXmlnsAttrs;
+        this.taglibAttrs = taglibAttrs;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /*
+     * Constructor.
+     * 
+     * @param qName The action's qualified name @param localName The action's
+     * local name @param text The text associated with this node @param start
+     * The location of the jsp page @param parent The enclosing node
+     */
+    public Node(String qName, String localName, String text, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.text = text;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    public String getQName() {
+        return this.qName;
+    }
+
+    public String getLocalName() {
+        return this.localName;
+    }
+
+    /*
+     * Gets this Node's attributes.
+     * 
+     * In the case of a Node parsed from standard syntax, this method returns
+     * all the Node's attributes.
+     * 
+     * In the case of a Node parsed from XML syntax, this method returns only
+     * those attributes whose name does not start with xmlns.
+     */
+    public Attributes getAttributes() {
+        return this.attrs;
+    }
+
+    /*
+     * Gets this Node's xmlns attributes that represent tag libraries (only
+     * meaningful for Nodes parsed from XML syntax)
+     */
+    public Attributes getTaglibAttributes() {
+        return this.taglibAttrs;
+    }
+
+    /*
+     * Gets this Node's xmlns attributes that do not represent tag libraries
+     * (only meaningful for Nodes parsed from XML syntax)
+     */
+    public Attributes getNonTaglibXmlnsAttributes() {
+        return this.nonTaglibXmlnsAttrs;
+    }
+
+    public void setAttributes(Attributes attrs) {
+        this.attrs = attrs;
+    }
+
+    public String getAttributeValue(String name) {
+        return (attrs == null) ? null : attrs.getValue(name);
+    }
+
+    /**
+     * Get the attribute that is non request time expression, either from the
+     * attribute of the node, or from a jsp:attrbute
+     */
+    public String getTextAttribute(String name) {
+
+        String attr = getAttributeValue(name);
+        if (attr != null) {
+            return attr;
+        }
+
+        NamedAttribute namedAttribute = getNamedAttributeNode(name);
+        if (namedAttribute == null) {
+            return null;
+        }
+
+        return namedAttribute.getText();
+    }
+
+    /**
+     * Searches all subnodes of this node for jsp:attribute standard actions
+     * with the given name, and returns the NamedAttribute node of the matching
+     * named attribute, nor null if no such node is found.
+     * <p>
+     * This should always be called and only be called for nodes that accept
+     * dynamic runtime attribute expressions.
+     */
+    public NamedAttribute getNamedAttributeNode(String name) {
+        NamedAttribute result = null;
+
+        // Look for the attribute in NamedAttribute children
+        Nodes nodes = getNamedAttributeNodes();
+        int numChildNodes = nodes.size();
+        for (int i = 0; i < numChildNodes; i++) {
+            NamedAttribute na = (NamedAttribute) nodes.getNode(i);
+            boolean found = false;
+            int index = name.indexOf(':');
+            if (index != -1) {
+                // qualified name
+                found = na.getName().equals(name);
+            } else {
+                found = na.getLocalName().equals(name);
+            }
+            if (found) {
+                result = na;
+                break;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Searches all subnodes of this node for jsp:attribute standard actions,
+     * and returns that set of nodes as a Node.Nodes object.
+     * 
+     * @return Possibly empty Node.Nodes object containing any jsp:attribute
+     *         subnodes of this Node
+     */
+    public Node.Nodes getNamedAttributeNodes() {
+
+        if (namedAttributeNodes != null) {
+            return namedAttributeNodes;
+        }
+
+        Node.Nodes result = new Node.Nodes();
+
+        // Look for the attribute in NamedAttribute children
+        Nodes nodes = getBody();
+        if (nodes != null) {
+            int numChildNodes = nodes.size();
+            for (int i = 0; i < numChildNodes; i++) {
+                Node n = nodes.getNode(i);
+                if (n instanceof NamedAttribute) {
+                    result.add(n);
+                } else if (!(n instanceof Comment)) {
+                    // Nothing can come before jsp:attribute, and only
+                    // jsp:body can come after it.
+                    break;
+                }
+            }
+        }
+
+        namedAttributeNodes = result;
+        return result;
+    }
+
+    public Nodes getBody() {
+        return body;
+    }
+
+    public void setBody(Nodes body) {
+        this.body = body;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public Mark getStart() {
+        return startMark;
+    }
+
+    public Node getParent() {
+        return parent;
+    }
+
+    public int getBeginJavaLine() {
+        return beginJavaLine;
+    }
+
+    public void setBeginJavaLine(int begin) {
+        beginJavaLine = begin;
+    }
+
+    public int getEndJavaLine() {
+        return endJavaLine;
+    }
+
+    public void setEndJavaLine(int end) {
+        endJavaLine = end;
+    }
+
+    public boolean isDummy() {
+        return isDummy;
+    }
+
+    public Node.Root getRoot() {
+        Node n = this;
+        while (!(n instanceof Node.Root)) {
+            n = n.getParent();
+        }
+        return (Node.Root) n;
+    }
+
+    public String getInnerClassName() {
+        return innerClassName;
+    }
+
+    public void setInnerClassName(String icn) {
+        innerClassName = icn;
+    }
+
+    /**
+     * Selects and invokes a method in the visitor class based on the node type.
+     * This is abstract and should be overrode by the extending classes.
+     * 
+     * @param v
+     *            The visitor class
+     */
+    abstract void accept(Visitor v) throws JasperException;
+
+    // *********************************************************************
+    // Private utility methods
+
+    /*
+     * Adds this Node to the body of the given parent.
+     */
+    private void addToParent(Node parent) {
+        if (parent != null) {
+            this.parent = parent;
+            Nodes parentBody = parent.getBody();
+            if (parentBody == null) {
+                parentBody = new Nodes();
+                parent.setBody(parentBody);
+            }
+            parentBody.add(this);
+        }
+    }
+
+    /***************************************************************************
+     * Child classes
+     */
+
+    /**
+     * Represents the root of a Jsp page or Jsp document
+     */
+    public static class Root extends Node {
+
+        private Root parentRoot;
+
+        private boolean isXmlSyntax;
+
+        // Source encoding of the page containing this Root
+        private String pageEnc;
+
+        // Page encoding specified in JSP config element
+        private String jspConfigPageEnc;
+
+        /*
+         * Flag indicating if the default page encoding is being used (only
+         * applicable with standard syntax).
+         * 
+         * True if the page does not provide a page directive with a
+         * 'contentType' attribute (or the 'contentType' attribute doesn't have
+         * a CHARSET value), the page does not provide a page directive with a
+         * 'pageEncoding' attribute, and there is no JSP configuration element
+         * page-encoding whose URL pattern matches the page.
+         */
+        private boolean isDefaultPageEncoding;
+
+        /*
+         * Indicates whether an encoding has been explicitly specified in the
+         * page's XML prolog (only used for pages in XML syntax). This
+         * information is used to decide whether a translation error must be
+         * reported for encoding conflicts.
+         */
+        private boolean isEncodingSpecifiedInProlog;
+
+        /*
+         * Indicates whether an encoding has been explicitly specified in the
+         * page's bom.
+         */
+        private boolean isBomPresent;
+
+        /*
+         * Sequence number for temporary variables.
+         */
+        private int tempSequenceNumber = 0;
+
+        /*
+         * Constructor.
+         */
+        Root(Mark start, Node parent, boolean isXmlSyntax) {
+            super(start, parent);
+            this.isXmlSyntax = isXmlSyntax;
+            this.qName = JSP_ROOT_ACTION;
+            this.localName = ROOT_ACTION;
+
+            // Figure out and set the parent root
+            Node r = parent;
+            while ((r != null) && !(r instanceof Node.Root))
+                r = r.getParent();
+            parentRoot = (Node.Root) r;
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public boolean isXmlSyntax() {
+            return isXmlSyntax;
+        }
+
+        /*
+         * Sets the encoding specified in the JSP config element whose URL
+         * pattern matches the page containing this Root.
+         */
+        public void setJspConfigPageEncoding(String enc) {
+            jspConfigPageEnc = enc;
+        }
+
+        /*
+         * Gets the encoding specified in the JSP config element whose URL
+         * pattern matches the page containing this Root.
+         */
+        public String getJspConfigPageEncoding() {
+            return jspConfigPageEnc;
+        }
+
+        public void setPageEncoding(String enc) {
+            pageEnc = enc;
+        }
+
+        public String getPageEncoding() {
+            return pageEnc;
+        }
+
+        public void setIsDefaultPageEncoding(boolean isDefault) {
+            isDefaultPageEncoding = isDefault;
+        }
+
+        public boolean isDefaultPageEncoding() {
+            return isDefaultPageEncoding;
+        }
+
+        public void setIsEncodingSpecifiedInProlog(boolean isSpecified) {
+            isEncodingSpecifiedInProlog = isSpecified;
+        }
+
+        public boolean isEncodingSpecifiedInProlog() {
+            return isEncodingSpecifiedInProlog;
+        }
+
+        public void setIsBomPresent(boolean isBom) {
+            isBomPresent = isBom;
+        }
+
+        public boolean isBomPresent() {
+            return isBomPresent;
+        }
+
+        /**
+         * @return The enclosing root to this Root. Usually represents the page
+         *         that includes this one.
+         */
+        public Root getParentRoot() {
+            return parentRoot;
+        }
+        
+        /**
+         * Generates a new temporary variable name.
+         */
+        public String nextTemporaryVariableName() {
+            if (parentRoot == null) {
+                return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+            } else {
+                return parentRoot.nextTemporaryVariableName();
+            }
+            
+        }
+    }
+
+    /**
+     * Represents the root of a Jsp document (XML syntax)
+     */
+    public static class JspRoot extends Node {
+
+        public JspRoot(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ROOT_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a page directive
+     */
+    public static class PageDirective extends Node {
+
+        private Vector imports;
+
+        public PageDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PAGE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public PageDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PAGE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            imports = new Vector();
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Parses the comma-separated list of class or package names in the
+         * given attribute value and adds each component to this PageDirective's
+         * vector of imported classes and packages.
+         * 
+         * @param value
+         *            A comma-separated string of imports.
+         */
+        public void addImport(String value) {
+            int start = 0;
+            int index;
+            while ((index = value.indexOf(',', start)) != -1) {
+                imports.add(value.substring(start, index).trim());
+                start = index + 1;
+            }
+            if (start == 0) {
+                // No comma found
+                imports.add(value.trim());
+            } else {
+                imports.add(value.substring(start).trim());
+            }
+        }
+
+        public List getImports() {
+            return imports;
+        }
+    }
+
+    /**
+     * Represents an include directive
+     */
+    public static class IncludeDirective extends Node {
+
+        public IncludeDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INCLUDE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public IncludeDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INCLUDE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a custom taglib directive
+     */
+    public static class TaglibDirective extends Node {
+
+        public TaglibDirective(Attributes attrs, Mark start, Node parent) {
+            super(JSP_TAGLIB_DIRECTIVE_ACTION, TAGLIB_DIRECTIVE_ACTION, attrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a tag directive
+     */
+    public static class TagDirective extends Node {
+        private Vector imports;
+
+        public TagDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_TAG_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public TagDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, TAG_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            imports = new Vector();
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Parses the comma-separated list of class or package names in the
+         * given attribute value and adds each component to this PageDirective's
+         * vector of imported classes and packages.
+         * 
+         * @param value
+         *            A comma-separated string of imports.
+         */
+        public void addImport(String value) {
+            int start = 0;
+            int index;
+            while ((index = value.indexOf(',', start)) != -1) {
+                imports.add(value.substring(start, index).trim());
+                start = index + 1;
+            }
+            if (start == 0) {
+                // No comma found
+                imports.add(value.trim());
+            } else {
+                imports.add(value.substring(start).trim());
+            }
+        }
+
+        public List getImports() {
+            return imports;
+        }
+    }
+
+    /**
+     * Represents an attribute directive
+     */
+    public static class AttributeDirective extends Node {
+
+        public AttributeDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ATTRIBUTE_DIRECTIVE_ACTION, attrs, null, null, start,
+                    parent);
+        }
+
+        public AttributeDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ATTRIBUTE_DIRECTIVE_ACTION, attrs,
+                    nonTaglibXmlnsAttrs, taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a variable directive
+     */
+    public static class VariableDirective extends Node {
+
+        public VariableDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_VARIABLE_DIRECTIVE_ACTION, attrs, null, null, start,
+                    parent);
+        }
+
+        public VariableDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, VARIABLE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a <jsp:invoke> tag file action
+     */
+    public static class InvokeAction extends Node {
+
+        public InvokeAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INVOKE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public InvokeAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INVOKE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a <jsp:doBody> tag file action
+     */
+    public static class DoBodyAction extends Node {
+
+        public DoBodyAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_DOBODY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public DoBodyAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, DOBODY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a Jsp comment Comments are kept for completeness.
+     */
+    public static class Comment extends Node {
+
+        public Comment(String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an expression, declaration, or scriptlet
+     */
+    public static abstract class ScriptingElement extends Node {
+
+        public ScriptingElement(String qName, String localName, String text,
+                Mark start, Node parent) {
+            super(qName, localName, text, start, parent);
+        }
+
+        public ScriptingElement(String qName, String localName,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, localName, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        /**
+         * When this node was created from a JSP page in JSP syntax, its text
+         * was stored as a String in the "text" field, whereas when this node
+         * was created from a JSP document, its text was stored as one or more
+         * TemplateText nodes in its body. This method handles either case.
+         * 
+         * @return The text string
+         */
+        public String getText() {
+            String ret = text;
+            if (ret == null) {
+                if (body != null) {
+                    StringBuffer buf = new StringBuffer();
+                    for (int i = 0; i < body.size(); i++) {
+                        buf.append(body.getNode(i).getText());
+                    }
+                    ret = buf.toString();
+                } else {
+                    // Nulls cause NPEs further down the line
+                    ret = "";
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * For the same reason as above, the source line information in the
+         * contained TemplateText node should be used.
+         */
+        public Mark getStart() {
+            if (text == null && body != null && body.size() > 0) {
+                return body.getNode(0).getStart();
+            } else {
+                return super.getStart();
+            }
+        }
+    }
+
+    /**
+     * Represents a declaration
+     */
+    public static class Declaration extends ScriptingElement {
+
+        public Declaration(String text, Mark start, Node parent) {
+            super(JSP_DECLARATION_ACTION, DECLARATION_ACTION, text, start,
+                    parent);
+        }
+
+        public Declaration(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, DECLARATION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an expression. Expressions in attributes are embedded in the
+     * attribute string and not here.
+     */
+    public static class Expression extends ScriptingElement {
+
+        public Expression(String text, Mark start, Node parent) {
+            super(JSP_EXPRESSION_ACTION, EXPRESSION_ACTION, text, start, parent);
+        }
+
+        public Expression(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, EXPRESSION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a scriptlet
+     */
+    public static class Scriptlet extends ScriptingElement {
+
+        public Scriptlet(String text, Mark start, Node parent) {
+            super(JSP_SCRIPTLET_ACTION, SCRIPTLET_ACTION, text, start, parent);
+        }
+
+        public Scriptlet(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, SCRIPTLET_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an EL expression. Expressions in attributes are embedded in
+     * the attribute string and not here.
+     */
+    public static class ELExpression extends Node {
+
+        private ELNode.Nodes el;
+
+        private final char type;
+
+        public ELExpression(char type, String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+            this.type = type;
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setEL(ELNode.Nodes el) {
+            this.el = el;
+        }
+
+        public ELNode.Nodes getEL() {
+            return el;
+        }
+
+        public char getType() {
+            return this.type;
+        }
+    }
+
+    /**
+     * Represents a param action
+     */
+    public static class ParamAction extends Node {
+
+        JspAttribute value;
+
+        public ParamAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PARAM_ACTION, attrs, null, null, start, parent);
+        }
+
+        public ParamAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PARAM_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setValue(JspAttribute value) {
+            this.value = value;
+        }
+
+        public JspAttribute getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Represents a params action
+     */
+    public static class ParamsAction extends Node {
+
+        public ParamsAction(Mark start, Node parent) {
+            this(JSP_PARAMS_ACTION, null, null, start, parent);
+        }
+
+        public ParamsAction(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, PARAMS_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a fallback action
+     */
+    public static class FallBackAction extends Node {
+
+        public FallBackAction(Mark start, Node parent) {
+            this(JSP_FALLBACK_ACTION, null, null, start, parent);
+        }
+
+        public FallBackAction(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, FALLBACK_ACTION, null, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an include action
+     */
+    public static class IncludeAction extends Node {
+
+        private JspAttribute page;
+
+        public IncludeAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INCLUDE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public IncludeAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INCLUDE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setPage(JspAttribute page) {
+            this.page = page;
+        }
+
+        public JspAttribute getPage() {
+            return page;
+        }
+    }
+
+    /**
+     * Represents a forward action
+     */
+    public static class ForwardAction extends Node {
+
+        private JspAttribute page;
+
+        public ForwardAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_FORWARD_ACTION, attrs, null, null, start, parent);
+        }
+
+        public ForwardAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, FORWARD_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setPage(JspAttribute page) {
+            this.page = page;
+        }
+
+        public JspAttribute getPage() {
+            return page;
+        }
+    }
+
+    /**
+     * Represents a getProperty action
+     */
+    public static class GetProperty extends Node {
+
+        public GetProperty(Attributes attrs, Mark start, Node parent) {
+            this(JSP_GET_PROPERTY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public GetProperty(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, GET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a setProperty action
+     */
+    public static class SetProperty extends Node {
+
+        private JspAttribute value;
+
+        public SetProperty(Attributes attrs, Mark start, Node parent) {
+            this(JSP_SET_PROPERTY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public SetProperty(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, SET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setValue(JspAttribute value) {
+            this.value = value;
+        }
+
+        public JspAttribute getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Represents a useBean action
+     */
+    public static class UseBean extends Node {
+
+        JspAttribute beanName;
+
+        public UseBean(Attributes attrs, Mark start, Node parent) {
+            this(JSP_USE_BEAN_ACTION, attrs, null, null, start, parent);
+        }
+
+        public UseBean(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, USE_BEAN_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setBeanName(JspAttribute beanName) {
+            this.beanName = beanName;
+        }
+
+        public JspAttribute getBeanName() {
+            return beanName;
+        }
+    }
+
+    /**
+     * Represents a plugin action
+     */
+    public static class PlugIn extends Node {
+
+        private JspAttribute width;
+
+        private JspAttribute height;
+
+        public PlugIn(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PLUGIN_ACTION, attrs, null, null, start, parent);
+        }
+
+        public PlugIn(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PLUGIN_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setHeight(JspAttribute height) {
+            this.height = height;
+        }
+
+        public void setWidth(JspAttribute width) {
+            this.width = width;
+        }
+
+        public JspAttribute getHeight() {
+            return height;
+        }
+
+        public JspAttribute getWidth() {
+            return width;
+        }
+    }
+
+    /**
+     * Represents an uninterpreted tag, from a Jsp document
+     */
+    public static class UninterpretedTag extends Node {
+
+        private JspAttribute[] jspAttrs;
+
+        public UninterpretedTag(String qName, String localName,
+                Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+    }
+
+    /**
+     * Represents a <jsp:element>.
+     */
+    public static class JspElement extends Node {
+
+        private JspAttribute[] jspAttrs;
+
+        private JspAttribute nameAttr;
+
+        public JspElement(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ELEMENT_ACTION, attrs, null, null, start, parent);
+        }
+
+        public JspElement(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ELEMENT_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+
+        /*
+         * Sets the XML-style 'name' attribute
+         */
+        public void setNameAttribute(JspAttribute nameAttr) {
+            this.nameAttr = nameAttr;
+        }
+
+        /*
+         * Gets the XML-style 'name' attribute
+         */
+        public JspAttribute getNameAttribute() {
+            return this.nameAttr;
+        }
+    }
+
+    /**
+     * Represents a <jsp:output>.
+     */
+    public static class JspOutput extends Node {
+
+        public JspOutput(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, OUTPUT_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Collected information about child elements. Used by nodes like CustomTag,
+     * JspBody, and NamedAttribute. The information is set in the Collector.
+     */
+    public static class ChildInfo {
+        private boolean scriptless; // true if the tag and its body
+
+        // contain no scripting elements.
+        private boolean hasUseBean;
+
+        private boolean hasIncludeAction;
+
+        private boolean hasParamAction;
+
+        private boolean hasSetProperty;
+
+        private boolean hasScriptingVars;
+
+        public void setScriptless(boolean s) {
+            scriptless = s;
+        }
+
+        public boolean isScriptless() {
+            return scriptless;
+        }
+
+        public void setHasUseBean(boolean u) {
+            hasUseBean = u;
+        }
+
+        public boolean hasUseBean() {
+            return hasUseBean;
+        }
+
+        public void setHasIncludeAction(boolean i) {
+            hasIncludeAction = i;
+        }
+
+        public boolean hasIncludeAction() {
+            return hasIncludeAction;
+        }
+
+        public void setHasParamAction(boolean i) {
+            hasParamAction = i;
+        }
+
+        public boolean hasParamAction() {
+            return hasParamAction;
+        }
+
+        public void setHasSetProperty(boolean s) {
+            hasSetProperty = s;
+        }
+
+        public boolean hasSetProperty() {
+            return hasSetProperty;
+        }
+
+        public void setHasScriptingVars(boolean s) {
+            hasScriptingVars = s;
+        }
+
+        public boolean hasScriptingVars() {
+            return hasScriptingVars;
+        }
+    }
+
+    /**
+     * Represents a custom tag
+     */
+    public static class CustomTag extends Node {
+
+        private String uri;
+
+        private String prefix;
+
+        private JspAttribute[] jspAttrs;
+
+        private TagData tagData;
+
+        private String tagHandlerPoolName;
+
+        private TagInfo tagInfo;
+
+        private TagFileInfo tagFileInfo;
+
+        private Class tagHandlerClass;
+
+        private VariableInfo[] varInfos;
+
+        private int customNestingLevel;
+
+        private ChildInfo childInfo;
+
+        private boolean implementsIterationTag;
+
+        private boolean implementsBodyTag;
+
+        private boolean implementsTryCatchFinally;
+
+        private boolean implementsJspIdConsumer;
+
+        private boolean implementsSimpleTag;
+
+        private boolean implementsDynamicAttributes;
+
+        private Vector atBeginScriptingVars;
+
+        private Vector atEndScriptingVars;
+
+        private Vector nestedScriptingVars;
+
+        private Node.CustomTag customTagParent;
+
+        private Integer numCount;
+
+        private boolean useTagPlugin;
+
+        private TagPluginContext tagPluginContext;
+
+        /**
+         * The following two fields are used for holding the Java scriptlets
+         * that the tag plugins may generate. Meaningful only if useTagPlugin is
+         * true; Could move them into TagPluginContextImpl, but we'll need to
+         * cast tagPluginContext to TagPluginContextImpl all the time...
+         */
+        private Nodes atSTag;
+
+        private Nodes atETag;
+
+        /*
+         * Constructor for custom action implemented by tag handler.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Mark start, Node parent,
+                TagInfo tagInfo, Class tagHandlerClass) {
+            this(qName, prefix, localName, uri, attrs, null, null, start,
+                    parent, tagInfo, tagHandlerClass);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag handler.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent,
+                TagInfo tagInfo, Class tagHandlerClass) {
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+
+            this.uri = uri;
+            this.prefix = prefix;
+            this.tagInfo = tagInfo;
+            this.tagHandlerClass = tagHandlerClass;
+            this.customNestingLevel = makeCustomNestingLevel();
+            this.childInfo = new ChildInfo();
+
+            this.implementsIterationTag = IterationTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsBodyTag = BodyTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsTryCatchFinally = TryCatchFinally.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsSimpleTag = SimpleTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsDynamicAttributes = DynamicAttributes.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsJspIdConsumer = JspIdConsumer.class
+                    .isAssignableFrom(tagHandlerClass);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag file.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Mark start, Node parent,
+                TagFileInfo tagFileInfo) {
+            this(qName, prefix, localName, uri, attrs, null, null, start,
+                    parent, tagFileInfo);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag file.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent,
+                TagFileInfo tagFileInfo) {
+
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+
+            this.uri = uri;
+            this.prefix = prefix;
+            this.tagFileInfo = tagFileInfo;
+            this.tagInfo = tagFileInfo.getTagInfo();
+            this.customNestingLevel = makeCustomNestingLevel();
+            this.childInfo = new ChildInfo();
+
+            this.implementsIterationTag = false;
+            this.implementsBodyTag = false;
+            this.implementsTryCatchFinally = false;
+            this.implementsSimpleTag = true;
+            this.implementsJspIdConsumer = false;
+            this.implementsDynamicAttributes = tagInfo.hasDynamicAttributes();
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * @return The URI namespace that this custom action belongs to
+         */
+        public String getURI() {
+            return this.uri;
+        }
+
+        /**
+         * @return The tag prefix
+         */
+        public String getPrefix() {
+            return prefix;
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public TagAttributeInfo getTagAttributeInfo(String name) {
+            TagInfo info = this.getTagInfo();
+            if (info == null)
+                return null;
+            TagAttributeInfo[] tai = info.getAttributes();
+            for (int i = 0; i < tai.length; i++) {
+                if (tai[i].getName().equals(name)) {
+                    return tai[i];
+                }
+            }
+            return null;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+
+        public ChildInfo getChildInfo() {
+            return childInfo;
+        }
+
+        public void setTagData(TagData tagData) {
+            this.tagData = tagData;
+            this.varInfos = tagInfo.getVariableInfo(tagData);
+            if (this.varInfos == null) {
+                this.varInfos = ZERO_VARIABLE_INFO;
+            }
+        }
+
+        public TagData getTagData() {
+            return tagData;
+        }
+
+        public void setTagHandlerPoolName(String s) {
+            tagHandlerPoolName = s;
+        }
+
+        public String getTagHandlerPoolName() {
+            return tagHandlerPoolName;
+        }
+
+        public TagInfo getTagInfo() {
+            return tagInfo;
+        }
+
+        public TagFileInfo getTagFileInfo() {
+            return tagFileInfo;
+        }
+
+        /*
+         * @return true if this custom action is supported by a tag file, false
+         * otherwise
+         */
+        public boolean isTagFile() {
+            return tagFileInfo != null;
+        }
+
+        public Class getTagHandlerClass() {
+            return tagHandlerClass;
+        }
+
+        public void setTagHandlerClass(Class hc) {
+            tagHandlerClass = hc;
+        }
+
+        public boolean implementsIterationTag() {
+            return implementsIterationTag;
+        }
+
+        public boolean implementsBodyTag() {
+            return implementsBodyTag;
+        }
+
+        public boolean implementsTryCatchFinally() {
+            return implementsTryCatchFinally;
+        }
+
+        public boolean implementsJspIdConsumer() {
+            return implementsJspIdConsumer;
+        }
+
+        public boolean implementsSimpleTag() {
+            return implementsSimpleTag;
+        }
+
+        public boolean implementsDynamicAttributes() {
+            return implementsDynamicAttributes;
+        }
+
+        public TagVariableInfo[] getTagVariableInfos() {
+            return tagInfo.getTagVariableInfos();
+        }
+
+        public VariableInfo[] getVariableInfos() {
+            return varInfos;
+        }
+
+        public void setCustomTagParent(Node.CustomTag n) {
+            this.customTagParent = n;
+        }
+
+        public Node.CustomTag getCustomTagParent() {
+            return this.customTagParent;
+        }
+
+        public void setNumCount(Integer count) {
+            this.numCount = count;
+        }
+
+        public Integer getNumCount() {
+            return this.numCount;
+        }
+
+        public void setScriptingVars(Vector vec, int scope) {
+            switch (scope) {
+            case VariableInfo.AT_BEGIN:
+                this.atBeginScriptingVars = vec;
+                break;
+            case VariableInfo.AT_END:
+                this.atEndScriptingVars = vec;
+                break;
+            case VariableInfo.NESTED:
+                this.nestedScriptingVars = vec;
+                break;
+            }
+        }
+
+        /*
+         * Gets the scripting variables for the given scope that need to be
+         * declared.
+         */
+        public Vector getScriptingVars(int scope) {
+            Vector vec = null;
+
+            switch (scope) {
+            case VariableInfo.AT_BEGIN:
+                vec = this.atBeginScriptingVars;
+                break;
+            case VariableInfo.AT_END:
+                vec = this.atEndScriptingVars;
+                break;
+            case VariableInfo.NESTED:
+                vec = this.nestedScriptingVars;
+                break;
+            }
+
+            return vec;
+        }
+
+        /*
+         * Gets this custom tag's custom nesting level, which is given as the
+         * number of times this custom tag is nested inside itself.
+         */
+        public int getCustomNestingLevel() {
+            return customNestingLevel;
+        }
+
+        /**
+         * Checks to see if the attribute of the given name is of type
+         * JspFragment.
+         */
+        public boolean checkIfAttributeIsJspFragment(String name) {
+            boolean result = false;
+
+            TagAttributeInfo[] attributes = tagInfo.getAttributes();
+            for (int i = 0; i < attributes.length; i++) {
+                if (attributes[i].getName().equals(name)
+                        && attributes[i].isFragment()) {
+                    result = true;
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        public void setUseTagPlugin(boolean use) {
+            useTagPlugin = use;
+        }
+
+        public boolean useTagPlugin() {
+            return useTagPlugin;
+        }
+
+        public void setTagPluginContext(TagPluginContext tagPluginContext) {
+            this.tagPluginContext = tagPluginContext;
+        }
+
+        public TagPluginContext getTagPluginContext() {
+            return tagPluginContext;
+        }
+
+        public void setAtSTag(Nodes sTag) {
+            atSTag = sTag;
+        }
+
+        public Nodes getAtSTag() {
+            return atSTag;
+        }
+
+        public void setAtETag(Nodes eTag) {
+            atETag = eTag;
+        }
+
+        public Nodes getAtETag() {
+            return atETag;
+        }
+
+        /*
+         * Computes this custom tag's custom nesting level, which corresponds to
+         * the number of times this custom tag is nested inside itself.
+         * 
+         * Example:
+         * 
+         * <g:h> <a:b> -- nesting level 0 <c:d> <e:f> <a:b> -- nesting level 1
+         * <a:b> -- nesting level 2 </a:b> </a:b> <a:b> -- nesting level 1
+         * </a:b> </e:f> </c:d> </a:b> </g:h>
+         * 
+         * @return Custom tag's nesting level
+         */
+        private int makeCustomNestingLevel() {
+            int n = 0;
+            Node p = parent;
+            while (p != null) {
+                if ((p instanceof Node.CustomTag)
+                        && qName.equals(((Node.CustomTag) p).qName)) {
+                    n++;
+                }
+                p = p.parent;
+            }
+            return n;
+        }
+
+        /**
+         * Returns true if this custom action has an empty body, and false
+         * otherwise.
+         * 
+         * A custom action is considered to have an empty body if the following
+         * holds true: - getBody() returns null, or - all immediate children are
+         * jsp:attribute actions, or - the action's jsp:body is empty.
+         */
+        public boolean hasEmptyBody() {
+            boolean hasEmptyBody = true;
+            Nodes nodes = getBody();
+            if (nodes != null) {
+                int numChildNodes = nodes.size();
+                for (int i = 0; i < numChildNodes; i++) {
+                    Node n = nodes.getNode(i);
+                    if (!(n instanceof NamedAttribute)) {
+                        if (n instanceof JspBody) {
+                            hasEmptyBody = (n.getBody() == null);
+                        } else {
+                            hasEmptyBody = false;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            return hasEmptyBody;
+        }
+    }
+
+    /**
+     * Used as a placeholder for the evaluation code of a custom action
+     * attribute (used by the tag plugin machinery only).
+     */
+    public static class AttributeGenerator extends Node {
+        String name; // name of the attribute
+
+        CustomTag tag; // The tag this attribute belongs to
+
+        public AttributeGenerator(Mark start, String name, CustomTag tag) {
+            super(start, null);
+            this.name = name;
+            this.tag = tag;
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public CustomTag getTag() {
+            return tag;
+        }
+    }
+
+    /**
+     * Represents the body of a &lt;jsp:text&gt; element
+     */
+    public static class JspText extends Node {
+
+        public JspText(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, TEXT_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a Named Attribute (&lt;jsp:attribute&gt;)
+     */
+    public static class NamedAttribute extends Node {
+
+        // A unique temporary variable name suitable for code generation
+        private String temporaryVariableName;
+
+        // True if this node is to be trimmed, or false otherwise
+        private boolean trim = true;
+
+        private ChildInfo childInfo;
+
+        private String name;
+
+        private String localName;
+
+        private String prefix;
+
+        public NamedAttribute(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ATTRIBUTE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public NamedAttribute(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+
+            super(qName, ATTRIBUTE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            if ("false".equals(this.getAttributeValue("trim"))) {
+                // (if null or true, leave default of true)
+                trim = false;
+            }
+            childInfo = new ChildInfo();
+            name = this.getAttributeValue("name");
+            if (name != null) {
+                // Mandatary attribute "name" will be checked in Validator
+                localName = name;
+                int index = name.indexOf(':');
+                if (index != -1) {
+                    prefix = name.substring(0, index);
+                    localName = name.substring(index + 1);
+                }
+            }
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public String getLocalName() {
+            return this.localName;
+        }
+
+        public String getPrefix() {
+            return this.prefix;
+        }
+
+        public ChildInfo getChildInfo() {
+            return this.childInfo;
+        }
+
+        public boolean isTrim() {
+            return trim;
+        }
+
+        /**
+         * @return A unique temporary variable name to store the result in.
+         *         (this probably could go elsewhere, but it's convenient here)
+         */
+        public String getTemporaryVariableName() {
+            if (temporaryVariableName == null) {
+                temporaryVariableName = getRoot().nextTemporaryVariableName();
+            }
+            return temporaryVariableName;
+        }
+
+        /*
+         * Get the attribute value from this named attribute (<jsp:attribute>).
+         * Since this method is only for attributes that are not rtexpr, we can
+         * assume the body of the jsp:attribute is a template text.
+         */
+        public String getText() {
+
+            class AttributeVisitor extends Visitor {
+                String attrValue = null;
+
+                public void visit(TemplateText txt) {
+                    attrValue = new String(txt.getText());
+                }
+
+                public String getAttrValue() {
+                    return attrValue;
+                }
+            }
+
+            // According to JSP 2.0, if the body of the <jsp:attribute>
+            // action is empty, it is equivalent of specifying "" as the value
+            // of the attribute.
+            String text = "";
+            if (getBody() != null) {
+                AttributeVisitor attributeVisitor = new AttributeVisitor();
+                try {
+                    getBody().visit(attributeVisitor);
+                } catch (JasperException e) {
+                }
+                text = attributeVisitor.getAttrValue();
+            }
+
+            return text;
+        }
+    }
+
+    /**
+     * Represents a JspBody node (&lt;jsp:body&gt;)
+     */
+    public static class JspBody extends Node {
+
+        private ChildInfo childInfo;
+
+        public JspBody(Mark start, Node parent) {
+            this(JSP_BODY_ACTION, null, null, start, parent);
+        }
+
+        public JspBody(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, BODY_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+            this.childInfo = new ChildInfo();
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public ChildInfo getChildInfo() {
+            return childInfo;
+        }
+    }
+
+    /**
+     * Represents a template text string
+     */
+    public static class TemplateText extends Node {
+
+        private ArrayList extraSmap = null;
+
+        public TemplateText(String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+        }
+
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Trim all whitespace from the left of the template text
+         */
+        public void ltrim() {
+            int index = 0;
+            while ((index < text.length()) && (text.charAt(index) <= ' ')) {
+                index++;
+            }
+            text = text.substring(index);
+        }
+
+        public void setText(String text) {
+            this.text = text;
+        }
+
+        /**
+         * Trim all whitespace from the right of the template text
+         */
+        public void rtrim() {
+            int index = text.length();
+            while ((index > 0) && (text.charAt(index - 1) <= ' ')) {
+                index--;
+            }
+            text = text.substring(0, index);
+        }
+
+        /**
+         * Returns true if this template text contains whitespace only.
+         */
+        public boolean isAllSpace() {
+            boolean isAllSpace = true;
+            for (int i = 0; i < text.length(); i++) {
+                if (!Character.isWhitespace(text.charAt(i))) {
+                    isAllSpace = false;
+                    break;
+                }
+            }
+            return isAllSpace;
+        }
+
+        /**
+         * Add a source to Java line mapping
+         * 
+         * @param srcLine
+         *            The postion of the source line, relative to the line at
+         *            the start of this node. The corresponding java line is
+         *            assumed to be consecutive, i.e. one more than the last.
+         */
+        public void addSmap(int srcLine) {
+            if (extraSmap == null) {
+                extraSmap = new ArrayList();
+            }
+            extraSmap.add(new Integer(srcLine));
+        }
+
+        public ArrayList getExtraSmap() {
+            return extraSmap;
+        }
+    }
+
+    /***************************************************************************
+     * Auxillary classes used in Node
+     */
+
+    /**
+     * Represents attributes that can be request time expressions.
+     * 
+     * Can either be a plain attribute, an attribute that represents a request
+     * time expression value, or a named attribute (specified using the
+     * jsp:attribute standard action).
+     */
+
+    public static class JspAttribute {
+
+        private String qName;
+
+        private String uri;
+
+        private String localName;
+
+        private String value;
+
+        private boolean expression;
+
+        private boolean dynamic;
+
+        private final ELNode.Nodes el;
+
+        private final TagAttributeInfo tai;
+
+        // If true, this JspAttribute represents a <jsp:attribute>
+        private boolean namedAttribute;
+
+        // The node in the parse tree for the NamedAttribute
+        private NamedAttribute namedAttributeNode;
+
+        JspAttribute(TagAttributeInfo tai, String qName, String uri,
+                String localName, String value, boolean expr, ELNode.Nodes el,
+                boolean dyn) {
+            this.qName = qName;
+            this.uri = uri;
+            this.localName = localName;
+            this.value = value;
+            this.namedAttributeNode = null;
+            this.expression = expr;
+            this.el = el;
+            this.dynamic = dyn;
+            this.namedAttribute = false;
+            this.tai = tai;
+        }
+
+        /**
+         * Allow node to validate itself
+         * 
+         * @param ef
+         * @param ctx
+         * @throws ELException
+         */
+        public void validateEL(ExpressionFactory ef, ELContext ctx)
+                throws ELException {
+            if (this.el != null) {
+                // determine exact type
+                ValueExpression ve = ef.createValueExpression(ctx, this.value,
+                        String.class);
+            }
+        }
+
+        /**
+         * Use this constructor if the JspAttribute represents a named
+         * attribute. In this case, we have to store the nodes of the body of
+         * the attribute.
+         */
+        JspAttribute(NamedAttribute na, TagAttributeInfo tai, boolean dyn) {
+            this.qName = na.getName();
+            this.localName = na.getLocalName();
+            this.value = null;
+            this.namedAttributeNode = na;
+            this.expression = false;
+            this.el = null;
+            this.dynamic = dyn;
+            this.namedAttribute = true;
+            this.tai = null;
+        }
+
+        /**
+         * @return The name of the attribute
+         */
+        public String getName() {
+            return qName;
+        }
+
+        /**
+         * @return The local name of the attribute
+         */
+        public String getLocalName() {
+            return localName;
+        }
+
+        /**
+         * @return The namespace of the attribute, or null if in the default
+         *         namespace
+         */
+        public String getURI() {
+            return uri;
+        }
+
+        public TagAttributeInfo getTagAttributeInfo() {
+            return this.tai;
+        }
+
+        /**
+         * 
+         * @return return true if there's TagAttributeInfo meaning we need to
+         *         assign a ValueExpression
+         */
+        public boolean isDeferredInput() {
+            return (this.tai != null) ? this.tai.isDeferredValue() : false;
+        }
+
+        /**
+         * 
+         * @return return true if there's TagAttributeInfo meaning we need to
+         *         assign a MethodExpression
+         */
+        public boolean isDeferredMethodInput() {
+            return (this.tai != null) ? this.tai.isDeferredMethod() : false;
+        }
+
+        public String getExpectedTypeName() {
+            if (this.tai != null) {
+                if (this.isDeferredInput()) {
+                    return this.tai.getExpectedTypeName();
+                } else if (this.isDeferredMethodInput()) {
+                    String m = this.tai.getMethodSignature();
+                    if (m != null) {
+                        int rti = m.trim().indexOf(' ');
+                        if (rti > 0) {
+                            return m.substring(0, rti).trim();
+                        }
+                    }
+                }
+            }
+            return "java.lang.Object";
+        }
+        
+        public String[] getParameterTypeNames() {
+            if (this.tai != null) {
+                if (this.isDeferredMethodInput()) {
+                    String m = this.tai.getMethodSignature();
+                    if (m != null) {
+                        m = m.trim();
+                        m = m.substring(m.indexOf('(') + 1);
+                        m = m.substring(0, m.length() - 1);
+                        if (m.trim().length() > 0) {
+                            String[] p = m.split(",");
+                            for (int i = 0; i < p.length; i++) {
+                                p[i] = p[i].trim();
+                            }
+                            return p;
+                        }
+                    }
+                }
+            }
+            return new String[0];
+        }
+
+        /**
+         * Only makes sense if namedAttribute is false.
+         * 
+         * @return the value for the attribute, or the expression string
+         *         (stripped of "<%=", "%>", "%=", or "%" but containing "${"
+         *         and "}" for EL expressions)
+         */
+        public String getValue() {
+            return value;
+        }
+
+        /**
+         * Only makes sense if namedAttribute is true.
+         * 
+         * @return the nodes that evaluate to the body of this attribute.
+         */
+        public NamedAttribute getNamedAttributeNode() {
+            return namedAttributeNode;
+        }
+
+        /**
+         * @return true if the value represents a traditional rtexprvalue
+         */
+        public boolean isExpression() {
+            return expression;
+        }
+
+        /**
+         * @return true if the value represents a NamedAttribute value.
+         */
+        public boolean isNamedAttribute() {
+            return namedAttribute;
+        }
+
+        /**
+         * @return true if the value represents an expression that should be fed
+         *         to the expression interpreter
+         * @return false for string literals or rtexprvalues that should not be
+         *         interpreted or reevaluated
+         */
+        public boolean isELInterpreterInput() {
+            return el != null || this.isDeferredInput()
+                    || this.isDeferredMethodInput();
+        }
+
+        /**
+         * @return true if the value is a string literal known at translation
+         *         time.
+         */
+        public boolean isLiteral() {
+            return !expression && (el != null) && !namedAttribute;
+        }
+
+        /**
+         * XXX
+         */
+        public boolean isDynamic() {
+            return dynamic;
+        }
+
+        public ELNode.Nodes getEL() {
+            return el;
+        }
+    }
+
+    /**
+     * An ordered list of Node, used to represent the body of an element, or a
+     * jsp page of jsp document.
+     */
+    public static class Nodes {
+
+        private List list;
+
+        private Node.Root root; // null if this is not a page
+
+        private boolean generatedInBuffer;
+
+        public Nodes() {
+            list = new Vector();
+        }
+
+        public Nodes(Node.Root root) {
+            this.root = root;
+            list = new Vector();
+            list.add(root);
+        }
+
+        /**
+         * Appends a node to the list
+         * 
+         * @param n
+         *            The node to add
+         */
+        public void add(Node n) {
+            list.add(n);
+            root = null;
+        }
+
+        /**
+         * Removes the given node from the list.
+         * 
+         * @param n
+         *            The node to be removed
+         */
+        public void remove(Node n) {
+            list.remove(n);
+        }
+
+        /**
+         * Visit the nodes in the list with the supplied visitor
+         * 
+         * @param v
+         *            The visitor used
+         */
+        public void visit(Visitor v) throws JasperException {
+            Iterator iter = list.iterator();
+            while (iter.hasNext()) {
+                Node n = (Node) iter.next();
+                n.accept(v);
+            }
+        }
+
+        public int size() {
+            return list.size();
+        }
+
+        public Node getNode(int index) {
+            Node n = null;
+            try {
+                n = (Node) list.get(index);
+            } catch (ArrayIndexOutOfBoundsException e) {
+            }
+            return n;
+        }
+
+        public Node.Root getRoot() {
+            return root;
+        }
+
+        public boolean isGeneratedInBuffer() {
+            return generatedInBuffer;
+        }
+
+        public void setGeneratedInBuffer(boolean g) {
+            generatedInBuffer = g;
+        }
+    }
+
+    /**
+     * A visitor class for visiting the node. This class also provides the
+     * default action (i.e. nop) for each of the child class of the Node. An
+     * actual visitor should extend this class and supply the visit method for
+     * the nodes that it cares.
+     */
+    public static class Visitor {
+
+        /**
+         * This method provides a place to put actions that are common to all
+         * nodes. Override this in the child visitor class if need to.
+         */
+        protected void doVisit(Node n) throws JasperException {
+        }
+
+        /**
+         * Visit the body of a node, using the current visitor
+         */
+        protected void visitBody(Node n) throws JasperException {
+            if (n.getBody() != null) {
+                n.getBody().visit(this);
+            }
+        }
+
+        public void visit(Root n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspRoot n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(PageDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(TagDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(IncludeDirective n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(TaglibDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(AttributeDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(VariableDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Comment n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Declaration n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Expression n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Scriptlet n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(ELExpression n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(IncludeAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ForwardAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(GetProperty n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(SetProperty n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ParamAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ParamsAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(FallBackAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(UseBean n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(PlugIn n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(CustomTag n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(UninterpretedTag n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspElement n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspText n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(NamedAttribute n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspBody n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(InvokeAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(DoBodyAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(TemplateText n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(JspOutput n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(AttributeGenerator n) throws JasperException {
+            doVisit(n);
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java
new file mode 100644
index 0000000..deb4adf
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java
@@ -0,0 +1,711 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ListIterator;
+import javax.servlet.jsp.tagext.PageData;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+import org.apache.jasper.JasperException;
+
+/**
+ * An implementation of <tt>javax.servlet.jsp.tagext.PageData</tt> which
+ * builds the XML view of a given page.
+ *
+ * The XML view is built in two passes:
+ *
+ * During the first pass, the FirstPassVisitor collects the attributes of the
+ * top-level jsp:root and those of the jsp:root elements of any included
+ * pages, and adds them to the jsp:root element of the XML view.
+ * In addition, any taglib directives are converted into xmlns: attributes and
+ * added to the jsp:root element of the XML view.
+ * This pass ignores any nodes other than JspRoot and TaglibDirective.
+ *
+ * During the second pass, the SecondPassVisitor produces the XML view, using
+ * the combined jsp:root attributes determined in the first pass and any
+ * remaining pages nodes (this pass ignores any JspRoot and TaglibDirective
+ * nodes).
+ *
+ * @author Jan Luehe
+ */
+class PageDataImpl extends PageData implements TagConstants {
+
+    private static final String JSP_VERSION = "2.0";
+    private static final String CDATA_START_SECTION = "<![CDATA[\n";
+    private static final String CDATA_END_SECTION = "]]>\n";
+
+    // string buffer used to build XML view
+    private StringBuffer buf;
+
+    /**
+     * Constructor.
+     *
+     * @param page the page nodes from which to generate the XML view
+     */
+    public PageDataImpl(Node.Nodes page, Compiler compiler)
+	        throws JasperException {
+
+	// First pass
+	FirstPassVisitor firstPass = new FirstPassVisitor(page.getRoot(),
+							  compiler.getPageInfo());
+	page.visit(firstPass);
+
+	// Second pass
+	buf = new StringBuffer();
+	SecondPassVisitor secondPass
+	    = new SecondPassVisitor(page.getRoot(), buf, compiler,
+				    firstPass.getJspIdPrefix());
+	page.visit(secondPass);
+    }
+
+    /**
+     * Returns the input stream of the XML view.
+     *
+     * @return the input stream of the XML view
+     */
+    public InputStream getInputStream() {
+	// Turn StringBuffer into InputStream
+        try {
+            return new ByteArrayInputStream(buf.toString().getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException uee) {
+	    // should never happen
+            throw new RuntimeException(uee.toString());
+        }
+    }
+
+    /*
+     * First-pass Visitor for JspRoot nodes (representing jsp:root elements)
+     * and TablibDirective nodes, ignoring any other nodes.
+     *
+     * The purpose of this Visitor is to collect the attributes of the
+     * top-level jsp:root and those of the jsp:root elements of any included
+     * pages, and add them to the jsp:root element of the XML view.
+     * In addition, this Visitor converts any taglib directives into xmlns:
+     * attributes and adds them to the jsp:root element of the XML view.
+     */
+    static class FirstPassVisitor
+	        extends Node.Visitor implements TagConstants {
+
+	private Node.Root root;
+	private AttributesImpl rootAttrs;
+	private PageInfo pageInfo;
+
+	// Prefix for the 'id' attribute
+	private String jspIdPrefix;
+
+	/*
+	 * Constructor
+	 */
+	public FirstPassVisitor(Node.Root root, PageInfo pageInfo) {
+	    this.root = root;
+	    this.pageInfo = pageInfo;
+	    this.rootAttrs = new AttributesImpl();
+	    this.rootAttrs.addAttribute("", "", "version", "CDATA",
+					JSP_VERSION);
+	    this.jspIdPrefix = "jsp";
+	}
+
+	public void visit(Node.Root n) throws JasperException {
+	    visitBody(n);
+	    if (n == root) {
+		/*
+		 * Top-level page.
+		 *
+		 * Add
+		 *   xmlns:jsp="http://java.sun.com/JSP/Page"
+		 * attribute only if not already present.
+		 */
+		if (!JSP_URI.equals(rootAttrs.getValue("xmlns:jsp"))) {
+		    rootAttrs.addAttribute("", "", "xmlns:jsp", "CDATA",
+					   JSP_URI);
+		}
+
+		if (pageInfo.isJspPrefixHijacked()) {
+		    /*
+		     * 'jsp' prefix has been hijacked, that is, bound to a
+		     * namespace other than the JSP namespace. This means that
+		     * when adding an 'id' attribute to each element, we can't
+		     * use the 'jsp' prefix. Therefore, create a new prefix 
+		     * (one that is unique across the translation unit) for use
+		     * by the 'id' attribute, and bind it to the JSP namespace
+		     */
+		    jspIdPrefix += "jsp";
+		    while (pageInfo.containsPrefix(jspIdPrefix)) {
+			jspIdPrefix += "jsp";
+		    }
+		    rootAttrs.addAttribute("", "", "xmlns:" + jspIdPrefix,
+					   "CDATA", JSP_URI);
+		}
+
+		root.setAttributes(rootAttrs);
+	    }
+	}
+
+	public void visit(Node.JspRoot n) throws JasperException {
+	    addAttributes(n.getTaglibAttributes());
+            addAttributes(n.getNonTaglibXmlnsAttributes());
+	    addAttributes(n.getAttributes());
+
+	    visitBody(n);
+	}
+
+	/*
+	 * Converts taglib directive into "xmlns:..." attribute of jsp:root
+	 * element.
+	 */
+	public void visit(Node.TaglibDirective n) throws JasperException {
+	    Attributes attrs = n.getAttributes();
+	    if (attrs != null) {
+		String qName = "xmlns:" + attrs.getValue("prefix");
+		/*
+		 * According to javadocs of org.xml.sax.helpers.AttributesImpl,
+		 * the addAttribute method does not check to see if the
+		 * specified attribute is already contained in the list: This
+		 * is the application's responsibility!
+		 */
+		if (rootAttrs.getIndex(qName) == -1) {
+		    String location = attrs.getValue("uri");
+		    if (location != null) {
+                        if (location.startsWith("/")) {
+                            location = URN_JSPTLD + location;
+                        }
+			rootAttrs.addAttribute("", "", qName, "CDATA",
+					       location);
+		    } else {
+			location = attrs.getValue("tagdir");
+			rootAttrs.addAttribute("", "", qName, "CDATA",
+					       URN_JSPTAGDIR + location);
+		    }
+		}
+	    }
+	}
+
+	public String getJspIdPrefix() {
+	    return jspIdPrefix;
+	}
+
+	private void addAttributes(Attributes attrs) {
+	    if (attrs != null) {
+		int len = attrs.getLength();
+
+		for (int i=0; i<len; i++) {
+                    String qName = attrs.getQName(i);
+		    if ("version".equals(qName)) {
+			continue;
+		    }
+
+                    // Bugzilla 35252: http://issues.apache.org/bugzilla/show_bug.cgi?id=35252
+                    if(rootAttrs.getIndex(qName) == -1) {
+                        rootAttrs.addAttribute(attrs.getURI(i),
+                                               attrs.getLocalName(i),
+                                               qName,
+                                               attrs.getType(i),
+                                               attrs.getValue(i));
+                    }
+		}
+	    }
+	}
+    }
+
+
+    /*
+     * Second-pass Visitor responsible for producing XML view and assigning
+     * each element a unique jsp:id attribute.
+     */
+    static class SecondPassVisitor extends Node.Visitor
+        	implements TagConstants {
+
+	private Node.Root root;
+	private StringBuffer buf;
+	private Compiler compiler;
+	private String jspIdPrefix;
+	private boolean resetDefaultNS = false;
+
+	// Current value of jsp:id attribute
+	private int jspId;
+
+	/*
+	 * Constructor
+	 */
+	public SecondPassVisitor(Node.Root root, StringBuffer buf,
+				 Compiler compiler, String jspIdPrefix) {
+	    this.root = root;
+	    this.buf = buf;
+	    this.compiler = compiler;
+	    this.jspIdPrefix = jspIdPrefix;
+	}
+
+	/*
+	 * Visits root node.
+	 */
+	public void visit(Node.Root n) throws JasperException {
+	    if (n == this.root) {
+		// top-level page
+		appendXmlProlog();
+		appendTag(n);
+	    } else {
+		boolean resetDefaultNSSave = resetDefaultNS;
+		if (n.isXmlSyntax()) {
+		    resetDefaultNS = true;
+		}
+		visitBody(n);
+		resetDefaultNS = resetDefaultNSSave;
+	    }
+	}
+
+	/*
+	 * Visits jsp:root element of JSP page in XML syntax.
+	 *
+	 * Any nested jsp:root elements (from pages included via an
+	 * include directive) are ignored.
+	 */
+	public void visit(Node.JspRoot n) throws JasperException {
+	    visitBody(n);
+	}
+
+	public void visit(Node.PageDirective n) throws JasperException {
+	    appendPageDirective(n);
+	}
+
+	public void visit(Node.IncludeDirective n) throws JasperException {
+	    // expand in place
+	    visitBody(n);
+	}
+
+	public void visit(Node.Comment n) throws JasperException {
+	    // Comments are ignored in XML view
+	}
+
+	public void visit(Node.Declaration n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.Expression n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.Scriptlet n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.JspElement n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.ELExpression n) throws JasperException {
+	    if (!n.getRoot().isXmlSyntax()) {
+		buf.append("<").append(JSP_TEXT_ACTION);
+		buf.append(" ");
+	        buf.append(jspIdPrefix);
+		buf.append(":id=\"");
+		buf.append(jspId++).append("\">");
+	    }
+	    buf.append("${");
+            buf.append(JspUtil.escapeXml(n.getText()));
+	    buf.append("}");
+	    if (!n.getRoot().isXmlSyntax()) {
+		buf.append(JSP_TEXT_ACTION_END);
+	    }
+	    buf.append("\n");
+	}
+
+	public void visit(Node.IncludeAction n) throws JasperException {
+	    appendTag(n);
+	}
+    
+	public void visit(Node.ForwardAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.GetProperty n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.SetProperty n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.ParamAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.ParamsAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.FallBackAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.UseBean n) throws JasperException {
+	    appendTag(n);
+	}
+	
+	public void visit(Node.PlugIn n) throws JasperException {
+	    appendTag(n);
+	}
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            appendTag(n);
+        }
+        
+        public void visit(Node.JspBody n) throws JasperException {
+            appendTag(n);
+        }
+
+	public void visit(Node.CustomTag n) throws JasperException {
+	    boolean resetDefaultNSSave = resetDefaultNS;
+	    appendTag(n, resetDefaultNS);
+	    resetDefaultNS = resetDefaultNSSave;
+	}
+
+	public void visit(Node.UninterpretedTag n) throws JasperException {
+	    boolean resetDefaultNSSave = resetDefaultNS;
+	    appendTag(n, resetDefaultNS);
+	    resetDefaultNS = resetDefaultNSSave;
+	}
+
+	public void visit(Node.JspText n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.DoBodyAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+        public void visit(Node.InvokeAction n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.TagDirective n) throws JasperException {
+	    appendTagDirective(n);
+	}
+
+	public void visit(Node.AttributeDirective n) throws JasperException {
+	    appendTag(n);
+	}
+
+	public void visit(Node.VariableDirective n) throws JasperException {
+	    appendTag(n);
+	}
+        
+	public void visit(Node.TemplateText n) throws JasperException {
+	    /*
+	     * If the template text came from a JSP page written in JSP syntax,
+	     * create a jsp:text element for it (JSP 5.3.2).
+	     */
+	    appendText(n.getText(), !n.getRoot().isXmlSyntax());
+	}
+
+	/*
+	 * Appends the given tag, including its body, to the XML view.
+	 */
+	private void appendTag(Node n) throws JasperException {
+	    appendTag(n, false);
+	}
+
+	/*
+	 * Appends the given tag, including its body, to the XML view,
+	 * and optionally reset default namespace to "", if none specified.
+	 */
+	private void appendTag(Node n, boolean addDefaultNS)
+		throws JasperException {
+
+	    Node.Nodes body = n.getBody();
+	    String text = n.getText();
+
+	    buf.append("<").append(n.getQName());
+	    buf.append("\n");
+
+	    printAttributes(n, addDefaultNS);
+	    buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+	    buf.append(jspId++).append("\"\n");
+
+	    if (ROOT_ACTION.equals(n.getLocalName()) || body != null
+		        || text != null) {
+		buf.append(">\n");
+		if (ROOT_ACTION.equals(n.getLocalName())) {
+		    if (compiler.getCompilationContext().isTagFile()) {
+			appendTagDirective();
+		    } else {
+			appendPageDirective();
+		    }
+		}
+		if (body != null) {
+		    body.visit(this);
+		} else {
+		    appendText(text, false);
+		}
+		buf.append("</" + n.getQName() + ">\n");
+	    } else {
+		buf.append("/>\n");
+	    }
+	}
+
+	/*
+	 * Appends the page directive with the given attributes to the XML
+	 * view.
+	 *
+	 * Since the import attribute of the page directive is the only page
+	 * attribute that is allowed to appear multiple times within the same
+	 * document, and since XML allows only single-value attributes,
+	 * the values of multiple import attributes must be combined into one,
+	 * separated by comma.
+	 *
+	 * If the given page directive contains just 'contentType' and/or
+	 * 'pageEncoding' attributes, we ignore it, as we've already appended
+	 * a page directive containing just these two attributes.
+	 */
+	private void appendPageDirective(Node.PageDirective n) {
+	    boolean append = false;
+	    Attributes attrs = n.getAttributes();
+	    int len = (attrs == null) ? 0 : attrs.getLength();
+	    for (int i=0; i<len; i++) {
+		String attrName = attrs.getQName(i);
+		if (!"pageEncoding".equals(attrName)
+		        && !"contentType".equals(attrName)) {
+		    append = true;
+		    break;
+		}
+	    }
+	    if (!append) {
+		return;
+	    }
+
+	    buf.append("<").append(n.getQName());
+	    buf.append("\n");
+
+	    // append jsp:id
+	    buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+	    buf.append(jspId++).append("\"\n");
+
+	    // append remaining attributes
+	    for (int i=0; i<len; i++) {
+		String attrName = attrs.getQName(i);
+		if ("import".equals(attrName) || "contentType".equals(attrName)
+		        || "pageEncoding".equals(attrName)) {
+		    /*
+		     * Page directive's 'import' attribute is considered
+		     * further down, and its 'pageEncoding' and 'contentType'
+		     * attributes are ignored, since we've already appended
+		     * a new page directive containing just these two
+		     * attributes
+		     */
+		    continue;
+		}
+		String value = attrs.getValue(i);
+		buf.append("  ").append(attrName).append("=\"");
+		buf.append(JspUtil.getExprInXml(value)).append("\"\n");
+	    }
+	    if (n.getImports().size() > 0) {
+		// Concatenate names of imported classes/packages
+		boolean first = true;
+		ListIterator iter = n.getImports().listIterator();
+		while (iter.hasNext()) {
+		    if (first) {
+			first = false;
+			buf.append("  import=\"");
+		    } else {
+			buf.append(",");
+		    }
+		    buf.append(JspUtil.getExprInXml((String) iter.next()));
+		}
+		buf.append("\"\n");
+	    }
+	    buf.append("/>\n");
+	}
+
+	/*
+	 * Appends a page directive with 'pageEncoding' and 'contentType'
+	 * attributes.
+	 *
+	 * The value of the 'pageEncoding' attribute is hard-coded
+	 * to UTF-8, whereas the value of the 'contentType' attribute, which
+	 * is identical to what the container will pass to
+	 * ServletResponse.setContentType(), is derived from the pageInfo.
+	 */
+	private void appendPageDirective() {
+	    buf.append("<").append(JSP_PAGE_DIRECTIVE_ACTION);
+	    buf.append("\n");
+
+	    // append jsp:id
+	    buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+	    buf.append(jspId++).append("\"\n");
+	    buf.append("  ").append("pageEncoding").append("=\"UTF-8\"\n");
+	    buf.append("  ").append("contentType").append("=\"");
+	    buf.append(compiler.getPageInfo().getContentType()).append("\"\n");
+	    buf.append("/>\n");	    
+	}
+
+	/*
+	 * Appends the tag directive with the given attributes to the XML
+	 * view.
+	 *
+	 * If the given tag directive contains just a 'pageEncoding'
+	 * attributes, we ignore it, as we've already appended
+	 * a tag directive containing just this attributes.
+	 */
+	private void appendTagDirective(Node.TagDirective n)
+	        throws JasperException {
+
+	    boolean append = false;
+	    Attributes attrs = n.getAttributes();
+	    int len = (attrs == null) ? 0 : attrs.getLength();
+	    for (int i=0; i<len; i++) {
+		String attrName = attrs.getQName(i);
+		if (!"pageEncoding".equals(attrName)) {
+		    append = true;
+		    break;
+		}
+	    }
+	    if (!append) {
+		return;
+	    }
+
+	    appendTag(n);
+	}
+
+	/*
+	 * Appends a tag directive containing a single 'pageEncoding'
+	 * attribute whose value is hard-coded to UTF-8.
+	 */
+	private void appendTagDirective() {
+	    buf.append("<").append(JSP_TAG_DIRECTIVE_ACTION);
+	    buf.append("\n");
+
+	    // append jsp:id
+	    buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+	    buf.append(jspId++).append("\"\n");
+	    buf.append("  ").append("pageEncoding").append("=\"UTF-8\"\n");
+	    buf.append("/>\n");	    
+	}
+
+	private void appendText(String text, boolean createJspTextElement) {
+	    if (createJspTextElement) {
+		buf.append("<").append(JSP_TEXT_ACTION);
+		buf.append("\n");
+
+		// append jsp:id
+		buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+		buf.append(jspId++).append("\"\n");
+		buf.append(">\n");
+
+		appendCDATA(text);
+		buf.append(JSP_TEXT_ACTION_END);
+		buf.append("\n");
+	    } else {
+		appendCDATA(text);
+	    }
+	}
+	
+	/*
+	 * Appends the given text as a CDATA section to the XML view, unless
+	 * the text has already been marked as CDATA.
+	 */
+	private void appendCDATA(String text) {
+	    buf.append(CDATA_START_SECTION);
+	    buf.append(escapeCDATA(text));
+	    buf.append(CDATA_END_SECTION);
+	}
+
+	/*
+	 * Escapes any occurrences of "]]>" (by replacing them with "]]&gt;")
+	 * within the given text, so it can be included in a CDATA section.
+	 */
+	private String escapeCDATA(String text) {
+            if( text==null ) return "";
+	    int len = text.length();
+	    CharArrayWriter result = new CharArrayWriter(len);
+	    for (int i=0; i<len; i++) {
+		if (((i+2) < len)
+		        && (text.charAt(i) == ']')
+		        && (text.charAt(i+1) == ']')
+		        && (text.charAt(i+2) == '>')) {
+		    // match found
+		    result.write(']');
+		    result.write(']');
+		    result.write('&');
+		    result.write('g');
+		    result.write('t');
+		    result.write(';');
+		    i += 2;
+		} else {
+		    result.write(text.charAt(i));
+		}
+	    }
+	    return result.toString();
+	}
+
+	/*
+	 * Appends the attributes of the given Node to the XML view.
+	 */
+	private void printAttributes(Node n, boolean addDefaultNS) {
+
+	    /*
+	     * Append "xmlns" attributes that represent tag libraries
+	     */
+	    Attributes attrs = n.getTaglibAttributes();
+	    int len = (attrs == null) ? 0 : attrs.getLength();
+	    for (int i=0; i<len; i++) {
+		String name = attrs.getQName(i);
+		String value = attrs.getValue(i);
+		buf.append("  ").append(name).append("=\"").append(value).append("\"\n");
+	    }
+
+	    /*
+	     * Append "xmlns" attributes that do not represent tag libraries
+	     */
+	    attrs = n.getNonTaglibXmlnsAttributes();
+	    len = (attrs == null) ? 0 : attrs.getLength();
+	    boolean defaultNSSeen = false;
+	    for (int i=0; i<len; i++) {
+		String name = attrs.getQName(i);
+		String value = attrs.getValue(i);
+		buf.append("  ").append(name).append("=\"").append(value).append("\"\n");
+		defaultNSSeen |= "xmlns".equals(name);
+	    }
+	    if (addDefaultNS && !defaultNSSeen) {
+		buf.append("  xmlns=\"\"\n");
+	    }
+	    resetDefaultNS = false;
+
+	    /*
+	     * Append all other attributes
+	     */
+	    attrs = n.getAttributes();
+	    len = (attrs == null) ? 0 : attrs.getLength();
+	    for (int i=0; i<len; i++) {
+		String name = attrs.getQName(i);
+		String value = attrs.getValue(i);
+		buf.append("  ").append(name).append("=\"");
+		buf.append(JspUtil.getExprInXml(value)).append("\"\n");
+	    }
+	}
+
+	/*
+	 * Appends XML prolog with encoding declaration.
+	 */
+	private void appendXmlProlog() {
+	    buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
+	}
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java
new file mode 100644
index 0000000..5a016fc
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java
@@ -0,0 +1,711 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.el.ExpressionFactoryImpl;
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+
+import javax.el.ExpressionFactory;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+/**
+ * A repository for various info about the translation unit under compilation.
+ *
+ * @author Kin-man Chung
+ */
+
+class PageInfo {
+
+    private Vector imports;
+    private Vector dependants;
+
+    private BeanRepository beanRepository;
+    private HashMap taglibsMap;
+    private HashMap jspPrefixMapper;
+    private HashMap xmlPrefixMapper;
+    private HashMap nonCustomTagPrefixMap;
+    private String jspFile;
+    private String defaultLanguage = "java";
+    private String language;
+    private String defaultExtends = Constants.JSP_SERVLET_BASE;
+    private String xtends;
+    private String contentType = null;
+    private String session;
+    private boolean isSession = true;
+    private String bufferValue;
+    private int buffer = 8*1024;    // XXX confirm
+    private String autoFlush;
+    private boolean isAutoFlush = true;
+    private String isThreadSafeValue;
+    private boolean isThreadSafe = true;
+    private String isErrorPageValue;
+    private boolean isErrorPage = false;
+    private String errorPage = null;
+    private String info;
+
+    private boolean scriptless = false;
+    private boolean scriptingInvalid = false;
+    
+    private String isELIgnoredValue;
+    private boolean isELIgnored = false;
+    
+    // JSP 2.1
+    private String deferredSyntaxAllowedAsLiteralValue;
+    private boolean deferredSyntaxAllowedAsLiteral = false;
+    private ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
+    private String trimDirectiveWhitespacesValue;
+    private boolean trimDirectiveWhitespaces = false;
+    
+    private String omitXmlDecl = null;
+    private String doctypeName = null;
+    private String doctypePublic = null;
+    private String doctypeSystem = null;
+
+    private boolean isJspPrefixHijacked;
+
+    // Set of all element and attribute prefixes used in this translation unit
+    private HashSet prefixes;
+
+    private boolean hasJspRoot = false;
+    private Vector includePrelude;
+    private Vector includeCoda;
+    private Vector pluginDcls;      // Id's for tagplugin declarations
+
+
+    PageInfo(BeanRepository beanRepository, String jspFile) {
+
+        this.jspFile = jspFile;
+        this.beanRepository = beanRepository;
+        this.taglibsMap = new HashMap();
+        this.jspPrefixMapper = new HashMap();
+        this.xmlPrefixMapper = new HashMap();
+        this.nonCustomTagPrefixMap = new HashMap();
+        this.imports = new Vector();
+        this.dependants = new Vector();
+        this.includePrelude = new Vector();
+        this.includeCoda = new Vector();
+        this.pluginDcls = new Vector();
+        this.prefixes = new HashSet();
+
+        // Enter standard imports
+        for(int i = 0; i < Constants.STANDARD_IMPORTS.length; i++)
+            imports.add(Constants.STANDARD_IMPORTS[i]);
+    }
+
+    /**
+     * Check if the plugin ID has been previously declared.  Make a not
+     * that this Id is now declared.
+     * @return true if Id has been declared.
+     */
+    public boolean isPluginDeclared(String id) {
+        if (pluginDcls.contains(id))
+            return true;
+        pluginDcls.add(id);
+        return false;
+    }
+
+    public void addImports(List imports) {
+        this.imports.addAll(imports);
+    }
+
+    public void addImport(String imp) {
+        this.imports.add(imp);
+    }
+
+    public List getImports() {
+        return imports;
+    }
+
+    public String getJspFile() {
+        return jspFile;
+    }
+
+    public void addDependant(String d) {
+        if (!dependants.contains(d) && !jspFile.equals(d))
+                dependants.add(d);
+    }
+
+    public List getDependants() {
+        return dependants;
+    }
+
+    public BeanRepository getBeanRepository() {
+        return beanRepository;
+    }
+
+    public void setScriptless(boolean s) {
+        scriptless = s;
+    }
+
+    public boolean isScriptless() {
+        return scriptless;
+    }
+
+    public void setScriptingInvalid(boolean s) {
+        scriptingInvalid = s;
+    }
+
+    public boolean isScriptingInvalid() {
+        return scriptingInvalid;
+    }
+
+    public List getIncludePrelude() {
+        return includePrelude;
+    }
+
+    public void setIncludePrelude(Vector prelude) {
+        includePrelude = prelude;
+    }
+
+    public List getIncludeCoda() {
+        return includeCoda;
+    }
+
+    public void setIncludeCoda(Vector coda) {
+        includeCoda = coda;
+    }
+
+    public void setHasJspRoot(boolean s) {
+        hasJspRoot = s;
+    }
+
+    public boolean hasJspRoot() {
+        return hasJspRoot;
+    }
+
+    public String getOmitXmlDecl() {
+        return omitXmlDecl;
+    }
+
+    public void setOmitXmlDecl(String omit) {
+        omitXmlDecl = omit;
+    }
+
+    public String getDoctypeName() {
+        return doctypeName;
+    }
+
+    public void setDoctypeName(String doctypeName) {
+        this.doctypeName = doctypeName;
+    }
+
+    public String getDoctypeSystem() {
+        return doctypeSystem;
+    }
+
+    public void setDoctypeSystem(String doctypeSystem) {
+        this.doctypeSystem = doctypeSystem;
+    }
+
+    public String getDoctypePublic() {
+        return doctypePublic;
+    }
+
+    public void setDoctypePublic(String doctypePublic) {
+        this.doctypePublic = doctypePublic;
+    }
+
+    /* Tag library and XML namespace management methods */
+
+    public void setIsJspPrefixHijacked(boolean isHijacked) {
+        isJspPrefixHijacked = isHijacked;
+    }
+
+    public boolean isJspPrefixHijacked() {
+        return isJspPrefixHijacked;
+    }
+
+    /*
+     * Adds the given prefix to the set of prefixes of this translation unit.
+     *
+     * @param prefix The prefix to add
+     */
+    public void addPrefix(String prefix) {
+        prefixes.add(prefix);
+    }
+
+    /*
+     * Checks to see if this translation unit contains the given prefix.
+     *
+     * @param prefix The prefix to check
+     *
+     * @return true if this translation unit contains the given prefix, false
+     * otherwise
+     */
+    public boolean containsPrefix(String prefix) {
+        return prefixes.contains(prefix);
+    }
+
+    /*
+     * Maps the given URI to the given tag library.
+     *
+     * @param uri The URI to map
+     * @param info The tag library to be associated with the given URI
+     */
+    public void addTaglib(String uri, TagLibraryInfo info) {
+        taglibsMap.put(uri, info);
+    }
+
+    /*
+     * Gets the tag library corresponding to the given URI.
+     *
+     * @return Tag library corresponding to the given URI
+     */
+    public TagLibraryInfo getTaglib(String uri) {
+        return (TagLibraryInfo) taglibsMap.get(uri);
+    }
+
+    /*
+     * Gets the collection of tag libraries that are associated with a URI
+     *
+     * @return Collection of tag libraries that are associated with a URI
+     */
+    public Collection getTaglibs() {
+        return taglibsMap.values();
+    }
+
+    /*
+     * Checks to see if the given URI is mapped to a tag library.
+     *
+     * @param uri The URI to map
+     *
+     * @return true if the given URI is mapped to a tag library, false
+     * otherwise
+     */
+    public boolean hasTaglib(String uri) {
+        return taglibsMap.containsKey(uri);
+    }
+
+    /*
+     * Maps the given prefix to the given URI.
+     *
+     * @param prefix The prefix to map
+     * @param uri The URI to be associated with the given prefix
+     */
+    public void addPrefixMapping(String prefix, String uri) {
+        jspPrefixMapper.put(prefix, uri);
+    }
+
+    /*
+     * Pushes the given URI onto the stack of URIs to which the given prefix
+     * is mapped.
+     *
+     * @param prefix The prefix whose stack of URIs is to be pushed
+     * @param uri The URI to be pushed onto the stack
+     */
+    public void pushPrefixMapping(String prefix, String uri) {
+        LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix);
+        if (stack == null) {
+            stack = new LinkedList();
+            xmlPrefixMapper.put(prefix, stack);
+        }
+        stack.addFirst(uri);
+    }
+
+    /*
+     * Removes the URI at the top of the stack of URIs to which the given
+     * prefix is mapped.
+     *
+     * @param prefix The prefix whose stack of URIs is to be popped
+     */
+    public void popPrefixMapping(String prefix) {
+        LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix);
+        if (stack == null || stack.size() == 0) {
+            // XXX throw new Exception("XXX");
+        }
+        stack.removeFirst();
+    }
+
+    /*
+     * Returns the URI to which the given prefix maps.
+     *
+     * @param prefix The prefix whose URI is sought
+     *
+     * @return The URI to which the given prefix maps
+     */
+    public String getURI(String prefix) {
+
+        String uri = null;
+
+        LinkedList stack = (LinkedList) xmlPrefixMapper.get(prefix);
+        if (stack == null || stack.size() == 0) {
+            uri = (String) jspPrefixMapper.get(prefix);
+        } else {
+            uri = (String) stack.getFirst();
+        }
+
+        return uri;
+    }
+
+
+    /* Page/Tag directive attributes */
+
+    /*
+     * language
+     */
+    public void setLanguage(String value, Node n, ErrorDispatcher err,
+                boolean pagedir)
+        throws JasperException {
+
+        if (!"java".equalsIgnoreCase(value)) {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.language.nonjava");
+            else
+                err.jspError(n, "jsp.error.tag.language.nonjava");
+        }
+
+        language = value;
+    }
+
+    public String getLanguage(boolean useDefault) {
+        return (language == null && useDefault ? defaultLanguage : language);
+    }
+
+    public String getLanguage() {
+        return getLanguage(true);
+    }
+
+
+    /*
+     * extends
+     */
+    public void setExtends(String value, Node.PageDirective n) {
+
+        xtends = value;
+
+        /*
+         * If page superclass is top level class (i.e. not in a package)
+         * explicitly import it. If this is not done, the compiler will assume
+         * the extended class is in the same pkg as the generated servlet.
+         */
+        if (value.indexOf('.') < 0)
+            n.addImport(value);
+    }
+
+    /**
+     * Gets the value of the 'extends' page directive attribute.
+     *
+     * @param useDefault TRUE if the default
+     * (org.apache.jasper.runtime.HttpJspBase) should be returned if this
+     * attribute has not been set, FALSE otherwise
+     *
+     * @return The value of the 'extends' page directive attribute, or the
+     * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has
+     * not been set and useDefault is TRUE
+     */
+    public String getExtends(boolean useDefault) {
+        return (xtends == null && useDefault ? defaultExtends : xtends);
+    }
+
+    /**
+     * Gets the value of the 'extends' page directive attribute.
+     *
+     * @return The value of the 'extends' page directive attribute, or the
+     * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has
+     * not been set
+     */
+    public String getExtends() {
+        return getExtends(true);
+    }
+
+
+    /*
+     * contentType
+     */
+    public void setContentType(String value) {
+        contentType = value;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+
+    /*
+     * buffer
+     */
+    public void setBufferValue(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("none".equalsIgnoreCase(value))
+            buffer = 0;
+        else {
+            if (value == null || !value.endsWith("kb"))
+                err.jspError(n, "jsp.error.page.invalid.buffer");
+            try {
+                Integer k = new Integer(value.substring(0, value.length()-2));
+                buffer = k.intValue() * 1024;
+            } catch (NumberFormatException e) {
+                err.jspError(n, "jsp.error.page.invalid.buffer");
+            }
+        }
+
+        bufferValue = value;
+    }
+
+    public String getBufferValue() {
+        return bufferValue;
+    }
+
+    public int getBuffer() {
+        return buffer;
+    }
+
+
+    /*
+     * session
+     */
+    public void setSession(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isSession = true;
+        else if ("false".equalsIgnoreCase(value))
+            isSession = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.session");
+
+        session = value;
+    }
+
+    public String getSession() {
+        return session;
+    }
+
+    public boolean isSession() {
+        return isSession;
+    }
+
+
+    /*
+     * autoFlush
+     */
+    public void setAutoFlush(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isAutoFlush = true;
+        else if ("false".equalsIgnoreCase(value))
+            isAutoFlush = false;
+        else
+            err.jspError(n, "jsp.error.autoFlush.invalid");
+
+        autoFlush = value;
+    }
+
+    public String getAutoFlush() {
+        return autoFlush;
+    }
+
+    public boolean isAutoFlush() {
+        return isAutoFlush;
+    }
+
+
+    /*
+     * isThreadSafe
+     */
+    public void setIsThreadSafe(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isThreadSafe = true;
+        else if ("false".equalsIgnoreCase(value))
+            isThreadSafe = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.isthreadsafe");
+
+        isThreadSafeValue = value;
+    }
+
+    public String getIsThreadSafe() {
+        return isThreadSafeValue;
+    }
+
+    public boolean isThreadSafe() {
+        return isThreadSafe;
+    }
+
+
+    /*
+     * info
+     */
+    public void setInfo(String value) {
+        info = value;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+
+
+    /*
+     * errorPage
+     */
+    public void setErrorPage(String value) {
+        errorPage = value;
+    }
+
+    public String getErrorPage() {
+        return errorPage;
+    }
+
+
+    /*
+     * isErrorPage
+     */
+    public void setIsErrorPage(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isErrorPage = true;
+        else if ("false".equalsIgnoreCase(value))
+            isErrorPage = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.iserrorpage");
+
+        isErrorPageValue = value;
+    }
+
+    public String getIsErrorPage() {
+        return isErrorPageValue;
+    }
+
+    public boolean isErrorPage() {
+        return isErrorPage;
+    }
+
+
+    /*
+     * isELIgnored
+     */
+    public void setIsELIgnored(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isELIgnored = true;
+        else if ("false".equalsIgnoreCase(value))
+            isELIgnored = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.iselignored");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.iselignored");
+        }
+
+        isELIgnoredValue = value;
+    }
+    
+    /*
+     * deferredSyntaxAllowedAsLiteral
+     */
+    public void setDeferredSyntaxAllowedAsLiteral(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            deferredSyntaxAllowedAsLiteral = true;
+        else if ("false".equalsIgnoreCase(value))
+            deferredSyntaxAllowedAsLiteral = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.deferredsyntaxallowedasliteral");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.deferredsyntaxallowedasliteral");
+        }
+
+        deferredSyntaxAllowedAsLiteralValue = value;
+    }
+    
+    /*
+     * trimDirectiveWhitespaces
+     */
+    public void setTrimDirectiveWhitespaces(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            trimDirectiveWhitespaces = true;
+        else if ("false".equalsIgnoreCase(value))
+            trimDirectiveWhitespaces = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.trimdirectivewhitespaces");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.trimdirectivewhitespaces");
+        }
+
+        trimDirectiveWhitespacesValue = value;
+    }
+
+    public void setELIgnored(boolean s) {
+        isELIgnored = s;
+    }
+
+    public String getIsELIgnored() {
+        return isELIgnoredValue;
+    }
+
+    public boolean isELIgnored() {
+        return isELIgnored;
+    }
+
+    public void putNonCustomTagPrefix(String prefix, Mark where) {
+        nonCustomTagPrefixMap.put(prefix, where);
+    }
+
+    public Mark getNonCustomTagPrefix(String prefix) {
+        return (Mark) nonCustomTagPrefixMap.get(prefix);
+    }
+    
+    public String getDeferredSyntaxAllowedAsLiteral() {
+        return deferredSyntaxAllowedAsLiteralValue;
+    }
+
+    public boolean isDeferredSyntaxAllowedAsLiteral() {
+        return deferredSyntaxAllowedAsLiteral;
+    }
+
+    public void setDeferredSyntaxAllowedAsLiteral(boolean isELDeferred) {
+        this.deferredSyntaxAllowedAsLiteral = isELDeferred;
+    }
+
+    public ExpressionFactory getExpressionFactory() {
+        return expressionFactory;
+    }
+
+    public String getTrimDirectiveWhitespaces() {
+        return trimDirectiveWhitespacesValue;
+    }
+
+    public boolean isTrimDirectiveWhitespaces() {
+        return trimDirectiveWhitespaces;
+    }
+
+    public void setTrimDirectiveWhitespaces(boolean trimDirectiveWhitespaces) {
+        this.trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java
new file mode 100644
index 0000000..f302e99
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Parser.java
@@ -0,0 +1,1791 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * This class implements a parser for a JSP page (non-xml view). JSP page
+ * grammar is included here for reference. The token '#' that appears in the
+ * production indicates the current input token location in the production.
+ * 
+ * @author Kin-man Chung
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+class Parser implements TagConstants {
+
+    private ParserController parserController;
+
+    private JspCompilationContext ctxt;
+
+    private JspReader reader;
+
+    private String currentFile;
+
+    private Mark start;
+
+    private ErrorDispatcher err;
+
+    private int scriptlessCount;
+
+    private boolean isTagFile;
+
+    private boolean directivesOnly;
+
+    private URL jarFileUrl;
+
+    private PageInfo pageInfo;
+
+    // Virtual body content types, to make parsing a little easier.
+    // These are not accessible from outside the parser.
+    private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM";
+
+    private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN";
+
+    private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
+
+    private static final boolean STRICT_QUOTE_ESCAPING = Boolean.valueOf(
+            System.getProperty(
+                    "org.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING",
+                    "true")).booleanValue();
+
+    /**
+     * The constructor
+     */
+    private Parser(ParserController pc, JspReader reader, boolean isTagFile,
+            boolean directivesOnly, URL jarFileUrl) {
+        this.parserController = pc;
+        this.ctxt = pc.getJspCompilationContext();
+        this.pageInfo = pc.getCompiler().getPageInfo();
+        this.err = pc.getCompiler().getErrorDispatcher();
+        this.reader = reader;
+        this.currentFile = reader.mark().getFile();
+        this.scriptlessCount = 0;
+        this.isTagFile = isTagFile;
+        this.directivesOnly = directivesOnly;
+        this.jarFileUrl = jarFileUrl;
+        start = reader.mark();
+    }
+
+    /**
+     * The main entry for Parser
+     * 
+     * @param pc
+     *            The ParseController, use for getting other objects in compiler
+     *            and for parsing included pages
+     * @param reader
+     *            To read the page
+     * @param parent
+     *            The parent node to this page, null for top level page
+     * @return list of nodes representing the parsed page
+     */
+    public static Node.Nodes parse(ParserController pc, JspReader reader,
+            Node parent, boolean isTagFile, boolean directivesOnly,
+            URL jarFileUrl, String pageEnc, String jspConfigPageEnc,
+            boolean isDefaultPageEncoding, boolean isBomPresent) throws JasperException {
+
+        Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
+                jarFileUrl);
+
+        Node.Root root = new Node.Root(reader.mark(), parent, false);
+        root.setPageEncoding(pageEnc);
+        root.setJspConfigPageEncoding(jspConfigPageEnc);
+        root.setIsDefaultPageEncoding(isDefaultPageEncoding);
+        root.setIsBomPresent(isBomPresent);
+
+        if (directivesOnly) {
+            parser.parseTagFileDirectives(root);
+            return new Node.Nodes(root);
+        }
+
+        // For the Top level page, add inlcude-prelude and include-coda
+        PageInfo pageInfo = pc.getCompiler().getPageInfo();
+        if (parent == null) {
+            parser.addInclude(root, pageInfo.getIncludePrelude());
+        }
+        while (reader.hasMoreInput()) {
+            parser.parseElements(root);
+        }
+        if (parent == null) {
+            parser.addInclude(root, pageInfo.getIncludeCoda());
+        }
+
+        Node.Nodes page = new Node.Nodes(root);
+        return page;
+    }
+
+    /**
+     * Attributes ::= (S Attribute)* S?
+     */
+    Attributes parseAttributes() throws JasperException {
+        AttributesImpl attrs = new AttributesImpl();
+
+        reader.skipSpaces();
+        while (parseAttribute(attrs))
+            reader.skipSpaces();
+
+        return attrs;
+    }
+
+    /**
+     * Parse Attributes for a reader, provided for external use
+     */
+    public static Attributes parseAttributes(ParserController pc,
+            JspReader reader) throws JasperException {
+        Parser tmpParser = new Parser(pc, reader, false, false, null);
+        return tmpParser.parseAttributes();
+    }
+
+    /**
+     * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"'
+     * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'"
+     * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces
+     * around Eq. It is added to be backward compatible with Tomcat, and with
+     * other xml parsers.
+     */
+    private boolean parseAttribute(AttributesImpl attrs) throws JasperException {
+
+        // Get the qualified name
+        String qName = parseName();
+        if (qName == null)
+            return false;
+
+        // Determine prefix and local name components
+        String localName = qName;
+        String uri = "";
+        int index = qName.indexOf(':');
+        if (index != -1) {
+            String prefix = qName.substring(0, index);
+            uri = pageInfo.getURI(prefix);
+            if (uri == null) {
+                err.jspError(reader.mark(),
+                        "jsp.error.attribute.invalidPrefix", prefix);
+            }
+            localName = qName.substring(index + 1);
+        }
+
+        reader.skipSpaces();
+        if (!reader.matches("="))
+            err.jspError(reader.mark(), "jsp.error.attribute.noequal");
+
+        reader.skipSpaces();
+        char quote = (char) reader.nextChar();
+        if (quote != '\'' && quote != '"')
+            err.jspError(reader.mark(), "jsp.error.attribute.noquote");
+
+        String watchString = "";
+        if (reader.matches("<%="))
+            watchString = "%>";
+        watchString = watchString + quote;
+
+        String attrValue = parseAttributeValue(watchString);
+        attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
+        return true;
+    }
+
+    /**
+     * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
+     */
+    private String parseName() throws JasperException {
+        char ch = (char) reader.peekChar();
+        if (Character.isLetter(ch) || ch == '_' || ch == ':') {
+            StringBuffer buf = new StringBuffer();
+            buf.append(ch);
+            reader.nextChar();
+            ch = (char) reader.peekChar();
+            while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.'
+                    || ch == '_' || ch == '-' || ch == ':') {
+                buf.append(ch);
+                reader.nextChar();
+                ch = (char) reader.peekChar();
+            }
+            return buf.toString();
+        }
+        return null;
+    }
+
+    /**
+     * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>)
+     * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
+     * ('%>"' | TRANSLATION_ERROR)
+     */
+    private String parseAttributeValue(String watch) throws JasperException {
+        Mark start = reader.mark();
+        Mark stop = reader.skipUntilIgnoreEsc(watch);
+        if (stop == null) {
+            err.jspError(start, "jsp.error.attribute.unterminated", watch);
+        }
+
+        String ret = parseQuoted(start, reader.getText(start, stop),
+                watch.charAt(watch.length() - 1));
+        if (watch.length() == 1) // quote
+            return ret;
+
+        // putback delimiter '<%=' and '%>', since they are needed if the
+        // attribute does not allow RTexpression.
+        return "<%=" + ret + "%>";
+    }
+
+    /**
+     * QuotedChar ::= '&apos;' | '&quot;' | '\\' | '\"' | "\'" | '\>' | '\$' |
+     * Char
+     */
+    private String parseQuoted(Mark start, String tx, char quote)
+            throws JasperException {
+        StringBuffer buf = new StringBuffer();
+        int size = tx.length();
+        int i = 0;
+        while (i < size) {
+            char ch = tx.charAt(i);
+            if (ch == '&') {
+                if (i + 5 < size && tx.charAt(i + 1) == 'a'
+                        && tx.charAt(i + 2) == 'p' && tx.charAt(i + 3) == 'o'
+                        && tx.charAt(i + 4) == 's' && tx.charAt(i + 5) == ';') {
+                    buf.append('\'');
+                    i += 6;
+                } else if (i + 5 < size && tx.charAt(i + 1) == 'q'
+                        && tx.charAt(i + 2) == 'u' && tx.charAt(i + 3) == 'o'
+                        && tx.charAt(i + 4) == 't' && tx.charAt(i + 5) == ';') {
+                    buf.append('"');
+                    i += 6;
+                } else {
+                    buf.append(ch);
+                    ++i;
+                }
+            } else if (ch == '\\' && i + 1 < size) {
+                ch = tx.charAt(i + 1);
+                if (ch == '\\' || ch == '\"' || ch == '\'' || ch == '>') {
+                    // \ " and ' are always unescaped regardless of if they are
+                    // inside or outside of an EL expression. JSP.1.6 takes
+                    // precedence over JSP.1.3.10 (confirmed with EG).
+                    buf.append(ch);
+                    i += 2;
+                } else {
+                    buf.append('\\');
+                    ++i;
+                }
+            } else if (ch == quote && STRICT_QUOTE_ESCAPING) {
+                // Unescaped quote character
+                err.jspError(start, "jsp.error.attribute.noescape", tx,
+                        "" + quote);
+            } else {
+                buf.append(ch);
+                ++i;
+            }
+        }
+        return buf.toString();
+    }
+
+    private String parseScriptText(String tx) {
+        CharArrayWriter cw = new CharArrayWriter();
+        int size = tx.length();
+        int i = 0;
+        while (i < size) {
+            char ch = tx.charAt(i);
+            if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
+                    && tx.charAt(i + 2) == '>') {
+                cw.write('%');
+                cw.write('>');
+                i += 3;
+            } else {
+                cw.write(ch);
+                ++i;
+            }
+        }
+        cw.close();
+        return cw.toString();
+    }
+
+    /*
+     * Invokes parserController to parse the included page
+     */
+    private void processIncludeDirective(String file, Node parent)
+            throws JasperException {
+        if (file == null) {
+            return;
+        }
+
+        try {
+            parserController.parse(file, parent, jarFileUrl);
+        } catch (FileNotFoundException ex) {
+            err.jspError(start, "jsp.error.file.not.found", file);
+        } catch (Exception ex) {
+            err.jspError(start, ex.getMessage());
+        }
+    }
+
+    /*
+     * Parses a page directive with the following syntax: PageDirective ::= ( S
+     * Attribute)*
+     */
+    private void parsePageDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        Node.PageDirective n = new Node.PageDirective(attrs, start, parent);
+
+        /*
+         * A page directive may contain multiple 'import' attributes, each of
+         * which consists of a comma-separated list of package names. Store each
+         * list with the node, where it is parsed.
+         */
+        for (int i = 0; i < attrs.getLength(); i++) {
+            if ("import".equals(attrs.getQName(i))) {
+                n.addImport(attrs.getValue(i));
+            }
+        }
+    }
+
+    /*
+     * Parses an include directive with the following syntax: IncludeDirective
+     * ::= ( S Attribute)*
+     */
+    private void parseIncludeDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+
+        // Included file expanded here
+        Node includeNode = new Node.IncludeDirective(attrs, start, parent);
+        processIncludeDirective(attrs.getValue("file"), includeNode);
+    }
+
+    /**
+     * Add a list of files. This is used for implementing include-prelude and
+     * include-coda of jsp-config element in web.xml
+     */
+    private void addInclude(Node parent, List files) throws JasperException {
+        if (files != null) {
+            Iterator iter = files.iterator();
+            while (iter.hasNext()) {
+                String file = (String) iter.next();
+                AttributesImpl attrs = new AttributesImpl();
+                attrs.addAttribute("", "file", "file", "CDATA", file);
+
+                // Create a dummy Include directive node
+                Node includeNode = new Node.IncludeDirective(attrs, reader
+                        .mark(), parent);
+                processIncludeDirective(file, includeNode);
+            }
+        }
+    }
+
+    /*
+     * Parses a taglib directive with the following syntax: Directive ::= ( S
+     * Attribute)*
+     */
+    private void parseTaglibDirective(Node parent) throws JasperException {
+
+        Attributes attrs = parseAttributes();
+        String uri = attrs.getValue("uri");
+        String prefix = attrs.getValue("prefix");
+        if (prefix != null) {
+            Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
+            if (prevMark != null) {
+                err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
+                        prefix, prevMark.getFile(), ""
+                                + prevMark.getLineNumber());
+            }
+            if (uri != null) {
+                String uriPrev = pageInfo.getURI(prefix);
+                if (uriPrev != null && !uriPrev.equals(uri)) {
+                    err.jspError(reader.mark(), "jsp.error.prefix.refined",
+                            prefix, uri, uriPrev);
+                }
+                if (pageInfo.getTaglib(uri) == null) {
+                    TagLibraryInfoImpl impl = null;
+                    if (ctxt.getOptions().isCaching()) {
+                        impl = (TagLibraryInfoImpl) ctxt.getOptions()
+                                .getCache().get(uri);
+                    }
+                    if (impl == null) {
+                        String[] location = ctxt.getTldLocation(uri);
+                        impl = new TagLibraryInfoImpl(ctxt, parserController, pageInfo,
+                                prefix, uri, location, err);
+                        if (ctxt.getOptions().isCaching()) {
+                            ctxt.getOptions().getCache().put(uri, impl);
+                        }
+                    } else {
+                        // Current compilation context needs location of cached
+                        // tag files
+                        for (TagFileInfo info : impl.getTagFiles()) {
+                            ctxt.setTagFileJarUrl(info.getPath(),
+                                    ctxt.getTagFileJarUrl());
+                        }
+                    }
+                    pageInfo.addTaglib(uri, impl);
+                }
+                pageInfo.addPrefixMapping(prefix, uri);
+            } else {
+                String tagdir = attrs.getValue("tagdir");
+                if (tagdir != null) {
+                    String urnTagdir = URN_JSPTAGDIR + tagdir;
+                    if (pageInfo.getTaglib(urnTagdir) == null) {
+                        pageInfo.addTaglib(urnTagdir,
+                                new ImplicitTagLibraryInfo(ctxt,
+                                        parserController, pageInfo, prefix, tagdir, err));
+                    }
+                    pageInfo.addPrefixMapping(prefix, urnTagdir);
+                }
+            }
+        }
+
+        new Node.TaglibDirective(attrs, start, parent);
+    }
+
+    /*
+     * Parses a directive with the following syntax: Directive ::= S? ( 'page'
+     * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S?
+     * '%>'
+     * 
+     * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective |
+     * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable
+     * VariableDirective S? '%>'
+     */
+    private void parseDirective(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        String directive = null;
+        if (reader.matches("page")) {
+            directive = "&lt;%@ page";
+            if (isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.istagfile",
+                        directive);
+            }
+            parsePageDirective(parent);
+        } else if (reader.matches("include")) {
+            directive = "&lt;%@ include";
+            parseIncludeDirective(parent);
+        } else if (reader.matches("taglib")) {
+            if (directivesOnly) {
+                // No need to get the tagLibInfo objects. This alos suppresses
+                // parsing of any tag files used in this tag file.
+                return;
+            }
+            directive = "&lt;%@ taglib";
+            parseTaglibDirective(parent);
+        } else if (reader.matches("tag")) {
+            directive = "&lt;%@ tag";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseTagDirective(parent);
+        } else if (reader.matches("attribute")) {
+            directive = "&lt;%@ attribute";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseAttributeDirective(parent);
+        } else if (reader.matches("variable")) {
+            directive = "&lt;%@ variable";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseVariableDirective(parent);
+        } else {
+            err.jspError(reader.mark(), "jsp.error.invalid.directive");
+        }
+
+        reader.skipSpaces();
+        if (!reader.matches("%>")) {
+            err.jspError(start, "jsp.error.unterminated", directive);
+        }
+    }
+
+    /*
+     * Parses a directive with the following syntax:
+     * 
+     * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | (
+     * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | <TRANSLATION_ERROR>
+     * 
+     * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | (
+     * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S?
+     * ETag ) ) ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLDirective(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        String eTag = null;
+        if (reader.matches("page")) {
+            eTag = "jsp:directive.page";
+            if (isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.istagfile",
+                        "&lt;" + eTag);
+            }
+            parsePageDirective(parent);
+        } else if (reader.matches("include")) {
+            eTag = "jsp:directive.include";
+            parseIncludeDirective(parent);
+        } else if (reader.matches("tag")) {
+            eTag = "jsp:directive.tag";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseTagDirective(parent);
+        } else if (reader.matches("attribute")) {
+            eTag = "jsp:directive.attribute";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseAttributeDirective(parent);
+        } else if (reader.matches("variable")) {
+            eTag = "jsp:directive.variable";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseVariableDirective(parent);
+        } else {
+            err.jspError(reader.mark(), "jsp.error.invalid.directive");
+        }
+
+        reader.skipSpaces();
+        if (reader.matches(">")) {
+            reader.skipSpaces();
+            if (!reader.matchesETag(eTag)) {
+                err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
+            }
+        } else if (!reader.matches("/>")) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
+        }
+    }
+
+    /*
+     * Parses a tag directive with the following syntax: PageDirective ::= ( S
+     * Attribute)*
+     */
+    private void parseTagDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        Node.TagDirective n = new Node.TagDirective(attrs, start, parent);
+
+        /*
+         * A page directive may contain multiple 'import' attributes, each of
+         * which consists of a comma-separated list of package names. Store each
+         * list with the node, where it is parsed.
+         */
+        for (int i = 0; i < attrs.getLength(); i++) {
+            if ("import".equals(attrs.getQName(i))) {
+                n.addImport(attrs.getValue(i));
+            }
+        }
+    }
+
+    /*
+     * Parses a attribute directive with the following syntax:
+     * AttributeDirective ::= ( S Attribute)*
+     */
+    private void parseAttributeDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        Node.AttributeDirective n = new Node.AttributeDirective(attrs, start,
+                parent);
+    }
+
+    /*
+     * Parses a variable directive with the following syntax: PageDirective ::= (
+     * S Attribute)*
+     */
+    private void parseVariableDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        Node.VariableDirective n = new Node.VariableDirective(attrs, start,
+                parent);
+    }
+
+    /*
+     * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
+     */
+    private void parseComment(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("--%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%--");
+        }
+
+        new Node.Comment(reader.getText(start, stop), start, parent);
+    }
+
+    /*
+     * DeclarationBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseDeclaration(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%!");
+        }
+
+        new Node.Declaration(parseScriptText(reader.getText(start, stop)),
+                start, parent);
+    }
+
+    /*
+     * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd
+     * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd
+     * ::= ']]>'
+     */
+    private void parseXMLDeclaration(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:declaration&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:declaration&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Declaration(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Declaration(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+
+            if (!reader.matchesETagWithoutLessThan("jsp:declaration")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:declaration&gt;");
+            }
+        }
+    }
+
+    /*
+     * ExpressionBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseExpression(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%=");
+        }
+
+        new Node.Expression(parseScriptText(reader.getText(start, stop)),
+                start, parent);
+    }
+
+    /*
+     * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLExpression(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:expression&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:expression&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Expression(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Expression(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+            if (!reader.matchesETagWithoutLessThan("jsp:expression")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:expression&gt;");
+            }
+        }
+    }
+
+    /*
+     * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal
+     * production and confirm implementation against it, // once it's decided
+     */
+    private void parseELExpression(Node parent, char type) throws JasperException {
+        start = reader.mark();
+        Mark last = null;
+        boolean singleQuoted = false, doubleQuoted = false;
+        int currentChar;
+        do {
+            // XXX could move this logic to JspReader
+            last = reader.mark(); // XXX somewhat wasteful
+            currentChar = reader.nextChar();
+            if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
+                // skip character following '\' within quotes
+                reader.nextChar();
+                currentChar = reader.nextChar();
+            }
+            if (currentChar == -1)
+                err.jspError(start, "jsp.error.unterminated", type + "{");
+            if (currentChar == '"' && !singleQuoted)
+                doubleQuoted = !doubleQuoted;
+            if (currentChar == '\'' && !doubleQuoted)
+                singleQuoted = !singleQuoted;
+        } while (currentChar != '}' || (singleQuoted || doubleQuoted));
+
+        new Node.ELExpression(type, reader.getText(start, last), start, parent);
+    }
+
+    /*
+     * ScriptletBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseScriptlet(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%");
+        }
+
+        new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start,
+                parent);
+    }
+
+    /*
+     * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLScriptlet(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:scriptlet&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:scriptlet&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Scriptlet(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Scriptlet(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+
+            if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:scriptlet&gt;");
+            }
+        }
+    }
+
+    /**
+     * Param ::= '<jsp:param' S Attributes S? EmptyBody S?
+     */
+    private void parseParam(Node parent) throws JasperException {
+        if (!reader.matches("<jsp:param")) {
+            err.jspError(reader.mark(), "jsp.error.paramexpected");
+        }
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node paramActionNode = new Node.ParamAction(attrs, start, parent);
+
+        parseEmptyBody(paramActionNode, "jsp:param");
+
+        reader.skipSpaces();
+    }
+
+    /*
+     * For Include: StdActionContent ::= Attributes ParamBody
+     * 
+     * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body'
+     * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag )
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     * JspBodyParam ::= S? '>' Param* '</jsp:body>'
+     */
+    private void parseInclude(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node includeNode = new Node.IncludeAction(attrs, start, parent);
+
+        parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    /*
+     * For Forward: StdActionContent ::= Attributes ParamBody
+     */
+    private void parseForward(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node forwardNode = new Node.ForwardAction(attrs, start, parent);
+
+        parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    private void parseInvoke(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node invokeNode = new Node.InvokeAction(attrs, start, parent);
+
+        parseEmptyBody(invokeNode, "jsp:invoke");
+    }
+
+    private void parseDoBody(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
+
+        parseEmptyBody(doBodyNode, "jsp:doBody");
+    }
+
+    private void parseElement(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node elementNode = new Node.JspElement(attrs, start, parent);
+
+        parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP);
+    }
+
+    /*
+     * For GetProperty: StdActionContent ::= Attributes EmptyBody
+     */
+    private void parseGetProperty(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node getPropertyNode = new Node.GetProperty(attrs, start, parent);
+
+        parseOptionalBody(getPropertyNode, "jsp:getProperty",
+                TagInfo.BODY_CONTENT_EMPTY);
+    }
+
+    /*
+     * For SetProperty: StdActionContent ::= Attributes EmptyBody
+     */
+    private void parseSetProperty(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node setPropertyNode = new Node.SetProperty(attrs, start, parent);
+
+        parseOptionalBody(setPropertyNode, "jsp:setProperty",
+                TagInfo.BODY_CONTENT_EMPTY);
+    }
+
+    /*
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     */
+    private void parseEmptyBody(Node parent, String tag) throws JasperException {
+        if (reader.matches("/>")) {
+            // Done
+        } else if (reader.matches(">")) {
+            if (reader.matchesETag(tag)) {
+                // Done
+            } else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
+                // Parse the one or more named attribute nodes
+                parseNamedAttributes(parent);
+                if (!reader.matchesETag(tag)) {
+                    // Body not allowed
+                    err.jspError(reader.mark(),
+                            "jsp.error.jspbody.emptybody.only", "&lt;" + tag);
+                }
+            } else {
+                err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
+                        "&lt;" + tag);
+            }
+        } else {
+            err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
+        }
+    }
+
+    /*
+     * For UseBean: StdActionContent ::= Attributes OptionalBody
+     */
+    private void parseUseBean(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node useBeanNode = new Node.UseBean(attrs, start, parent);
+
+        parseOptionalBody(useBeanNode, "jsp:useBean", TagInfo.BODY_CONTENT_JSP);
+    }
+
+    /*
+     * Parses OptionalBody, but also reused to parse bodies for plugin and param
+     * since the syntax is identical (the only thing that differs substantially
+     * is how to process the body, and thus we accept the body type as a
+     * parameter).
+     * 
+     * OptionalBody ::= EmptyBody | ActionBody
+     * 
+     * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
+     * 
+     * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag )
+     * 
+     * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag )
+     * 
+     * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody
+     * ETag )
+     * 
+     */
+    private void parseOptionalBody(Node parent, String tag, String bodyType)
+            throws JasperException {
+        if (reader.matches("/>")) {
+            // EmptyBody
+            return;
+        }
+
+        if (!reader.matches(">")) {
+            err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
+        }
+
+        if (reader.matchesETag(tag)) {
+            // EmptyBody
+            return;
+        }
+
+        if (!parseJspAttributeAndBody(parent, tag, bodyType)) {
+            // Must be ( '>' # Body ETag )
+            parseBody(parent, tag, bodyType);
+        }
+    }
+
+    /**
+     * Attempts to parse 'JspAttributeAndBody' production. Returns true if it
+     * matched, or false if not. Assumes EmptyBody is okay as well.
+     * 
+     * JspAttributeAndBody ::= ( '>' # S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
+     * JspBodyBody | <TRANSLATION_ERROR> ) S? ETag )
+     */
+    private boolean parseJspAttributeAndBody(Node parent, String tag,
+            String bodyType) throws JasperException {
+        boolean result = false;
+
+        if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
+            // May be an EmptyBody, depending on whether
+            // There's a "<jsp:body" before the ETag
+
+            // First, parse <jsp:attribute> elements:
+            parseNamedAttributes(parent);
+
+            result = true;
+        }
+
+        if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) {
+            // ActionBody
+            parseJspBody(parent, bodyType);
+            reader.skipSpaces();
+            if (!reader.matchesETag(tag)) {
+                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
+                        + tag);
+            }
+
+            result = true;
+        } else if (result && !reader.matchesETag(tag)) {
+            // If we have <jsp:attribute> but something other than
+            // <jsp:body> or the end tag, translation error.
+            err.jspError(reader.mark(), "jsp.error.jspbody.required", "&lt;"
+                    + tag);
+        }
+
+        return result;
+    }
+
+    /*
+     * Params ::= `>' S? ( ( `<jsp:body>' ( ( S? Param+ S? `</jsp:body>' ) |
+     * <TRANSLATION_ERROR> ) ) | Param+ ) '</jsp:params>'
+     */
+    private void parseJspParams(Node parent) throws JasperException {
+        Node jspParamsNode = new Node.ParamsAction(start, parent);
+        parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    /*
+     * Fallback ::= '/>' | ( `>' S? `<jsp:body>' ( ( S? ( Char* - ( Char* `</jsp:body>' ) ) `</jsp:body>'
+     * S? ) | <TRANSLATION_ERROR> ) `</jsp:fallback>' ) | ( '>' ( Char* - (
+     * Char* '</jsp:fallback>' ) ) '</jsp:fallback>' )
+     */
+    private void parseFallBack(Node parent) throws JasperException {
+        Node fallBackNode = new Node.FallBackAction(start, parent);
+        parseOptionalBody(fallBackNode, "jsp:fallback",
+                JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
+    }
+
+    /*
+     * For Plugin: StdActionContent ::= Attributes PluginBody
+     * 
+     * PluginBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
+     * JspBodyPluginTags | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? PluginTags
+     * ETag )
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     */
+    private void parsePlugin(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node pluginNode = new Node.PlugIn(attrs, start, parent);
+
+        parseOptionalBody(pluginNode, "jsp:plugin", JAVAX_BODY_CONTENT_PLUGIN);
+    }
+
+    /*
+     * PluginTags ::= ( '<jsp:params' Params S? )? ( '<jsp:fallback' Fallback?
+     * S? )?
+     */
+    private void parsePluginTags(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        if (reader.matches("<jsp:params")) {
+            parseJspParams(parent);
+            reader.skipSpaces();
+        }
+
+        if (reader.matches("<jsp:fallback")) {
+            parseFallBack(parent);
+            reader.skipSpaces();
+        }
+    }
+
+    /*
+     * StandardAction ::= 'include' StdActionContent | 'forward'
+     * StdActionContent | 'invoke' StdActionContent | 'doBody' StdActionContent |
+     * 'getProperty' StdActionContent | 'setProperty' StdActionContent |
+     * 'useBean' StdActionContent | 'plugin' StdActionContent | 'element'
+     * StdActionContent
+     */
+    private void parseStandardAction(Node parent) throws JasperException {
+        Mark start = reader.mark();
+
+        if (reader.matches(INCLUDE_ACTION)) {
+            parseInclude(parent);
+        } else if (reader.matches(FORWARD_ACTION)) {
+            parseForward(parent);
+        } else if (reader.matches(INVOKE_ACTION)) {
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
+                        "&lt;jsp:invoke");
+            }
+            parseInvoke(parent);
+        } else if (reader.matches(DOBODY_ACTION)) {
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
+                        "&lt;jsp:doBody");
+            }
+            parseDoBody(parent);
+        } else if (reader.matches(GET_PROPERTY_ACTION)) {
+            parseGetProperty(parent);
+        } else if (reader.matches(SET_PROPERTY_ACTION)) {
+            parseSetProperty(parent);
+        } else if (reader.matches(USE_BEAN_ACTION)) {
+            parseUseBean(parent);
+        } else if (reader.matches(PLUGIN_ACTION)) {
+            parsePlugin(parent);
+        } else if (reader.matches(ELEMENT_ACTION)) {
+            parseElement(parent);
+        } else if (reader.matches(ATTRIBUTE_ACTION)) {
+            err.jspError(start, "jsp.error.namedAttribute.invalidUse");
+        } else if (reader.matches(BODY_ACTION)) {
+            err.jspError(start, "jsp.error.jspbody.invalidUse");
+        } else if (reader.matches(FALLBACK_ACTION)) {
+            err.jspError(start, "jsp.error.fallback.invalidUse");
+        } else if (reader.matches(PARAMS_ACTION)) {
+            err.jspError(start, "jsp.error.params.invalidUse");
+        } else if (reader.matches(PARAM_ACTION)) {
+            err.jspError(start, "jsp.error.param.invalidUse");
+        } else if (reader.matches(OUTPUT_ACTION)) {
+            err.jspError(start, "jsp.error.jspoutput.invalidUse");
+        } else {
+            err.jspError(start, "jsp.error.badStandardAction");
+        }
+    }
+
+    /*
+     * # '<' CustomAction CustomActionBody
+     * 
+     * CustomAction ::= TagPrefix ':' CustomActionName
+     * 
+     * TagPrefix ::= Name
+     * 
+     * CustomActionName ::= Name
+     * 
+     * CustomActionBody ::= ( Attributes CustomActionEnd ) | <TRANSLATION_ERROR>
+     * 
+     * Attributes ::= ( S Attribute )* S?
+     * 
+     * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent |
+     * CustomActionScriptlessContent
+     * 
+     * CustomActionTagDependent ::= TagDependentOptionalBody
+     * 
+     * CustomActionJSPContent ::= OptionalBody
+     * 
+     * CustomActionScriptlessContent ::= ScriptlessOptionalBody
+     */
+    private boolean parseCustomTag(Node parent) throws JasperException {
+
+        if (reader.peekChar() != '<') {
+            return false;
+        }
+
+        // Parse 'CustomAction' production (tag prefix and custom action name)
+        reader.nextChar(); // skip '<'
+        String tagName = reader.parseToken(false);
+        int i = tagName.indexOf(':');
+        if (i == -1) {
+            reader.reset(start);
+            return false;
+        }
+
+        String prefix = tagName.substring(0, i);
+        String shortTagName = tagName.substring(i + 1);
+
+        // Check if this is a user-defined tag.
+        String uri = pageInfo.getURI(prefix);
+        if (uri == null) {
+            reader.reset(start);
+            // Remember the prefix for later error checking
+            pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
+            return false;
+        }
+
+        TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
+        TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
+        TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
+        if (tagInfo == null && tagFileInfo == null) {
+            err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
+        }
+        Class tagHandlerClass = null;
+        if (tagInfo != null) {
+            // Must be a classic tag, load it here.
+            // tag files will be loaded later, in TagFileProcessor
+            String handlerClassName = tagInfo.getTagClassName();
+            try {
+                tagHandlerClass = ctxt.getClassLoader().loadClass(
+                        handlerClassName);
+            } catch (Exception e) {
+                err.jspError(start, "jsp.error.loadclass.taghandler",
+                        handlerClassName, tagName);
+            }
+        }
+
+        // Parse 'CustomActionBody' production:
+        // At this point we are committed - if anything fails, we produce
+        // a translation error.
+
+        // Parse 'Attributes' production:
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        // Parse 'CustomActionEnd' production:
+        if (reader.matches("/>")) {
+            if (tagInfo != null) {
+                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
+                        start, parent, tagInfo, tagHandlerClass);
+            } else {
+                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
+                        start, parent, tagFileInfo);
+            }
+            return true;
+        }
+
+        // Now we parse one of 'CustomActionTagDependent',
+        // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
+        // depending on body-content in TLD.
+
+        // Looking for a body, it still can be empty; but if there is a
+        // a tag body, its syntax would be dependent on the type of
+        // body content declared in the TLD.
+        String bc;
+        if (tagInfo != null) {
+            bc = tagInfo.getBodyContent();
+        } else {
+            bc = tagFileInfo.getTagInfo().getBodyContent();
+        }
+
+        Node tagNode = null;
+        if (tagInfo != null) {
+            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
+                    attrs, start, parent, tagInfo, tagHandlerClass);
+        } else {
+            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
+                    attrs, start, parent, tagFileInfo);
+        }
+
+        parseOptionalBody(tagNode, tagName, bc);
+
+        return true;
+    }
+
+    /*
+     * Parse for a template text string until '<' or "${" or "#{" is encountered,
+     * recognizing escape sequences "\%", "\$", and "\#".
+     */
+    private void parseTemplateText(Node parent) throws JasperException {
+
+        if (!reader.hasMoreInput())
+            return;
+
+        CharArrayWriter ttext = new CharArrayWriter();
+        // Output the first character
+        int ch = reader.nextChar();
+        if (ch == '\\') {
+            reader.pushChar();
+        } else {
+            ttext.write(ch);
+        }
+
+        while (reader.hasMoreInput()) {
+            ch = reader.nextChar();
+            if (ch == '<') {
+                reader.pushChar();
+                break;
+            } else if ((ch == '$' || ch == '#') && !pageInfo.isELIgnored()) {
+                if (!reader.hasMoreInput()) {
+                    ttext.write(ch);
+                    break;
+                }
+                if (reader.nextChar() == '{') {
+                    reader.pushChar();
+                    reader.pushChar();
+                    break;
+                }
+                ttext.write(ch);
+                reader.pushChar();
+                continue;
+            } else if (ch == '\\') {
+                if (!reader.hasMoreInput()) {
+                    ttext.write('\\');
+                    break;
+                }
+                char next = (char) reader.peekChar();
+                // Looking for \% or \$ or \#
+                if (next == '%' || ((next == '$' || next == '#') &&
+                        !pageInfo.isELIgnored())) {
+                    ch = reader.nextChar();
+                }
+            }
+            ttext.write(ch);
+        }
+        new Node.TemplateText(ttext.toString(), start, parent);
+    }
+
+    /*
+     * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' |
+     * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) |
+     * <TRANSLATION_ERROR>
+     */
+    private void parseXMLTemplateText(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:text&gt;");
+            }
+            CharArrayWriter ttext = new CharArrayWriter();
+            while (reader.hasMoreInput()) {
+                int ch = reader.nextChar();
+                if (ch == '<') {
+                    // Check for <![CDATA[
+                    if (!reader.matches("![CDATA[")) {
+                        break;
+                    }
+                    start = reader.mark();
+                    Mark stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    String text = reader.getText(start, stop);
+                    ttext.write(text, 0, text.length());
+                } else if (ch == '\\') {
+                    if (!reader.hasMoreInput()) {
+                        ttext.write('\\');
+                        break;
+                    }
+                    ch = reader.nextChar();
+                    if (ch != '$' && ch != '#') {
+                        ttext.write('\\');
+                    }
+                    ttext.write(ch);
+                } else if (ch == '$' || ch == '#') {
+                    if (!reader.hasMoreInput()) {
+                        ttext.write(ch);
+                        break;
+                    }
+                    if (reader.nextChar() != '{') {
+                        ttext.write(ch);
+                        reader.pushChar();
+                        continue;
+                    }
+                    // Create a template text node
+                    new Node.TemplateText(ttext.toString(), start, parent);
+
+                    // Mark and parse the EL expression and create its node:
+                    start = reader.mark();
+                    parseELExpression(parent, (char) ch);
+
+                    start = reader.mark();
+                    ttext = new CharArrayWriter();
+                } else {
+                    ttext.write(ch);
+                }
+            }
+
+            new Node.TemplateText(ttext.toString(), start, parent);
+
+            if (!reader.hasMoreInput()) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:text&gt;");
+            } else if (!reader.matchesETagWithoutLessThan("jsp:text")) {
+                err.jspError(start, "jsp.error.jsptext.badcontent");
+            }
+        }
+    }
+
+    /*
+     * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' DeclarationBody ) | ( '<jsp:declaration'
+     * XMLDeclarationBody ) | ( '<%=' ExpressionBody ) | ( '<jsp:expression'
+     * XMLExpressionBody ) | ( '${' ELExpressionBody ) | ( '<%' ScriptletBody ) | ( '<jsp:scriptlet'
+     * XMLScriptletBody ) | ( '<jsp:text' XMLTemplateText ) | ( '<jsp:'
+     * StandardAction ) | ( '<' CustomAction CustomActionBody ) | TemplateText
+     */
+    private void parseElements(Node parent) throws JasperException {
+        if (scriptlessCount > 0) {
+            // vc: ScriptlessBody
+            // We must follow the ScriptlessBody production if one of
+            // our parents is ScriptlessBody.
+            parseElementsScriptless(parent);
+            return;
+        }
+
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            parseDeclaration(parent);
+        } else if (reader.matches("<jsp:declaration")) {
+            parseXMLDeclaration(parent);
+        } else if (reader.matches("<%=")) {
+            parseExpression(parent);
+        } else if (reader.matches("<jsp:expression")) {
+            parseXMLExpression(parent);
+        } else if (reader.matches("<%")) {
+            parseScriptlet(parent);
+        } else if (reader.matches("<jsp:scriptlet")) {
+            parseXMLScriptlet(parent);
+        } else if (reader.matches("<jsp:text")) {
+            parseXMLTemplateText(parent);
+        } else if (reader.matches("${") && !pageInfo.isELIgnored()) {
+            parseELExpression(parent, '$');
+        } else if (reader.matches("#{") && !pageInfo.isELIgnored()) {
+            parseELExpression(parent, '#');
+        } else if (reader.matches("<jsp:")) {
+            parseStandardAction(parent);
+        } else if (!parseCustomTag(parent)) {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+    }
+
+    /*
+     * ScriptlessBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
+     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
+     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:text' XMLTemplateText ) | ( '${'
+     * ELExpressionBody ) | ( '<jsp:' StandardAction ) | ( '<' CustomAction
+     * CustomActionBody ) | TemplateText
+     */
+    private void parseElementsScriptless(Node parent) throws JasperException {
+        // Keep track of how many scriptless nodes we've encountered
+        // so we know whether our child nodes are forced scriptless
+        scriptlessCount++;
+
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:declaration")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<%=")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:expression")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<%")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:scriptlet")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:text")) {
+            parseXMLTemplateText(parent);
+        } else if (reader.matches("${")) {
+            parseELExpression(parent, '$');
+        } else if (reader.matches("#{")) {
+            parseELExpression(parent, '#');
+        } else if (reader.matches("<jsp:")) {
+            parseStandardAction(parent);
+        } else if (!parseCustomTag(parent)) {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+
+        scriptlessCount--;
+    }
+
+    /*
+     * TemplateTextBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
+     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
+     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:text' <TRANSLATION_ERROR> ) | ( '${'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:' <TRANSLATION_ERROR> ) | TemplateText
+     */
+    private void parseElementsTemplateText(Node parent) throws JasperException {
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Declarations");
+        } else if (reader.matches("<jsp:declaration")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Declarations");
+        } else if (reader.matches("<%=")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expressions");
+        } else if (reader.matches("<jsp:expression")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expressions");
+        } else if (reader.matches("<%")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Scriptlets");
+        } else if (reader.matches("<jsp:scriptlet")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Scriptlets");
+        } else if (reader.matches("<jsp:text")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "&lt;jsp:text");
+        } else if (reader.matches("${")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expression language");
+        } else if (reader.matches("#{")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expression language");
+        } else if (reader.matches("<jsp:")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Standard actions");
+        } else if (parseCustomTag(parent)) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Custom actions");
+        } else {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+    }
+
+    /*
+     * Flag as error if an unbalanced end tag appears by itself.
+     */
+    private void checkUnbalancedEndTag() throws JasperException {
+
+        if (!reader.matches("</")) {
+            return;
+        }
+
+        // Check for unbalanced standard actions
+        if (reader.matches("jsp:")) {
+            err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
+        }
+
+        // Check for unbalanced custom actions
+        String tagName = reader.parseToken(false);
+        int i = tagName.indexOf(':');
+        if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
+            reader.reset(start);
+            return;
+        }
+
+        err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
+    }
+
+    /**
+     * TagDependentBody :=
+     */
+    private void parseTagDependentBody(Node parent, String tag)
+            throws JasperException {
+        Mark bodyStart = reader.mark();
+        Mark bodyEnd = reader.skipUntilETag(tag);
+        if (bodyEnd == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
+        }
+        new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart,
+                parent);
+    }
+
+    /*
+     * Parses jsp:body action.
+     */
+    private void parseJspBody(Node parent, String bodyType)
+            throws JasperException {
+        Mark start = reader.mark();
+        Node bodyNode = new Node.JspBody(start, parent);
+
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated", "&lt;jsp:body");
+            }
+            parseBody(bodyNode, "jsp:body", bodyType);
+        }
+    }
+
+    /*
+     * Parse the body as JSP content. @param tag The name of the tag whose end
+     * tag would terminate the body @param bodyType One of the TagInfo body
+     * types
+     */
+    private void parseBody(Node parent, String tag, String bodyType)
+            throws JasperException {
+        if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) {
+            parseTagDependentBody(parent, tag);
+        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) {
+            if (!reader.matchesETag(tag)) {
+                err.jspError(start, "jasper.error.emptybodycontent.nonempty",
+                        tag);
+            }
+        } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) {
+            // (note the == since we won't recognize JAVAX_*
+            // from outside this module).
+            parsePluginTags(parent);
+            if (!reader.matchesETag(tag)) {
+                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
+                        + tag);
+            }
+        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)
+                || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)
+                || (bodyType == JAVAX_BODY_CONTENT_PARAM)
+                || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) {
+            while (reader.hasMoreInput()) {
+                if (reader.matchesETag(tag)) {
+                    return;
+                }
+
+                // Check for nested jsp:body or jsp:attribute
+                if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
+                    if (reader.matches("<jsp:attribute")) {
+                        err.jspError(reader.mark(),
+                                "jsp.error.nested.jspattribute");
+                    } else if (reader.matches("<jsp:body")) {
+                        err.jspError(reader.mark(), "jsp.error.nested.jspbody");
+                    }
+                }
+
+                if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
+                    parseElements(parent);
+                } else if (bodyType
+                        .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                    parseElementsScriptless(parent);
+                } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
+                    // (note the == since we won't recognize JAVAX_*
+                    // from outside this module).
+                    reader.skipSpaces();
+                    parseParam(parent);
+                } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
+                    parseElementsTemplateText(parent);
+                }
+            }
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
+        } else {
+            err.jspError(start, "jasper.error.bad.bodycontent.type");
+        }
+    }
+
+    /*
+     * Parses named attributes.
+     */
+    private void parseNamedAttributes(Node parent) throws JasperException {
+        do {
+            Mark start = reader.mark();
+            Attributes attrs = parseAttributes();
+            Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
+                    attrs, start, parent);
+
+            reader.skipSpaces();
+            if (!reader.matches("/>")) {
+                if (!reader.matches(">")) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:attribute");
+                }
+                if (namedAttributeNode.isTrim()) {
+                    reader.skipSpaces();
+                }
+                parseBody(namedAttributeNode, "jsp:attribute",
+                        getAttributeBodyType(parent, attrs.getValue("name")));
+                if (namedAttributeNode.isTrim()) {
+                    Node.Nodes subElems = namedAttributeNode.getBody();
+                    if (subElems != null) {
+                        Node lastNode = subElems.getNode(subElems.size() - 1);
+                        if (lastNode instanceof Node.TemplateText) {
+                            ((Node.TemplateText) lastNode).rtrim();
+                        }
+                    }
+                }
+            }
+            reader.skipSpaces();
+        } while (reader.matches("<jsp:attribute"));
+    }
+
+    /**
+     * Determine the body type of <jsp:attribute> from the enclosing node
+     */
+    private String getAttributeBodyType(Node n, String name) {
+
+        if (n instanceof Node.CustomTag) {
+            TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo();
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            for (int i = 0; i < tldAttrs.length; i++) {
+                if (name.equals(tldAttrs[i].getName())) {
+                    if (tldAttrs[i].isFragment()) {
+                        return TagInfo.BODY_CONTENT_SCRIPTLESS;
+                    }
+                    if (tldAttrs[i].canBeRequestTime()) {
+                        return TagInfo.BODY_CONTENT_JSP;
+                    }
+                }
+            }
+            if (tagInfo.hasDynamicAttributes()) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.IncludeAction) {
+            if ("page".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.ForwardAction) {
+            if ("page".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.SetProperty) {
+            if ("value".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.UseBean) {
+            if ("beanName".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.PlugIn) {
+            if ("width".equals(name) || "height".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.ParamAction) {
+            if ("value".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.JspElement) {
+            return TagInfo.BODY_CONTENT_JSP;
+        }
+
+        return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
+    }
+
+    private void parseTagFileDirectives(Node parent) throws JasperException {
+        reader.setSingleFile(true);
+        reader.skipUntil("<");
+        while (reader.hasMoreInput()) {
+            start = reader.mark();
+            if (reader.matches("%--")) {
+                parseComment(parent);
+            } else if (reader.matches("%@")) {
+                parseDirective(parent);
+            } else if (reader.matches("jsp:directive.")) {
+                parseXMLDirective(parent);
+            }
+            reader.skipUntil("<");
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java
new file mode 100644
index 0000000..817fd7a
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java
@@ -0,0 +1,638 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Stack;
+import java.util.jar.JarFile;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.xmlparser.XMLEncodingDetector;
+import org.xml.sax.Attributes;
+
+/**
+ * Controller for the parsing of a JSP page.
+ * <p>
+ * The same ParserController instance is used for a JSP page and any JSP
+ * segments included by it (via an include directive), where each segment may
+ * be provided in standard or XML syntax. This class selects and invokes the
+ * appropriate parser for the JSP page and its included segments.
+ *
+ * @author Pierre Delisle
+ * @author Jan Luehe
+ */
+class ParserController implements TagConstants {
+
+    private static final String CHARSET = "charset=";
+
+    private JspCompilationContext ctxt;
+    private Compiler compiler;
+    private ErrorDispatcher err;
+
+    /*
+     * Indicates the syntax (XML or standard) of the file being processed
+     */
+    private boolean isXml;
+
+    /*
+     * A stack to keep track of the 'current base directory'
+     * for include directives that refer to relative paths.
+     */
+    private Stack baseDirStack = new Stack();
+
+    private boolean isEncodingSpecifiedInProlog;
+    private boolean isBomPresent;
+    private int skip;
+
+    private String sourceEnc;
+
+    private boolean isDefaultPageEncoding;
+    private boolean isTagFile;
+    private boolean directiveOnly;
+
+    /*
+     * Constructor
+     */
+    public ParserController(JspCompilationContext ctxt, Compiler compiler) {
+        this.ctxt = ctxt; 
+        this.compiler = compiler;
+        this.err = compiler.getErrorDispatcher();
+    }
+
+    public JspCompilationContext getJspCompilationContext () {
+        return ctxt;
+    }
+
+    public Compiler getCompiler () {
+        return compiler;
+    }
+
+    /**
+     * Parses a JSP page or tag file. This is invoked by the compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parse(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = false;
+        return doParse(inFileName, null, ctxt.getTagFileJarUrl());
+    }
+
+    /**
+     * Parses the directives of a JSP page or tag file. This is invoked by the
+     * compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parseDirectives(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = true;
+        return doParse(inFileName, null, ctxt.getTagFileJarUrl());
+    }
+
+
+    /**
+     * Processes an include directive with the given path.
+     *
+     * @param inFileName The path to the resource to be included.
+     * @param parent The parent node of the include directive.
+     * @param jarFile The JAR file from which to read the included resource,
+     * or null of the included resource is to be read from the filesystem
+     */
+    public Node.Nodes parse(String inFileName, Node parent,
+            URL jarFileUrl)
+    throws FileNotFoundException, JasperException, IOException {
+        // For files that are statically included, isTagfile and directiveOnly
+        // remain unchanged.
+        return doParse(inFileName, parent, jarFileUrl);
+    }
+
+    /**
+     * Extracts tag file directive information from the tag file with the
+     * given name.
+     *
+     * This is invoked by the compiler 
+     *
+     * @param inFileName The name of the tag file to be parsed.
+     * @deprecated Use {@link #parseTagFileDirectives(String, URL)}
+     *             See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+     */
+    public Node.Nodes parseTagFileDirectives(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        return parseTagFileDirectives(
+                inFileName, ctxt.getTagFileJarUrl(inFileName));
+    }
+
+    /**
+     * Extracts tag file directive information from the given tag file.
+     *
+     * This is invoked by the compiler 
+     *
+     * @param inFileName    The name of the tag file to be parsed.
+     * @param tagFileJarUrl The location of the tag file.
+     */
+    public Node.Nodes parseTagFileDirectives(String inFileName,
+            URL tagFileJarUrl)
+            throws FileNotFoundException, JasperException, IOException {
+        boolean isTagFileSave = isTagFile;
+        boolean directiveOnlySave = directiveOnly;
+        isTagFile = true;
+        directiveOnly = true;
+        Node.Nodes page = doParse(inFileName, null, tagFileJarUrl);
+        directiveOnly = directiveOnlySave;
+        isTagFile = isTagFileSave;
+        return page;
+    }
+
+    /**
+     * Parses the JSP page or tag file with the given path name.
+     *
+     * @param inFileName The name of the JSP page or tag file to be parsed.
+     * @param parent The parent node (non-null when processing an include
+     * directive)
+     * @param isTagFile true if file to be parsed is tag file, and false if it
+     * is a regular JSP page
+     * @param directivesOnly true if the file to be parsed is a tag file and
+     * we are only interested in the directives needed for constructing a
+     * TagFileInfo.
+     * @param jarFile The JAR file from which to read the JSP page or tag file,
+     * or null if the JSP page or tag file is to be read from the filesystem
+     */
+    private Node.Nodes doParse(String inFileName,
+            Node parent,
+            URL jarFileUrl)
+    throws FileNotFoundException, JasperException, IOException {
+
+        Node.Nodes parsedPage = null;
+        isEncodingSpecifiedInProlog = false;
+        isBomPresent = false;
+        isDefaultPageEncoding = false;
+
+        JarFile jarFile = getJarFile(jarFileUrl);
+        String absFileName = resolveFileName(inFileName);
+        String jspConfigPageEnc = getJspConfigPageEncoding(absFileName);
+
+        // Figure out what type of JSP document and encoding type we are
+        // dealing with
+        determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc);
+
+        if (parent != null) {
+            // Included resource, add to dependent list
+            if (jarFile == null) {
+                compiler.getPageInfo().addDependant(absFileName);
+            } else {
+                compiler.getPageInfo().addDependant(
+                        jarFileUrl.toExternalForm() + absFileName.substring(1));
+            }
+        }
+
+        if ((isXml && isEncodingSpecifiedInProlog) || isBomPresent) {
+            /*
+             * Make sure the encoding explicitly specified in the XML
+             * prolog (if any) matches that in the JSP config element
+             * (if any), treating "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc)
+                    && (!jspConfigPageEnc.startsWith("UTF-16")
+                            || !sourceEnc.startsWith("UTF-16"))) {
+                err.jspError("jsp.error.prolog_config_encoding_mismatch",
+                        sourceEnc, jspConfigPageEnc);
+            }
+        }
+
+        // Dispatch to the appropriate parser
+        if (isXml) {
+            // JSP document (XML syntax)
+            // InputStream for jspx page is created and properly closed in
+            // JspDocumentParser.
+            parsedPage = JspDocumentParser.parse(this, absFileName,
+                    jarFile, parent,
+                    isTagFile, directiveOnly,
+                    sourceEnc,
+                    jspConfigPageEnc,
+                    isEncodingSpecifiedInProlog,
+                    isBomPresent);
+        } else {
+            // Standard syntax
+            InputStreamReader inStreamReader = null;
+            try {
+                inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
+                        jarFile, ctxt, err, skip);
+                JspReader jspReader = new JspReader(ctxt, absFileName,
+                        sourceEnc, inStreamReader,
+                        err);
+                parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
+                        directiveOnly, jarFileUrl,
+                        sourceEnc, jspConfigPageEnc,
+                        isDefaultPageEncoding, isBomPresent);
+            } finally {
+                if (inStreamReader != null) {
+                    try {
+                        inStreamReader.close();
+                    } catch (Exception any) {
+                    }
+                }
+            }
+        }
+
+        if (jarFile != null) {
+            try {
+                jarFile.close();
+            } catch (Throwable t) {}
+        }
+
+        baseDirStack.pop();
+
+        return parsedPage;
+    }
+
+    /*
+     * Checks to see if the given URI is matched by a URL pattern specified in
+     * a jsp-property-group in web.xml, and if so, returns the value of the
+     * <page-encoding> element.
+     *
+     * @param absFileName The URI to match
+     *
+     * @return The value of the <page-encoding> attribute of the 
+     * jsp-property-group with matching URL pattern
+     */
+    private String getJspConfigPageEncoding(String absFileName)
+    throws JasperException {
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty
+            = jspConfig.findJspProperty(absFileName);
+        return jspProperty.getPageEncoding();
+    }
+
+    /**
+     * Determines the syntax (standard or XML) and page encoding properties
+     * for the given file, and stores them in the 'isXml' and 'sourceEnc'
+     * instance variables, respectively.
+     */
+    private void determineSyntaxAndEncoding(String absFileName,
+            JarFile jarFile,
+            String jspConfigPageEnc)
+    throws JasperException, IOException {
+
+        isXml = false;
+
+        /*
+         * 'true' if the syntax (XML or standard) of the file is given
+         * from external information: either via a JSP configuration element,
+         * the ".jspx" suffix, or the enclosing file (for included resources)
+         */
+        boolean isExternal = false;
+
+        /*
+         * Indicates whether we need to revert from temporary usage of
+         * "ISO-8859-1" back to "UTF-8"
+         */
+        boolean revert = false;
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(
+                absFileName);
+        if (jspProperty.isXml() != null) {
+            // If <is-xml> is specified in a <jsp-property-group>, it is used.
+            isXml = JspUtil.booleanValue(jspProperty.isXml());
+            isExternal = true;
+        } else if (absFileName.endsWith(".jspx")
+                || absFileName.endsWith(".tagx")) {
+            isXml = true;
+            isExternal = true;
+        }
+
+        if (isExternal && !isXml) {
+            // JSP (standard) syntax. Use encoding specified in jsp-config
+            // if provided.
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc != null) {
+                return;
+            }
+            // We don't know the encoding, so use BOM to determine it
+            sourceEnc = "ISO-8859-1";
+        } else {
+            // XML syntax or unknown, (auto)detect encoding ...
+            Object[] ret = XMLEncodingDetector.getEncoding(absFileName,
+                    jarFile, ctxt, err);
+            sourceEnc = (String) ret[0];
+            if (((Boolean) ret[1]).booleanValue()) {
+                isEncodingSpecifiedInProlog = true;
+            }
+            if (((Boolean) ret[2]).booleanValue()) {
+                isBomPresent = true;
+            }
+            skip = ((Integer) ret[3]).intValue();
+
+            if (!isXml && sourceEnc.equals("UTF-8")) {
+                /*
+                 * We don't know if we're dealing with XML or standard syntax.
+                 * Therefore, we need to check to see if the page contains
+                 * a <jsp:root> element.
+                 *
+                 * We need to be careful, because the page may be encoded in
+                 * ISO-8859-1 (or something entirely different), and may
+                 * contain byte sequences that will cause a UTF-8 converter to
+                 * throw exceptions. 
+                 *
+                 * It is safe to use a source encoding of ISO-8859-1 in this
+                 * case, as there are no invalid byte sequences in ISO-8859-1,
+                 * and the byte/character sequences we're looking for (i.e.,
+                 * <jsp:root>) are identical in either encoding (both UTF-8
+                 * and ISO-8859-1 are extensions of ASCII).
+                 */
+                sourceEnc = "ISO-8859-1";
+                revert = true;
+            }
+        }
+
+        if (isXml) {
+            // (This implies 'isExternal' is TRUE.)
+            // We know we're dealing with a JSP document (via JSP config or
+            // ".jspx" suffix), so we're done.
+            return;
+        }
+
+        /*
+         * At this point, 'isExternal' or 'isXml' is FALSE.
+         * Search for jsp:root action, in order to determine if we're dealing 
+         * with XML or standard syntax (unless we already know what we're 
+         * dealing with, i.e., when 'isExternal' is TRUE and 'isXml' is FALSE).
+         * No check for XML prolog, since nothing prevents a page from
+         * outputting XML and still using JSP syntax (in this case, the 
+         * XML prolog is treated as template text).
+         */
+        JspReader jspReader = null;
+        try {
+            jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile,
+                    err);
+        } catch (FileNotFoundException ex) {
+            throw new JasperException(ex);
+        }
+        jspReader.setSingleFile(true);
+        Mark startMark = jspReader.mark();
+        if (!isExternal) {
+            jspReader.reset(startMark);
+            if (hasJspRoot(jspReader)) {
+                if (revert) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = true;
+                return;
+            } else {
+                if (revert && isBomPresent) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = false;
+            }
+        }
+
+        /*
+         * At this point, we know we're dealing with JSP syntax.
+         * If an XML prolog is provided, it's treated as template text.
+         * Determine the page encoding from the page directive, unless it's
+         * specified via JSP config.
+         */
+        if (!isBomPresent) {
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc == null) {
+                sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark);
+                if (sourceEnc == null) {
+                    // Default to "ISO-8859-1" per JSP spec
+                    sourceEnc = "ISO-8859-1";
+                    isDefaultPageEncoding = true;
+                }
+            }
+        }
+        
+    }
+
+    /*
+     * Determines page source encoding for page or tag file in JSP syntax,
+     * by reading (in this order) the value of the 'pageEncoding' page
+     * directive attribute, or the charset value of the 'contentType' page
+     * directive attribute.
+     *
+     * @return The page encoding, or null if not found
+     */
+    private String getPageEncodingForJspSyntax(JspReader jspReader,
+            Mark startMark)
+    throws JasperException {
+
+        String encoding = null;
+        String saveEncoding = null;
+
+        jspReader.reset(startMark);
+
+        /*
+         * Determine page encoding from directive of the form <%@ page %>,
+         * <%@ tag %>, <jsp:directive.page > or <jsp:directive.tag >.
+         */
+        while (true) {
+            if (jspReader.skipUntil("<") == null) {
+                break;
+            }
+            // If this is a comment, skip until its end
+            if (jspReader.matches("%--")) {
+                if (jspReader.skipUntil("--%>") == null) {
+                    // error will be caught in Parser
+                    break;
+                }
+                continue;
+            }
+            boolean isDirective = jspReader.matches("%@");
+            if (isDirective) {
+                jspReader.skipSpaces();
+            }
+            else {
+                isDirective = jspReader.matches("jsp:directive.");
+            }
+            if (!isDirective) {
+                continue;
+            }
+
+            // compare for "tag ", so we don't match "taglib"
+            if (jspReader.matches("tag ") || jspReader.matches("page")) {
+
+                jspReader.skipSpaces();
+                Attributes attrs = Parser.parseAttributes(this, jspReader);
+                encoding = getPageEncodingFromDirective(attrs, "pageEncoding");
+                if (encoding != null) {
+                    break;
+                }
+                encoding = getPageEncodingFromDirective(attrs, "contentType");
+                if (encoding != null) {
+                    saveEncoding = encoding;
+                }
+            }
+        }
+
+        if (encoding == null) {
+            encoding = saveEncoding;
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Scans the given attributes for the attribute with the given name,
+     * which is either 'pageEncoding' or 'contentType', and returns the
+     * specified page encoding.
+     *
+     * In the case of 'contentType', the page encoding is taken from the
+     * content type's 'charset' component.
+     *
+     * @param attrs The page directive attributes
+     * @param attrName The name of the attribute to search for (either
+     * 'pageEncoding' or 'contentType')
+     *
+     * @return The page encoding, or null
+     */
+    private String getPageEncodingFromDirective(Attributes attrs,
+            String attrName) {
+        String value = attrs.getValue(attrName);
+        if (attrName.equals("pageEncoding")) {
+            return value;
+        }
+
+        // attrName = contentType
+        String contentType = value;
+        String encoding = null;
+        if (contentType != null) {
+            int loc = contentType.indexOf(CHARSET);
+            if (loc != -1) {
+                encoding = contentType.substring(loc + CHARSET.length());
+            }
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Resolve the name of the file and update baseDirStack() to keep track of
+     * the current base directory for each included file.
+     * The 'root' file is always an 'absolute' path, so no need to put an
+     * initial value in the baseDirStack.
+     */
+    private String resolveFileName(String inFileName) {
+        String fileName = inFileName.replace('\\', '/');
+        boolean isAbsolute = fileName.startsWith("/");
+        fileName = isAbsolute ? fileName 
+                : (String) baseDirStack.peek() + fileName;
+        String baseDir = 
+            fileName.substring(0, fileName.lastIndexOf("/") + 1);
+        baseDirStack.push(baseDir);
+        return fileName;
+    }
+
+    /*
+     * Checks to see if the given page contains, as its first element, a <root>
+     * element whose prefix is bound to the JSP namespace, as in:
+     *
+     * <wombat:root xmlns:wombat="http://java.sun.com/JSP/Page" version="1.2">
+     *   ...
+     * </wombat:root>
+     *
+     * @param reader The reader for this page
+     *
+     * @return true if this page contains a root element whose prefix is bound
+     * to the JSP namespace, and false otherwise
+     */
+    private boolean hasJspRoot(JspReader reader) throws JasperException {
+
+        // <prefix>:root must be the first element
+        Mark start = null;
+        while ((start = reader.skipUntil("<")) != null) {
+            int c = reader.nextChar();
+            if (c != '!' && c != '?') break;
+        }
+        if (start == null) {
+            return false;
+        }
+        Mark stop = reader.skipUntil(":root");
+        if (stop == null) {
+            return false;
+        }
+        // call substring to get rid of leading '<'
+        String prefix = reader.getText(start, stop).substring(1);
+
+        start = stop;
+        stop = reader.skipUntil(">");
+        if (stop == null) {
+            return false;
+        }
+
+        // Determine namespace associated with <root> element's prefix
+        String root = reader.getText(start, stop);
+        String xmlnsDecl = "xmlns:" + prefix;
+        int index = root.indexOf(xmlnsDecl);
+        if (index == -1) {
+            return false;
+        }
+        index += xmlnsDecl.length();
+        while (index < root.length()
+                && Character.isWhitespace(root.charAt(index))) {
+            index++;
+        }
+        if (index < root.length() && root.charAt(index) == '=') {
+            index++;
+            while (index < root.length()
+                    && Character.isWhitespace(root.charAt(index))) {
+                index++;
+            }
+            if (index < root.length() && root.charAt(index++) == '"'
+                && root.regionMatches(index, JSP_URI, 0,
+                        JSP_URI.length())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private JarFile getJarFile(URL jarFileUrl) throws IOException {
+        JarFile jarFile = null;
+
+        if (jarFileUrl != null) {
+            JarURLConnection conn = (JarURLConnection) jarFileUrl.openConnection();
+            conn.setUseCaches(false);
+            conn.connect();
+            jarFile = conn.getJarFile();
+        }
+
+        return jarFile;
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java
new file mode 100644
index 0000000..7110356
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java
@@ -0,0 +1,148 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.*;
+import org.apache.jasper.JasperException;
+
+/**
+ * Class responsible for determining the scripting variables that every
+ * custom action needs to declare.
+ *
+ * @author Jan Luehe
+ */
+class ScriptingVariabler {
+
+    private static final Integer MAX_SCOPE = new Integer(Integer.MAX_VALUE);
+
+    /*
+     * Assigns an identifier (of type integer) to every custom tag, in order
+     * to help identify, for every custom tag, the scripting variables that it
+     * needs to declare.
+     */
+    static class CustomTagCounter extends Node.Visitor {
+
+	private int count;
+	private Node.CustomTag parent;
+
+	public void visit(Node.CustomTag n) throws JasperException {
+	    n.setCustomTagParent(parent);
+	    Node.CustomTag tmpParent = parent;
+	    parent = n;
+	    visitBody(n);
+	    parent = tmpParent;
+	    n.setNumCount(new Integer(count++));
+	}
+    }
+
+    /*
+     * For every custom tag, determines the scripting variables it needs to
+     * declare. 
+     */
+    static class ScriptingVariableVisitor extends Node.Visitor {
+
+	private ErrorDispatcher err;
+	private Hashtable scriptVars;
+	
+	public ScriptingVariableVisitor(ErrorDispatcher err) {
+	    this.err = err;
+	    scriptVars = new Hashtable();
+	}
+
+	public void visit(Node.CustomTag n) throws JasperException {
+	    setScriptingVars(n, VariableInfo.AT_BEGIN);
+	    setScriptingVars(n, VariableInfo.NESTED);
+	    visitBody(n);
+	    setScriptingVars(n, VariableInfo.AT_END);
+	}
+
+	private void setScriptingVars(Node.CustomTag n, int scope)
+	        throws JasperException {
+
+	    TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+	    VariableInfo[] varInfos = n.getVariableInfos();
+	    if (tagVarInfos.length == 0 && varInfos.length == 0) {
+		return;
+	    }
+
+	    Vector vec = new Vector();
+
+	    Integer ownRange = null;
+	    if (scope == VariableInfo.AT_BEGIN
+		    || scope == VariableInfo.AT_END) {
+		Node.CustomTag parent = n.getCustomTagParent();
+		if (parent == null)
+		    ownRange = MAX_SCOPE;
+		else
+		    ownRange = parent.getNumCount();
+	    } else {
+		// NESTED
+		ownRange = n.getNumCount();
+	    }
+
+	    if (varInfos.length > 0) {
+		for (int i=0; i<varInfos.length; i++) {
+		    if (varInfos[i].getScope() != scope
+			    || !varInfos[i].getDeclare()) {
+			continue;
+		    }
+		    String varName = varInfos[i].getVarName();
+		    
+		    Integer currentRange = (Integer) scriptVars.get(varName);
+		    if (currentRange == null
+			    || ownRange.compareTo(currentRange) > 0) {
+			scriptVars.put(varName, ownRange);
+			vec.add(varInfos[i]);
+		    }
+		}
+	    } else {
+		for (int i=0; i<tagVarInfos.length; i++) {
+		    if (tagVarInfos[i].getScope() != scope
+			    || !tagVarInfos[i].getDeclare()) {
+			continue;
+		    }
+		    String varName = tagVarInfos[i].getNameGiven();
+		    if (varName == null) {
+			varName = n.getTagData().getAttributeString(
+		                        tagVarInfos[i].getNameFromAttribute());
+			if (varName == null) {
+			    err.jspError(n, "jsp.error.scripting.variable.missing_name",
+					 tagVarInfos[i].getNameFromAttribute());
+			}
+		    }
+
+		    Integer currentRange = (Integer) scriptVars.get(varName);
+		    if (currentRange == null
+			    || ownRange.compareTo(currentRange) > 0) {
+			scriptVars.put(varName, ownRange);
+			vec.add(tagVarInfos[i]);
+		    }
+		}
+	    }
+
+	    n.setScriptingVars(vec, scope);
+	}
+    }
+
+    public static void set(Node.Nodes page, ErrorDispatcher err)
+	    throws JasperException {
+	page.visit(new CustomTagCounter());
+	page.visit(new ScriptingVariableVisitor(err));
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java
new file mode 100644
index 0000000..a407f77
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java
@@ -0,0 +1,176 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * This is what is used to generate servlets. 
+ *
+ * @author Anil K. Vijendran
+ * @author Kin-man Chung
+ */
+public class ServletWriter {
+    public static int TAB_WIDTH = 2;
+    public static String SPACES = "                              ";
+
+    // Current indent level:
+    private int indent = 0;
+    private int virtual_indent = 0;
+
+    // The sink writer:
+    PrintWriter writer;
+    
+    // servlet line numbers start from 1
+    private int javaLine = 1;
+
+
+    public ServletWriter(PrintWriter writer) {
+	this.writer = writer;
+    }
+
+    public void close() throws IOException {
+	writer.close();
+    }
+
+    
+    // -------------------- Access informations --------------------
+
+    public int getJavaLine() {
+        return javaLine;
+    }
+
+
+    // -------------------- Formatting --------------------
+
+    public void pushIndent() {
+	virtual_indent += TAB_WIDTH;
+	if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+	    indent = virtual_indent;
+    }
+
+    public void popIndent() {
+	virtual_indent -= TAB_WIDTH;
+	if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+	    indent = virtual_indent;
+    }
+
+    /**
+     * Print a standard comment for echo outputed chunk.
+     * @param start The starting position of the JSP chunk being processed. 
+     * @param stop  The ending position of the JSP chunk being processed. 
+     */
+    public void printComment(Mark start, Mark stop, char[] chars) {
+        if (start != null && stop != null) {
+            println("// from="+start);
+            println("//   to="+stop);
+        }
+        
+        if (chars != null)
+            for(int i = 0; i < chars.length;) {
+                printin();
+                print("// ");
+                while (chars[i] != '\n' && i < chars.length)
+                    writer.print(chars[i++]);
+            }
+    }
+
+    /**
+     * Prints the given string followed by '\n'
+     */
+    public void println(String s) {
+        javaLine++;
+	writer.println(s);
+    }
+
+    /**
+     * Prints a '\n'
+     */
+    public void println() {
+        javaLine++;
+	writer.println("");
+    }
+
+    /**
+     * Prints the current indention
+     */
+    public void printin() {
+	writer.print(SPACES.substring(0, indent));
+    }
+
+    /**
+     * Prints the current indention, followed by the given string
+     */
+    public void printin(String s) {
+	writer.print(SPACES.substring(0, indent));
+	writer.print(s);
+    }
+
+    /**
+     * Prints the current indention, and then the string, and a '\n'.
+     */
+    public void printil(String s) {
+        javaLine++;
+	writer.print(SPACES.substring(0, indent));
+	writer.println(s);
+    }
+
+    /**
+     * Prints the given char.
+     *
+     * Use println() to print a '\n'.
+     */
+    public void print(char c) {
+	writer.print(c);
+    }
+
+    /**
+     * Prints the given int.
+     */
+    public void print(int i) {
+	writer.print(i);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * The string must not contain any '\n', otherwise the line count will be
+     * off.
+     */
+    public void print(String s) {
+	writer.print(s);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * If the string spans multiple lines, the line count will be adjusted
+     * accordingly.
+     */
+    public void printMultiLn(String s) {
+        int index = 0;
+
+        // look for hidden newlines inside strings
+        while ((index=s.indexOf('\n',index)) > -1 ) {
+            javaLine++;
+            index++;
+        }
+
+	writer.print(s);
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java
new file mode 100644
index 0000000..6737447
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java
@@ -0,0 +1,171 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Represents a source map (SMAP), which serves to associate lines
+ * of the input JSP file(s) to lines in the generated servlet in the
+ * final .class file, according to the JSR-045 spec.
+ * 
+ * @author Shawn Bayern
+ */
+public class SmapGenerator {
+
+    //*********************************************************************
+    // Overview
+
+    /*
+     * The SMAP syntax is reasonably straightforward.  The purpose of this
+     * class is currently twofold:
+     *  - to provide a simple but low-level Java interface to build
+     *    a logical SMAP
+     *  - to serialize this logical SMAP for eventual inclusion directly
+     *    into a .class file.
+     */
+
+
+    //*********************************************************************
+    // Private state
+
+    private String outputFileName;
+    private String defaultStratum = "Java";
+    private List strata = new ArrayList();
+    private List embedded = new ArrayList();
+    private boolean doEmbedded = true;
+
+    //*********************************************************************
+    // Methods for adding mapping data
+
+    /**
+     * Sets the filename (without path information) for the generated
+     * source file.  E.g., "foo$jsp.java".
+     */
+    public synchronized void setOutputFileName(String x) {
+	outputFileName = x;
+    }
+
+    /**
+     * Adds the given SmapStratum object, representing a Stratum with
+     * logically associated FileSection and LineSection blocks, to
+     * the current SmapGenerator.  If <tt>default</tt> is true, this
+     * stratum is made the default stratum, overriding any previously
+     * set default.
+     *
+     * @param stratum the SmapStratum object to add
+     * @param defaultStratum if <tt>true</tt>, this SmapStratum is considered
+     *                to represent the default SMAP stratum unless
+     *                overwritten
+     */
+    public synchronized void addStratum(SmapStratum stratum,
+					boolean defaultStratum) {
+	strata.add(stratum);
+	if (defaultStratum)
+	    this.defaultStratum = stratum.getStratumName();
+    }
+
+    /**
+     * Adds the given string as an embedded SMAP with the given stratum name.
+     *
+     * @param smap the SMAP to embed
+     * @param stratumName the name of the stratum output by the compilation
+     *                    that produced the <tt>smap</tt> to be embedded
+     */
+    public synchronized void addSmap(String smap, String stratumName) {
+	embedded.add("*O " + stratumName + "\n"
+		   + smap
+		   + "*C " + stratumName + "\n");
+    }
+
+    /**
+     * Instructs the SmapGenerator whether to actually print any embedded
+     * SMAPs or not.  Intended for situations without an SMAP resolver.
+     *
+     * @param status If <tt>false</tt>, ignore any embedded SMAPs.
+     */
+    public void setDoEmbedded(boolean status) {
+	doEmbedded = status;
+    }
+
+    //*********************************************************************
+    // Methods for serializing the logical SMAP
+
+    public synchronized String getString() {
+	// check state and initialize buffer
+	if (outputFileName == null)
+	    throw new IllegalStateException();
+        StringBuffer out = new StringBuffer();
+
+	// start the SMAP
+	out.append("SMAP\n");
+	out.append(outputFileName + '\n');
+	out.append(defaultStratum + '\n');
+
+	// include embedded SMAPs
+	if (doEmbedded) {
+	    int nEmbedded = embedded.size();
+	    for (int i = 0; i < nEmbedded; i++) {
+	        out.append(embedded.get(i));
+	    }
+	}
+
+	// print our StratumSections, FileSections, and LineSections
+	int nStrata = strata.size();
+	for (int i = 0; i < nStrata; i++) {
+	    SmapStratum s = (SmapStratum) strata.get(i);
+	    out.append(s.getString());
+	}
+
+	// end the SMAP
+	out.append("*E\n");
+
+	return out.toString();
+    }
+
+    public String toString() { return getString(); }
+
+    //*********************************************************************
+    // For testing (and as an example of use)...
+
+    public static void main(String args[]) {
+	SmapGenerator g = new SmapGenerator();
+	g.setOutputFileName("foo.java");
+	SmapStratum s = new SmapStratum("JSP");
+	s.addFile("foo.jsp");
+	s.addFile("bar.jsp", "/foo/foo/bar.jsp");
+	s.addLineData(1, "foo.jsp", 1, 1, 1);
+	s.addLineData(2, "foo.jsp", 1, 6, 1);
+	s.addLineData(3, "foo.jsp", 2, 10, 5);
+	s.addLineData(20, "bar.jsp", 1, 30, 1);
+	g.addStratum(s, true);
+	System.out.print(g);
+
+	System.out.println("---");
+
+	SmapGenerator embedded = new SmapGenerator();
+	embedded.setOutputFileName("blargh.tier2");
+	s = new SmapStratum("Tier2");
+	s.addFile("1.tier2");
+	s.addLineData(1, "1.tier2", 1, 1, 1);
+	embedded.addStratum(s, true);
+	g.addSmap(embedded.toString(), "JSP");
+	System.out.println(g);
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java
new file mode 100644
index 0000000..d0c506e
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java
@@ -0,0 +1,336 @@
+/*
+ * 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.jasper.compiler;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Represents the line and file mappings associated with a JSR-045
+ * "stratum".
+ *
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ */
+public class SmapStratum {
+
+    //*********************************************************************
+    // Class for storing LineInfo data
+
+    /**
+     * Represents a single LineSection in an SMAP, associated with
+     * a particular stratum.
+     */
+    public static class LineInfo {
+        private int inputStartLine = -1;
+        private int outputStartLine = -1;
+        private int lineFileID = 0;
+        private int inputLineCount = 1;
+        private int outputLineIncrement = 1;
+        private boolean lineFileIDSet = false;
+
+        /** Sets InputStartLine. */
+        public void setInputStartLine(int inputStartLine) {
+            if (inputStartLine < 0)
+                throw new IllegalArgumentException("" + inputStartLine);
+            this.inputStartLine = inputStartLine;
+        }
+
+        /** Sets OutputStartLine. */
+        public void setOutputStartLine(int outputStartLine) {
+            if (outputStartLine < 0)
+                throw new IllegalArgumentException("" + outputStartLine);
+            this.outputStartLine = outputStartLine;
+        }
+
+        /**
+             * Sets lineFileID.  Should be called only when different from
+             * that of prior LineInfo object (in any given context) or 0
+             * if the current LineInfo has no (logical) predecessor.
+             * <tt>LineInfo</tt> will print this file number no matter what.
+             */
+        public void setLineFileID(int lineFileID) {
+            if (lineFileID < 0)
+                throw new IllegalArgumentException("" + lineFileID);
+            this.lineFileID = lineFileID;
+            this.lineFileIDSet = true;
+        }
+
+        /** Sets InputLineCount. */
+        public void setInputLineCount(int inputLineCount) {
+            if (inputLineCount < 0)
+                throw new IllegalArgumentException("" + inputLineCount);
+            this.inputLineCount = inputLineCount;
+        }
+
+        /** Sets OutputLineIncrement. */
+        public void setOutputLineIncrement(int outputLineIncrement) {
+            if (outputLineIncrement < 0)
+                throw new IllegalArgumentException("" + outputLineIncrement);
+            this.outputLineIncrement = outputLineIncrement;
+        }
+
+        /**
+         * Retrieves the current LineInfo as a String, print all values
+         * only when appropriate (but LineInfoID if and only if it's been
+         * specified, as its necessity is sensitive to context).
+         */
+        public String getString() {
+            if (inputStartLine == -1 || outputStartLine == -1)
+                throw new IllegalStateException();
+            StringBuffer out = new StringBuffer();
+            out.append(inputStartLine);
+            if (lineFileIDSet)
+                out.append("#" + lineFileID);
+            if (inputLineCount != 1)
+                out.append("," + inputLineCount);
+            out.append(":" + outputStartLine);
+            if (outputLineIncrement != 1)
+                out.append("," + outputLineIncrement);
+            out.append('\n');
+            return out.toString();
+        }
+
+        public String toString() {
+            return getString();
+        }
+    }
+
+    //*********************************************************************
+    // Private state
+
+    private String stratumName;
+    private List fileNameList;
+    private List filePathList;
+    private List lineData;
+    private int lastFileID;
+
+    //*********************************************************************
+    // Constructor
+
+    /**
+     * Constructs a new SmapStratum object for the given stratum name
+     * (e.g., JSP).
+     *
+     * @param stratumName the name of the stratum (e.g., JSP)
+     */
+    public SmapStratum(String stratumName) {
+        this.stratumName = stratumName;
+        fileNameList = new ArrayList();
+        filePathList = new ArrayList();
+        lineData = new ArrayList();
+        lastFileID = 0;
+    }
+
+    //*********************************************************************
+    // Methods to add mapping information
+
+    /**
+     * Adds record of a new file, by filename.
+     *
+     * @param filename the filename to add, unqualified by path.
+     */
+    public void addFile(String filename) {
+        addFile(filename, filename);
+    }
+
+    /**
+     * Adds record of a new file, by filename and path.  The path
+     * may be relative to a source compilation path.
+     *
+     * @param filename the filename to add, unqualified by path
+     * @param filePath the path for the filename, potentially relative
+     *                 to a source compilation path
+     */
+    public void addFile(String filename, String filePath) {
+        int pathIndex = filePathList.indexOf(filePath);
+        if (pathIndex == -1) {
+            fileNameList.add(filename);
+            filePathList.add(filePath);
+        }
+    }
+
+    /**
+     * Combines consecutive LineInfos wherever possible
+     */
+    public void optimizeLineSection() {
+
+/* Some debugging code
+        for (int i = 0; i < lineData.size(); i++) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            System.out.print(li.toString());
+        }
+*/
+        //Incorporate each LineInfo into the previous LineInfo's 
+        //outputLineIncrement, if possible
+        int i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            LineInfo liNext = (LineInfo)lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine
+                && liNext.inputLineCount == 1
+                && li.inputLineCount == 1
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setOutputLineIncrement(
+                    liNext.outputStartLine
+                        - li.outputStartLine
+                        + liNext.outputLineIncrement);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+
+        //Incorporate each LineInfo into the previous LineInfo's
+        //inputLineCount, if possible
+        i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            LineInfo liNext = (LineInfo)lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine + li.inputLineCount
+                && liNext.outputLineIncrement == li.outputLineIncrement
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setInputLineCount(li.inputLineCount + liNext.inputLineCount);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+    }
+
+    /**
+     * Adds complete information about a simple line mapping.  Specify
+     * all the fields in this method; the back-end machinery takes care
+     * of printing only those that are necessary in the final SMAP.
+     * (My view is that fields are optional primarily for spatial efficiency,
+     * not for programmer convenience.  Could always add utility methods
+     * later.)
+     *
+     * @param inputStartLine starting line in the source file
+     *        (SMAP <tt>InputStartLine</tt>)
+     * @param inputFileName the filepath (or name) from which the input comes
+     *        (yields SMAP <tt>LineFileID</tt>)  Use unqualified names
+     *        carefully, and only when they uniquely identify a file.
+     * @param inputLineCount the number of lines in the input to map
+     *        (SMAP <tt>LineFileCount</tt>)
+     * @param outputStartLine starting line in the output file 
+     *        (SMAP <tt>OutputStartLine</tt>)
+     * @param outputLineIncrement number of output lines to map to each
+     *        input line (SMAP <tt>OutputLineIncrement</tt>).  <i>Given the
+     *        fact that the name starts with "output", I continuously have
+     *        the subconscious urge to call this field
+     *        <tt>OutputLineExcrement</tt>.</i>
+     */
+    public void addLineData(
+        int inputStartLine,
+        String inputFileName,
+        int inputLineCount,
+        int outputStartLine,
+        int outputLineIncrement) {
+        // check the input - what are you doing here??
+        int fileIndex = filePathList.indexOf(inputFileName);
+        if (fileIndex == -1) // still
+            throw new IllegalArgumentException(
+                "inputFileName: " + inputFileName);
+
+        //Jasper incorrectly SMAPs certain Nodes, giving them an 
+        //outputStartLine of 0.  This can cause a fatal error in
+        //optimizeLineSection, making it impossible for Jasper to
+        //compile the JSP.  Until we can fix the underlying
+        //SMAPping problem, we simply ignore the flawed SMAP entries.
+        if (outputStartLine == 0)
+            return;
+
+        // build the LineInfo
+        LineInfo li = new LineInfo();
+        li.setInputStartLine(inputStartLine);
+        li.setInputLineCount(inputLineCount);
+        li.setOutputStartLine(outputStartLine);
+        li.setOutputLineIncrement(outputLineIncrement);
+        if (fileIndex != lastFileID)
+            li.setLineFileID(fileIndex);
+        lastFileID = fileIndex;
+
+        // save it
+        lineData.add(li);
+    }
+
+    //*********************************************************************
+    // Methods to retrieve information
+
+    /**
+     * Returns the name of the stratum.
+     */
+    public String getStratumName() {
+        return stratumName;
+    }
+
+    /**
+     * Returns the given stratum as a String:  a StratumSection,
+     * followed by at least one FileSection and at least one LineSection.
+     */
+    public String getString() {
+        // check state and initialize buffer
+        if (fileNameList.size() == 0 || lineData.size() == 0)
+            return null;
+
+        StringBuffer out = new StringBuffer();
+
+        // print StratumSection
+        out.append("*S " + stratumName + "\n");
+
+        // print FileSection
+        out.append("*F\n");
+        int bound = fileNameList.size();
+        for (int i = 0; i < bound; i++) {
+            if (filePathList.get(i) != null) {
+                out.append("+ " + i + " " + fileNameList.get(i) + "\n");
+                // Source paths must be relative, not absolute, so we
+                // remove the leading "/", if one exists.
+                String filePath = (String)filePathList.get(i);
+                if (filePath.startsWith("/")) {
+                    filePath = filePath.substring(1);
+                }
+                out.append(filePath + "\n");
+            } else {
+                out.append(i + " " + fileNameList.get(i) + "\n");
+            }
+        }
+
+        // print LineSection
+        out.append("*L\n");
+        bound = lineData.size();
+        for (int i = 0; i < bound; i++) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            out.append(li.getString());
+        }
+
+        return out.toString();
+    }
+
+    public String toString() {
+        return getString();
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java
new file mode 100644
index 0000000..c9e08ec
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java
@@ -0,0 +1,729 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Contains static utilities for generating SMAP data based on the
+ * current version of Jasper.
+ * 
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ * @author Robert Field (inner SDEInstaller class)
+ * @author Mark Roth
+ * @author Kin-man Chung
+ */
+public class SmapUtil {
+
+    private org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SmapUtil.class );
+
+    //*********************************************************************
+    // Constants
+
+    public static final String SMAP_ENCODING = "UTF-8";
+
+    //*********************************************************************
+    // Public entry points
+
+    /**
+     * Generates an appropriate SMAP representing the current compilation
+     * context.  (JSR-045.)
+     *
+     * @param ctxt Current compilation context
+     * @param pageNodes The current JSP page
+     * @return a SMAP for the page
+     */
+    public static String[] generateSmap(
+        JspCompilationContext ctxt,
+        Node.Nodes pageNodes)
+        throws IOException {
+
+        // Scan the nodes for presence of Jasper generated inner classes
+        PreScanVisitor psVisitor = new PreScanVisitor();
+        try {
+            pageNodes.visit(psVisitor);
+        } catch (JasperException ex) {
+        }
+        HashMap map = psVisitor.getMap();
+
+        // set up our SMAP generator
+        SmapGenerator g = new SmapGenerator();
+        
+        /** Disable reading of input SMAP because:
+            1. There is a bug here: getRealPath() is null if .jsp is in a jar
+        	Bugzilla 14660.
+            2. Mappings from other sources into .jsp files are not supported.
+            TODO: fix 1. if 2. is not true.
+        // determine if we have an input SMAP
+        String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile()));
+            File inputSmap = new File(smapPath);
+            if (inputSmap.exists()) {
+                byte[] embeddedSmap = null;
+            byte[] subSmap = SDEInstaller.readWhole(inputSmap);
+            String subSmapString = new String(subSmap, SMAP_ENCODING);
+            g.addSmap(subSmapString, "JSP");
+        }
+        **/
+
+        // now, assemble info about our own stratum (JSP) using JspLineMap
+        SmapStratum s = new SmapStratum("JSP");
+
+        g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+
+        // Map out Node.Nodes
+        evaluateNodes(pageNodes, s, map, ctxt.getOptions().getMappedFile());
+        s.optimizeLineSection();
+        g.addStratum(s, true);
+
+        if (ctxt.getOptions().isSmapDumped()) {
+            File outSmap = new File(ctxt.getClassFileName() + ".smap");
+            PrintWriter so =
+                new PrintWriter(
+                    new OutputStreamWriter(
+                        new FileOutputStream(outSmap),
+                        SMAP_ENCODING));
+            so.print(g.getString());
+            so.close();
+        }
+
+        String classFileName = ctxt.getClassFileName();
+        int innerClassCount = map.size();
+        String [] smapInfo = new String[2 + innerClassCount*2];
+        smapInfo[0] = classFileName;
+        smapInfo[1] = g.getString();
+
+        int count = 2;
+        Iterator iter = map.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            String innerClass = (String) entry.getKey();
+            s = (SmapStratum) entry.getValue();
+            s.optimizeLineSection();
+            g = new SmapGenerator();
+            g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+            g.addStratum(s, true);
+
+            String innerClassFileName =
+                classFileName.substring(0, classFileName.indexOf(".class")) +
+                '$' + innerClass + ".class";
+            if (ctxt.getOptions().isSmapDumped()) {
+                File outSmap = new File(innerClassFileName + ".smap");
+                PrintWriter so =
+                    new PrintWriter(
+                        new OutputStreamWriter(
+                            new FileOutputStream(outSmap),
+                            SMAP_ENCODING));
+                so.print(g.getString());
+                so.close();
+            }
+            smapInfo[count] = innerClassFileName;
+            smapInfo[count+1] = g.getString();
+            count += 2;
+        }
+
+        return smapInfo;
+    }
+
+    public static void installSmap(String[] smap)
+        throws IOException {
+        if (smap == null) {
+            return;
+        }
+
+        for (int i = 0; i < smap.length; i += 2) {
+            File outServlet = new File(smap[i]);
+            SDEInstaller.install(outServlet, smap[i+1].getBytes());
+        }
+    }
+
+    //*********************************************************************
+    // Private utilities
+
+    /**
+     * Returns an unqualified version of the given file path.
+     */
+    private static String unqualify(String path) {
+        path = path.replace('\\', '/');
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+    /**
+     * Returns a file path corresponding to a potential SMAP input
+     * for the given compilation input (JSP file).
+     */
+    private static String inputSmapPath(String path) {
+        return path.substring(0, path.lastIndexOf('.') + 1) + "smap";
+    }
+
+    //*********************************************************************
+    // Installation logic (from Robert Field, JSR-045 spec lead)
+    private static class SDEInstaller {
+
+        private org.apache.juli.logging.Log log=
+            org.apache.juli.logging.LogFactory.getLog( SDEInstaller.class );
+
+        static final String nameSDE = "SourceDebugExtension";
+
+        byte[] orig;
+        byte[] sdeAttr;
+        byte[] gen;
+
+        int origPos = 0;
+        int genPos = 0;
+
+        int sdeIndex;
+
+        public static void main(String[] args) throws IOException {
+            if (args.length == 2) {
+                install(new File(args[0]), new File(args[1]));
+            } else if (args.length == 3) {
+                install(
+                    new File(args[0]),
+                    new File(args[1]),
+                    new File(args[2]));
+            } else {
+                System.err.println(
+                    "Usage: <command> <input class file> "
+                        + "<attribute file> <output class file name>\n"
+                        + "<command> <input/output class file> <attribute file>");
+            }
+        }
+
+        static void install(File inClassFile, File attrFile, File outClassFile)
+            throws IOException {
+            new SDEInstaller(inClassFile, attrFile, outClassFile);
+        }
+
+        static void install(File inOutClassFile, File attrFile)
+            throws IOException {
+            File tmpFile = new File(inOutClassFile.getPath() + "tmp");
+            new SDEInstaller(inOutClassFile, attrFile, tmpFile);
+            if (!inOutClassFile.delete()) {
+                throw new IOException("inOutClassFile.delete() failed");
+            }
+            if (!tmpFile.renameTo(inOutClassFile)) {
+                throw new IOException("tmpFile.renameTo(inOutClassFile) failed");
+            }
+        }
+
+        static void install(File classFile, byte[] smap) throws IOException {
+            File tmpFile = new File(classFile.getPath() + "tmp");
+            new SDEInstaller(classFile, smap, tmpFile);
+            if (!classFile.delete()) {
+                throw new IOException("classFile.delete() failed");
+            }
+            if (!tmpFile.renameTo(classFile)) {
+                throw new IOException("tmpFile.renameTo(classFile) failed");
+            }
+        }
+
+        SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
+            throws IOException {
+            if (!inClassFile.exists()) {
+                throw new FileNotFoundException("no such file: " + inClassFile);
+            }
+
+            this.sdeAttr = sdeAttr;
+            // get the bytes
+            orig = readWhole(inClassFile);
+            gen = new byte[orig.length + sdeAttr.length + 100];
+
+            // do it
+            addSDE();
+
+            // write result
+            FileOutputStream outStream = new FileOutputStream(outClassFile);
+            outStream.write(gen, 0, genPos);
+            outStream.close();
+        }
+
+        SDEInstaller(File inClassFile, File attrFile, File outClassFile)
+            throws IOException {
+            this(inClassFile, readWhole(attrFile), outClassFile);
+        }
+
+        static byte[] readWhole(File input) throws IOException {
+            FileInputStream inStream = new FileInputStream(input);
+            int len = (int)input.length();
+            byte[] bytes = new byte[len];
+            if (inStream.read(bytes, 0, len) != len) {
+                throw new IOException("expected size: " + len);
+            }
+            inStream.close();
+            return bytes;
+        }
+
+        void addSDE() throws UnsupportedEncodingException, IOException {
+            int i;
+            copy(4 + 2 + 2); // magic min/maj version
+            int constantPoolCountPos = genPos;
+            int constantPoolCount = readU2();
+            if (log.isDebugEnabled())
+                log.debug("constant pool count: " + constantPoolCount);
+            writeU2(constantPoolCount);
+
+            // copy old constant pool return index of SDE symbol, if found
+            sdeIndex = copyConstantPool(constantPoolCount);
+            if (sdeIndex < 0) {
+                // if "SourceDebugExtension" symbol not there add it
+                writeUtf8ForSDE();
+
+                // increment the countantPoolCount
+                sdeIndex = constantPoolCount;
+                ++constantPoolCount;
+                randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
+
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension not found, installed at: " + sdeIndex);
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension found at: " + sdeIndex);
+            }
+            copy(2 + 2 + 2); // access, this, super
+            int interfaceCount = readU2();
+            writeU2(interfaceCount);
+            if (log.isDebugEnabled())
+                log.debug("interfaceCount: " + interfaceCount);
+            copy(interfaceCount * 2);
+            copyMembers(); // fields
+            copyMembers(); // methods
+            int attrCountPos = genPos;
+            int attrCount = readU2();
+            writeU2(attrCount);
+            if (log.isDebugEnabled())
+                log.debug("class attrCount: " + attrCount);
+            // copy the class attributes, return true if SDE attr found (not copied)
+            if (!copyAttrs(attrCount)) {
+                // we will be adding SDE and it isn't already counted
+                ++attrCount;
+                randomAccessWriteU2(attrCountPos, attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("class attrCount incremented");
+            }
+            writeAttrForSDE(sdeIndex);
+        }
+
+        void copyMembers() {
+            int count = readU2();
+            writeU2(count);
+            if (log.isDebugEnabled())
+                log.debug("members count: " + count);
+            for (int i = 0; i < count; ++i) {
+                copy(6); // access, name, descriptor
+                int attrCount = readU2();
+                writeU2(attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("member attr count: " + attrCount);
+                copyAttrs(attrCount);
+            }
+        }
+
+        boolean copyAttrs(int attrCount) {
+            boolean sdeFound = false;
+            for (int i = 0; i < attrCount; ++i) {
+                int nameIndex = readU2();
+                // don't write old SDE
+                if (nameIndex == sdeIndex) {
+                    sdeFound = true;
+                    if (log.isDebugEnabled())
+                        log.debug("SDE attr found");
+                } else {
+                    writeU2(nameIndex); // name
+                    int len = readU4();
+                    writeU4(len);
+                    copy(len);
+                    if (log.isDebugEnabled())
+                        log.debug("attr len: " + len);
+                }
+            }
+            return sdeFound;
+        }
+
+        void writeAttrForSDE(int index) {
+            writeU2(index);
+            writeU4(sdeAttr.length);
+            for (int i = 0; i < sdeAttr.length; ++i) {
+                writeU1(sdeAttr[i]);
+            }
+        }
+
+        void randomAccessWriteU2(int pos, int val) {
+            int savePos = genPos;
+            genPos = pos;
+            writeU2(val);
+            genPos = savePos;
+        }
+
+        int readU1() {
+            return ((int)orig[origPos++]) & 0xFF;
+        }
+
+        int readU2() {
+            int res = readU1();
+            return (res << 8) + readU1();
+        }
+
+        int readU4() {
+            int res = readU2();
+            return (res << 16) + readU2();
+        }
+
+        void writeU1(int val) {
+            gen[genPos++] = (byte)val;
+        }
+
+        void writeU2(int val) {
+            writeU1(val >> 8);
+            writeU1(val & 0xFF);
+        }
+
+        void writeU4(int val) {
+            writeU2(val >> 16);
+            writeU2(val & 0xFFFF);
+        }
+
+        void copy(int count) {
+            for (int i = 0; i < count; ++i) {
+                gen[genPos++] = orig[origPos++];
+            }
+        }
+
+        byte[] readBytes(int count) {
+            byte[] bytes = new byte[count];
+            for (int i = 0; i < count; ++i) {
+                bytes[i] = orig[origPos++];
+            }
+            return bytes;
+        }
+
+        void writeBytes(byte[] bytes) {
+            for (int i = 0; i < bytes.length; ++i) {
+                gen[genPos++] = bytes[i];
+            }
+        }
+
+        int copyConstantPool(int constantPoolCount)
+            throws UnsupportedEncodingException, IOException {
+            int sdeIndex = -1;
+            // copy const pool index zero not in class file
+            for (int i = 1; i < constantPoolCount; ++i) {
+                int tag = readU1();
+                writeU1(tag);
+                switch (tag) {
+                    case 7 : // Class
+                    case 8 : // String
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 2 bytes");
+                        copy(2);
+                        break;
+                    case 9 : // Field
+                    case 10 : // Method
+                    case 11 : // InterfaceMethod
+                    case 3 : // Integer
+                    case 4 : // Float
+                    case 12 : // NameAndType
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 4 bytes");
+                        copy(4);
+                        break;
+                    case 5 : // Long
+                    case 6 : // Double
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 8 bytes");
+                        copy(8);
+                        i++;
+                        break;
+                    case 1 : // Utf8
+                        int len = readU2();
+                        writeU2(len);
+                        byte[] utf8 = readBytes(len);
+                        String str = new String(utf8, "UTF-8");
+                        if (log.isDebugEnabled())
+                            log.debug(i + " read class attr -- '" + str + "'");
+                        if (str.equals(nameSDE)) {
+                            sdeIndex = i;
+                        }
+                        writeBytes(utf8);
+                        break;
+                    default :
+                        throw new IOException("unexpected tag: " + tag);
+                }
+            }
+            return sdeIndex;
+        }
+
+        void writeUtf8ForSDE() {
+            int len = nameSDE.length();
+            writeU1(1); // Utf8 tag
+            writeU2(len);
+            for (int i = 0; i < len; ++i) {
+                writeU1(nameSDE.charAt(i));
+            }
+        }
+    }
+
+    public static void evaluateNodes(
+        Node.Nodes nodes,
+        SmapStratum s,
+        HashMap innerClassMap,
+        boolean breakAtLF) {
+        try {
+            nodes.visit(new SmapGenVisitor(s, breakAtLF, innerClassMap));
+        } catch (JasperException ex) {
+        }
+    }
+
+    static class SmapGenVisitor extends Node.Visitor {
+
+        private SmapStratum smap;
+        private boolean breakAtLF;
+        private HashMap innerClassMap;
+
+        SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap map) {
+            this.smap = s;
+            this.breakAtLF = breakAtLF;
+            this.innerClassMap = map;
+        }
+
+        public void visitBody(Node n) throws JasperException {
+            SmapStratum smapSave = smap;
+            String innerClass = n.getInnerClassName();
+            if (innerClass != null) {
+                this.smap = (SmapStratum) innerClassMap.get(innerClass);
+            }
+            super.visitBody(n);
+            smap = smapSave;
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.GetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.JspText n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            visitBody(n);
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.InvokeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.DoBodyAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            doSmap(n);
+        }
+
+        public void visit(Node.TemplateText n) throws JasperException {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            //Add the file information
+            String fileName = mark.getFile();
+            smap.addFile(unqualify(fileName), fileName);
+
+            //Add a LineInfo that corresponds to the beginning of this node
+            int iInputStartLine = mark.getLineNumber();
+            int iOutputStartLine = n.getBeginJavaLine();
+            int iOutputLineIncrement = breakAtLF? 1: 0;
+            smap.addLineData(iInputStartLine, fileName, 1, iOutputStartLine, 
+                             iOutputLineIncrement);
+
+            // Output additional mappings in the text
+            java.util.ArrayList extraSmap = n.getExtraSmap();
+
+            if (extraSmap != null) {
+                for (int i = 0; i < extraSmap.size(); i++) {
+                    iOutputStartLine += iOutputLineIncrement;
+                    smap.addLineData(
+                        iInputStartLine+((Integer)extraSmap.get(i)).intValue(),
+                        fileName,
+                        1,
+                        iOutputStartLine,
+                        iOutputLineIncrement);
+                }
+            }
+        }
+
+        private void doSmap(
+            Node n,
+            int inLineCount,
+            int outIncrement,
+            int skippedLines) {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            String unqualifiedName = unqualify(mark.getFile());
+            smap.addFile(unqualifiedName, mark.getFile());
+            smap.addLineData(
+                mark.getLineNumber() + skippedLines,
+                mark.getFile(),
+                inLineCount - skippedLines,
+                n.getBeginJavaLine() + skippedLines,
+                outIncrement);
+        }
+
+        private void doSmap(Node n) {
+            doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0);
+        }
+
+        private void doSmapText(Node n) {
+            String text = n.getText();
+            int index = 0;
+            int next = 0;
+            int lineCount = 1;
+            int skippedLines = 0;
+            boolean slashStarSeen = false;
+            boolean beginning = true;
+
+            // Count lines inside text, but skipping comment lines at the
+            // beginning of the text.
+            while ((next = text.indexOf('\n', index)) > -1) {
+                if (beginning) {
+                    String line = text.substring(index, next).trim();
+                    if (!slashStarSeen && line.startsWith("/*")) {
+                        slashStarSeen = true;
+                    }
+                    if (slashStarSeen) {
+                        skippedLines++;
+                        int endIndex = line.indexOf("*/");
+                        if (endIndex >= 0) {
+                            // End of /* */ comment
+                            slashStarSeen = false;
+                            if (endIndex < line.length() - 2) {
+                                // Some executable code after comment
+                                skippedLines--;
+                                beginning = false;
+                            }
+                        }
+                    } else if (line.length() == 0 || line.startsWith("//")) {
+                        skippedLines++;
+                    } else {
+                        beginning = false;
+                    }
+                }
+                lineCount++;
+                index = next + 1;
+            }
+
+            doSmap(n, lineCount, 1, skippedLines);
+        }
+    }
+
+    private static class PreScanVisitor extends Node.Visitor {
+
+        HashMap map = new HashMap();
+
+        public void doVisit(Node n) {
+            String inner = n.getInnerClassName();
+            if (inner != null && !map.containsKey(inner)) {
+                map.put(inner, new SmapStratum("JSP"));
+            }
+        }
+
+        HashMap getMap() {
+            return map;
+        }
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java
new file mode 100644
index 0000000..373eb0e
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java
@@ -0,0 +1,116 @@
+/*
+ * 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.jasper.compiler;
+
+public interface TagConstants {
+
+    public static final String JSP_URI = "http://java.sun.com/JSP/Page";
+
+    public static final String DIRECTIVE_ACTION = "directive.";
+
+    public static final String ROOT_ACTION = "root";
+    public static final String JSP_ROOT_ACTION = "jsp:root";
+
+    public static final String PAGE_DIRECTIVE_ACTION = "directive.page";
+    public static final String JSP_PAGE_DIRECTIVE_ACTION = "jsp:directive.page";
+
+    public static final String INCLUDE_DIRECTIVE_ACTION = "directive.include";
+    public static final String JSP_INCLUDE_DIRECTIVE_ACTION = "jsp:directive.include";
+
+    public static final String DECLARATION_ACTION = "declaration";
+    public static final String JSP_DECLARATION_ACTION = "jsp:declaration";
+
+    public static final String SCRIPTLET_ACTION = "scriptlet";
+    public static final String JSP_SCRIPTLET_ACTION = "jsp:scriptlet";
+
+    public static final String EXPRESSION_ACTION = "expression";
+    public static final String JSP_EXPRESSION_ACTION = "jsp:expression";
+
+    public static final String USE_BEAN_ACTION = "useBean";
+    public static final String JSP_USE_BEAN_ACTION = "jsp:useBean";
+
+    public static final String SET_PROPERTY_ACTION = "setProperty";
+    public static final String JSP_SET_PROPERTY_ACTION = "jsp:setProperty";
+
+    public static final String GET_PROPERTY_ACTION = "getProperty";
+    public static final String JSP_GET_PROPERTY_ACTION = "jsp:getProperty";
+
+    public static final String INCLUDE_ACTION = "include";
+    public static final String JSP_INCLUDE_ACTION = "jsp:include";
+
+    public static final String FORWARD_ACTION = "forward";
+    public static final String JSP_FORWARD_ACTION = "jsp:forward";
+
+    public static final String PARAM_ACTION = "param";
+    public static final String JSP_PARAM_ACTION = "jsp:param";
+
+    public static final String PARAMS_ACTION = "params";
+    public static final String JSP_PARAMS_ACTION = "jsp:params";
+
+    public static final String PLUGIN_ACTION = "plugin";
+    public static final String JSP_PLUGIN_ACTION = "jsp:plugin";
+
+    public static final String FALLBACK_ACTION = "fallback";
+    public static final String JSP_FALLBACK_ACTION = "jsp:fallback";
+
+    public static final String TEXT_ACTION = "text";
+    public static final String JSP_TEXT_ACTION = "jsp:text";
+    public static final String JSP_TEXT_ACTION_END = "</jsp:text>";
+
+    public static final String ATTRIBUTE_ACTION = "attribute";
+    public static final String JSP_ATTRIBUTE_ACTION = "jsp:attribute";
+
+    public static final String BODY_ACTION = "body";
+    public static final String JSP_BODY_ACTION = "jsp:body";
+
+    public static final String ELEMENT_ACTION = "element";
+    public static final String JSP_ELEMENT_ACTION = "jsp:element";
+
+    public static final String OUTPUT_ACTION = "output";
+    public static final String JSP_OUTPUT_ACTION = "jsp:output";
+
+    public static final String TAGLIB_DIRECTIVE_ACTION = "taglib";
+    public static final String JSP_TAGLIB_DIRECTIVE_ACTION = "jsp:taglib";
+
+    /*
+     * Tag Files
+     */
+    public static final String INVOKE_ACTION = "invoke";
+    public static final String JSP_INVOKE_ACTION = "jsp:invoke";
+
+    public static final String DOBODY_ACTION = "doBody";
+    public static final String JSP_DOBODY_ACTION = "jsp:doBody";
+
+    /*
+     * Tag File Directives
+     */
+    public static final String TAG_DIRECTIVE_ACTION = "directive.tag";
+    public static final String JSP_TAG_DIRECTIVE_ACTION = "jsp:directive.tag";
+
+    public static final String ATTRIBUTE_DIRECTIVE_ACTION = "directive.attribute";
+    public static final String JSP_ATTRIBUTE_DIRECTIVE_ACTION = "jsp:directive.attribute";
+
+    public static final String VARIABLE_DIRECTIVE_ACTION = "directive.variable";
+    public static final String JSP_VARIABLE_DIRECTIVE_ACTION = "jsp:directive.variable";
+
+    /*
+     * Directive attributes
+     */
+    public static final String URN_JSPTAGDIR = "urn:jsptagdir:";
+    public static final String URN_JSPTLD = "urn:jsptld:";
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java
new file mode 100644
index 0000000..d758bc0
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java
@@ -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.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.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.servlet.JspServletWrapper;
+import org.apache.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;
+            }
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java
new file mode 100644
index 0000000..7c0291d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java
@@ -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.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.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.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;
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java
new file mode 100644
index 0000000..e95d038
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java
@@ -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.jasper.compiler;
+
+import java.util.*;
+import java.io.*;
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.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;
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java
new file mode 100644
index 0000000..82df521
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java
@@ -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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+import org.apache.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();
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java
new file mode 100644
index 0000000..023bb0c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java
@@ -0,0 +1,549 @@
+/*
+ * 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.jasper.compiler;
+
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import org.xml.sax.InputSource;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A container for all tag libraries that are defined "globally"
+ * for the web application.
+ * 
+ * Tag Libraries can be defined globally in one of two ways:
+ *   1. Via <taglib> elements in web.xml:
+ *      the uri and location of the tag-library are specified in
+ *      the <taglib> element.
+ *   2. Via packaged jar files that contain .tld files
+ *      within the META-INF directory, or some subdirectory
+ *      of it. The taglib is 'global' if it has the <uri>
+ *      element defined.
+ *
+ * A mapping between the taglib URI and its associated TaglibraryInfoImpl
+ * is maintained in this container.
+ * Actually, that's what we'd like to do. However, because of the
+ * way the classes TagLibraryInfo and TagInfo have been defined,
+ * it is not currently possible to share an instance of TagLibraryInfo
+ * across page invocations. A bug has been submitted to the spec lead.
+ * In the mean time, all we do is save the 'location' where the
+ * TLD associated with a taglib URI can be found.
+ *
+ * When a JSP page has a taglib directive, the mappings in this container
+ * are first searched (see method getLocation()).
+ * If a mapping is found, then the location of the TLD is returned.
+ * If no mapping is found, then the uri specified
+ * in the taglib directive is to be interpreted as the location for
+ * the TLD of this tag library.
+ *
+ * @author Pierre Delisle
+ * @author Jan Luehe
+ */
+
+public class TldLocationsCache {
+
+    // Logger
+    private Log log = LogFactory.getLog(TldLocationsCache.class);
+
+    /**
+     * The types of URI one may specify for a tag library
+     */
+    public static final int ABS_URI = 0;
+    public static final int ROOT_REL_URI = 1;
+    public static final int NOROOT_REL_URI = 2;
+
+    private static final String WEB_XML = "/WEB-INF/web.xml";
+    private static final String FILE_PROTOCOL = "file:";
+    private static final String JAR_FILE_SUFFIX = ".jar";
+
+    // Names of JARs that are known not to contain any TLDs
+    private static HashSet<String> noTldJars;
+
+    /**
+     * The mapping of the 'global' tag library URI to the location (resource
+     * path) of the TLD associated with that tag library. The location is
+     * returned as a String array:
+     *    [0] The location
+     *    [1] If the location is a jar file, this is the location of the tld.
+     */
+    private Hashtable mappings;
+
+    private boolean initialized;
+    private ServletContext ctxt;
+    private boolean redeployMode;
+
+    //*********************************************************************
+    // Constructor and Initilizations
+
+    /*
+     * Initializes the set of JARs that are known not to contain any TLDs
+     */
+    static {
+        noTldJars = new HashSet<String>();
+        // Bootstrap JARs
+        noTldJars.add("bootstrap.jar");
+        noTldJars.add("commons-daemon.jar");
+        noTldJars.add("tomcat-juli.jar");
+        // Main JARs
+        noTldJars.add("annotations-api.jar");
+        noTldJars.add("catalina.jar");
+        noTldJars.add("catalina-ant.jar");
+        noTldJars.add("catalina-ha.jar");
+        noTldJars.add("catalina-tribes.jar");
+        noTldJars.add("el-api.jar");
+        noTldJars.add("jasper.jar");
+        noTldJars.add("jasper-el.jar");
+        noTldJars.add("jasper-jdt.jar");
+        noTldJars.add("jsp-api.jar");
+        noTldJars.add("servlet-api.jar");
+        noTldJars.add("tomcat-coyote.jar");
+        noTldJars.add("tomcat-dbcp.jar");
+        // i18n JARs
+        noTldJars.add("tomcat-i18n-en.jar");
+        noTldJars.add("tomcat-i18n-es.jar");
+        noTldJars.add("tomcat-i18n-fr.jar");
+        noTldJars.add("tomcat-i18n-ja.jar");
+        // Misc JARs not included with Tomcat
+        noTldJars.add("ant.jar");
+        noTldJars.add("commons-dbcp.jar");
+        noTldJars.add("commons-beanutils.jar");
+        noTldJars.add("commons-fileupload-1.0.jar");
+        noTldJars.add("commons-pool.jar");
+        noTldJars.add("commons-digester.jar");
+        noTldJars.add("commons-logging.jar");
+        noTldJars.add("commons-collections.jar");
+        noTldJars.add("jmx.jar");
+        noTldJars.add("jmx-tools.jar");
+        noTldJars.add("xercesImpl.jar");
+        noTldJars.add("xmlParserAPIs.jar");
+        noTldJars.add("xml-apis.jar");
+        // JARs from J2SE runtime
+        noTldJars.add("sunjce_provider.jar");
+        noTldJars.add("ldapsec.jar");
+        noTldJars.add("localedata.jar");
+        noTldJars.add("dnsns.jar");
+        noTldJars.add("tools.jar");
+        noTldJars.add("sunpkcs11.jar");
+    }
+    
+    public TldLocationsCache(ServletContext ctxt) {
+        this(ctxt, true);
+    }
+
+    /** Constructor. 
+     *
+     * @param ctxt the servlet context of the web application in which Jasper 
+     * is running
+     * @param redeployMode if true, then the compiler will allow redeploying 
+     * a tag library from the same jar, at the expense of slowing down the
+     * server a bit. Note that this may only work on JDK 1.3.1_01a and later,
+     * because of JDK bug 4211817 fixed in this release.
+     * If redeployMode is false, a faster but less capable mode will be used.
+     */
+    public TldLocationsCache(ServletContext ctxt, boolean redeployMode) {
+        this.ctxt = ctxt;
+        this.redeployMode = redeployMode;
+        mappings = new Hashtable();
+        initialized = false;
+    }
+
+    /**
+     * Sets the list of JARs that are known not to contain any TLDs.
+     *
+     * @param jarNames List of comma-separated names of JAR files that are 
+     * known not to contain any TLDs 
+     */
+    public static void setNoTldJars(String jarNames) {
+        if (jarNames != null) {
+            noTldJars.clear();
+            StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
+            while (tokenizer.hasMoreElements()) {
+                noTldJars.add(tokenizer.nextToken());
+            }
+        }
+    }
+
+    /**
+     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
+     *
+     * Returns null if the uri is not associated with any tag library 'exposed'
+     * in the web application. A tag library is 'exposed' either explicitly in
+     * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
+     * in a jar file (WEB-INF/lib).
+     * 
+     * @param uri The taglib uri
+     *
+     * @return An array of two Strings: The first element denotes the real
+     * 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.
+     * Returns null if the uri is not associated with any tag library 'exposed'
+     * in the web application.
+     */
+    public String[] getLocation(String uri) throws JasperException {
+        if (!initialized) {
+            init();
+        }
+        return (String[]) mappings.get(uri);
+    }
+
+    /** 
+     * Returns the type of a URI:
+     *     ABS_URI
+     *     ROOT_REL_URI
+     *     NOROOT_REL_URI
+     */
+    public static int uriType(String uri) {
+        if (uri.indexOf(':') != -1) {
+            return ABS_URI;
+        } else if (uri.startsWith("/")) {
+            return ROOT_REL_URI;
+        } else {
+            return NOROOT_REL_URI;
+        }
+    }
+
+    private void init() throws JasperException {
+        if (initialized) return;
+        try {
+            processWebDotXml();
+            scanJars();
+            processTldsInFileSystem("/WEB-INF/");
+            initialized = true;
+        } catch (Exception ex) {
+            throw new JasperException(Localizer.getMessage(
+                    "jsp.error.internal.tldinit", ex.getMessage()));
+        }
+    }
+
+    /*
+     * Populates taglib map described in web.xml.
+     */    
+    private void processWebDotXml() throws Exception {
+
+        InputStream is = null;
+
+        try {
+            // Acquire input stream to web application deployment descriptor
+            String altDDName = (String)ctxt.getAttribute(
+                                                    Constants.ALT_DD_ATTR);
+            URL uri = null;
+            if (altDDName != null) {
+                try {
+                    uri = new URL(FILE_PROTOCOL+altDDName.replace('\\', '/'));
+                } catch (MalformedURLException e) {
+                    if (log.isWarnEnabled()) {
+                        log.warn(Localizer.getMessage(
+                                            "jsp.error.internal.filenotfound",
+                                            altDDName));
+                    }
+                }
+            } else {
+                uri = ctxt.getResource(WEB_XML);
+                if (uri == null && log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                                            "jsp.error.internal.filenotfound",
+                                            WEB_XML));
+                }
+            }
+
+            if (uri == null) {
+                return;
+            }
+            is = uri.openStream();
+            InputSource ip = new InputSource(is);
+            ip.setSystemId(uri.toExternalForm()); 
+
+            // Parse the web application deployment descriptor
+            TreeNode webtld = null;
+            // altDDName is the absolute path of the DD
+            if (altDDName != null) {
+                webtld = new ParserUtils().parseXMLDocument(altDDName, ip);
+            } else {
+                webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip);
+            }
+
+            // Allow taglib to be an element of the root or jsp-config (JSP2.0)
+            TreeNode jspConfig = webtld.findChild("jsp-config");
+            if (jspConfig != null) {
+                webtld = jspConfig;
+            }
+            Iterator taglibs = webtld.findChildren("taglib");
+            while (taglibs.hasNext()) {
+
+                // Parse the next <taglib> element
+                TreeNode taglib = (TreeNode) taglibs.next();
+                String tagUri = null;
+                String tagLoc = null;
+                TreeNode child = taglib.findChild("taglib-uri");
+                if (child != null)
+                    tagUri = child.getBody();
+                child = taglib.findChild("taglib-location");
+                if (child != null)
+                    tagLoc = child.getBody();
+
+                // Save this location if appropriate
+                if (tagLoc == null)
+                    continue;
+                if (uriType(tagLoc) == NOROOT_REL_URI)
+                    tagLoc = "/WEB-INF/" + tagLoc;
+                String tagLoc2 = null;
+                if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
+                    tagLoc = ctxt.getResource(tagLoc).toString();
+                    tagLoc2 = "META-INF/taglib.tld";
+                }
+                mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
+            }
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (Throwable t) {}
+            }
+        }
+    }
+
+    /**
+     * Scans the given JarURLConnection for TLD files located in META-INF
+     * (or a subdirectory of it), adding an implicit map entry to the taglib
+     * map for any TLD that has a <uri> element.
+     *
+     * @param conn The JarURLConnection to the JAR file to scan
+     * @param ignore true if any exceptions raised when processing the given
+     * JAR should be ignored, false otherwise
+     */
+    private void scanJar(JarURLConnection conn, boolean ignore)
+                throws JasperException {
+
+        JarFile jarFile = null;
+        String resourcePath = conn.getJarFileURL().toString();
+        try {
+            if (redeployMode) {
+                conn.setUseCaches(false);
+            }
+            jarFile = conn.getJarFile();
+            Enumeration entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = (JarEntry) entries.nextElement();
+                String name = entry.getName();
+                if (!name.startsWith("META-INF/")) continue;
+                if (!name.endsWith(".tld")) continue;
+                InputStream stream = jarFile.getInputStream(entry);
+                try {
+                    String uri = getUriFromTld(resourcePath, stream);
+                    // Add implicit map entry only if its uri is not already
+                    // present in the map
+                    if (uri != null && mappings.get(uri) == null) {
+                        mappings.put(uri, new String[]{ resourcePath, name });
+                    }
+                } finally {
+                    if (stream != null) {
+                        try {
+                            stream.close();
+                        } catch (Throwable t) {
+                            // do nothing
+                        }
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            if (!redeployMode) {
+                // if not in redeploy mode, close the jar in case of an error
+                if (jarFile != null) {
+                    try {
+                        jarFile.close();
+                    } catch (Throwable t) {
+                        // ignore
+                    }
+                }
+            }
+            if (!ignore) {
+                throw new JasperException(ex);
+            }
+        } finally {
+            if (redeployMode) {
+                // if in redeploy mode, always close the jar
+                if (jarFile != null) {
+                    try {
+                        jarFile.close();
+                    } catch (Throwable t) {
+                        // ignore
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+     * Searches the filesystem under /WEB-INF for any TLD files, and adds
+     * an implicit map entry to the taglib map for any TLD that has a <uri>
+     * element.
+     */
+    private void processTldsInFileSystem(String startPath)
+            throws Exception {
+
+        Set dirList = ctxt.getResourcePaths(startPath);
+        if (dirList != null) {
+            Iterator it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = (String) it.next();
+                if (path.endsWith("/")) {
+                    processTldsInFileSystem(path);
+                }
+                if (!path.endsWith(".tld")) {
+                    continue;
+                }
+                InputStream stream = ctxt.getResourceAsStream(path);
+                String uri = null;
+                try {
+                    uri = getUriFromTld(path, stream);
+                } finally {
+                    if (stream != null) {
+                        try {
+                            stream.close();
+                        } catch (Throwable t) {
+                            // do nothing
+                        }
+                    }
+                }
+                // Add implicit map entry only if its uri is not already
+                // present in the map
+                if (uri != null && mappings.get(uri) == null) {
+                    mappings.put(uri, new String[] { path, null });
+                }
+            }
+        }
+    }
+
+    /*
+     * Returns the value of the uri element of the given TLD, or null if the
+     * given TLD does not contain any such element.
+     */
+    private String getUriFromTld(String resourcePath, InputStream in) 
+        throws JasperException
+    {
+        // Parse the tag library descriptor at the specified resource path
+        TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in);
+        TreeNode uri = tld.findChild("uri");
+        if (uri != null) {
+            String body = uri.getBody();
+            if (body != null)
+                return body;
+        }
+
+        return null;
+    }
+
+    /*
+     * Scans all JARs accessible to the webapp's classloader and its
+     * parent classloaders for TLDs.
+     * 
+     * The list of JARs always includes the JARs under WEB-INF/lib, as well as
+     * all shared JARs in the classloader delegation chain of the webapp's
+     * classloader.
+     *
+     * Considering JARs in the classloader delegation chain constitutes a
+     * Tomcat-specific extension to the TLD search
+     * order defined in the JSP spec. It allows tag libraries packaged as JAR
+     * files to be shared by web applications by simply dropping them in a 
+     * location that all web applications have access to (e.g.,
+     * <CATALINA_HOME>/common/lib).
+     *
+     * The set of shared JARs to be scanned for TLDs is narrowed down by
+     * the <tt>noTldJars</tt> class variable, which contains the names of JARs
+     * that are known not to contain any TLDs.
+     */
+    private void scanJars() throws Exception {
+
+        ClassLoader webappLoader
+            = Thread.currentThread().getContextClassLoader();
+        ClassLoader loader = webappLoader;
+
+        while (loader != null) {
+            if (loader instanceof URLClassLoader) {
+                URL[] urls = ((URLClassLoader) loader).getURLs();
+                for (int i=0; i<urls.length; i++) {
+                    URLConnection conn = urls[i].openConnection();
+                    if (conn instanceof JarURLConnection) {
+                        if (needScanJar(loader, webappLoader,
+                                        ((JarURLConnection) conn).getJarFile().getName())) {
+                            scanJar((JarURLConnection) conn, true);
+                        }
+                    } else {
+                        String urlStr = urls[i].toString();
+                        if (urlStr.startsWith(FILE_PROTOCOL)
+                                && urlStr.endsWith(JAR_FILE_SUFFIX)
+                                && needScanJar(loader, webappLoader, urlStr)) {
+                            URL jarURL = new URL("jar:" + urlStr + "!/");
+                            scanJar((JarURLConnection) jarURL.openConnection(),
+                                    true);
+                        }
+                    }
+                }
+            }
+
+            loader = loader.getParent();
+        }
+    }
+
+    /*
+     * Determines if the JAR file with the given <tt>jarPath</tt> needs to be
+     * scanned for TLDs.
+     *
+     * @param loader The current classloader in the parent chain
+     * @param webappLoader The webapp classloader
+     * @param jarPath The JAR file path
+     *
+     * @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be
+     * scanned for TLDs, FALSE otherwise
+     */
+    private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader,
+                                String jarPath) {
+        if (loader == webappLoader) {
+            // JARs under WEB-INF/lib must be scanned unconditionally according
+            // to the spec.
+            return true;
+        } else {
+            String jarName = jarPath;
+            int slash = jarPath.lastIndexOf('/');
+            if (slash >= 0) {
+                jarName = jarPath.substring(slash + 1);
+            }
+            return (!noTldJars.contains(jarName));
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Validator.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Validator.java
new file mode 100644
index 0000000..dd73aee
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Validator.java
@@ -0,0 +1,1799 @@
+/*
+ * 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.jasper.compiler;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.el.FunctionMapper;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.JspFragment;
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.ValidationMessage;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.el.ELContextImpl;
+import org.xml.sax.Attributes;
+
+/**
+ * Performs validation on the page elements. Attributes are checked for
+ * mandatory presence, entry value validity, and consistency. As a side effect,
+ * some page global value (such as those from page direcitves) are stored, for
+ * later use.
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+class Validator {
+
+    /**
+     * A visitor to validate and extract page directive info
+     */
+    static class DirectiveVisitor extends Node.Visitor {
+
+        private PageInfo pageInfo;
+
+        private ErrorDispatcher err;
+
+        private static final JspUtil.ValidAttribute[] pageDirectiveAttrs = {
+            new JspUtil.ValidAttribute("language"),
+            new JspUtil.ValidAttribute("extends"),
+            new JspUtil.ValidAttribute("import"),
+            new JspUtil.ValidAttribute("session"),
+            new JspUtil.ValidAttribute("buffer"),
+            new JspUtil.ValidAttribute("autoFlush"),
+            new JspUtil.ValidAttribute("isThreadSafe"),
+            new JspUtil.ValidAttribute("info"),
+            new JspUtil.ValidAttribute("errorPage"),
+            new JspUtil.ValidAttribute("isErrorPage"),
+            new JspUtil.ValidAttribute("contentType"),
+            new JspUtil.ValidAttribute("pageEncoding"),
+            new JspUtil.ValidAttribute("isELIgnored"),
+            new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"),
+            new JspUtil.ValidAttribute("trimDirectiveWhitespaces")
+        };
+
+        private boolean pageEncodingSeen = false;
+
+        /*
+         * Constructor
+         */
+        DirectiveVisitor(Compiler compiler) throws JasperException {
+            this.pageInfo = compiler.getPageInfo();
+            this.err = compiler.getErrorDispatcher();
+        }
+
+        public void visit(Node.IncludeDirective n) throws JasperException {
+            // Since pageDirectiveSeen flag only applies to the Current page
+            // save it here and restore it after the file is included.
+            boolean pageEncodingSeenSave = pageEncodingSeen;
+            pageEncodingSeen = false;
+            visitBody(n);
+            pageEncodingSeen = pageEncodingSeenSave;
+        }
+
+        public void visit(Node.PageDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs,
+                    err);
+
+            // JSP.2.10.1
+            Attributes attrs = n.getAttributes();
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                String attr = attrs.getQName(i);
+                String value = attrs.getValue(i);
+
+                if ("language".equals(attr)) {
+                    if (pageInfo.getLanguage(false) == null) {
+                        pageInfo.setLanguage(value, n, err, true);
+                    } else if (!pageInfo.getLanguage(false).equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.language",
+                                pageInfo.getLanguage(false), value);
+                    }
+                } else if ("extends".equals(attr)) {
+                    if (pageInfo.getExtends(false) == null) {
+                        pageInfo.setExtends(value, n);
+                    } else if (!pageInfo.getExtends(false).equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.extends",
+                                pageInfo.getExtends(false), value);
+                    }
+                } else if ("contentType".equals(attr)) {
+                    if (pageInfo.getContentType() == null) {
+                        pageInfo.setContentType(value);
+                    } else if (!pageInfo.getContentType().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.contenttype",
+                                pageInfo.getContentType(), value);
+                    }
+                } else if ("session".equals(attr)) {
+                    if (pageInfo.getSession() == null) {
+                        pageInfo.setSession(value, n, err);
+                    } else if (!pageInfo.getSession().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.session",
+                                pageInfo.getSession(), value);
+                    }
+                } else if ("buffer".equals(attr)) {
+                    if (pageInfo.getBufferValue() == null) {
+                        pageInfo.setBufferValue(value, n, err);
+                    } else if (!pageInfo.getBufferValue().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.buffer",
+                                pageInfo.getBufferValue(), value);
+                    }
+                } else if ("autoFlush".equals(attr)) {
+                    if (pageInfo.getAutoFlush() == null) {
+                        pageInfo.setAutoFlush(value, n, err);
+                    } else if (!pageInfo.getAutoFlush().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.autoflush",
+                                pageInfo.getAutoFlush(), value);
+                    }
+                } else if ("isThreadSafe".equals(attr)) {
+                    if (pageInfo.getIsThreadSafe() == null) {
+                        pageInfo.setIsThreadSafe(value, n, err);
+                    } else if (!pageInfo.getIsThreadSafe().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.isthreadsafe",
+                                pageInfo.getIsThreadSafe(), value);
+                    }
+                } else if ("isELIgnored".equals(attr)) {
+                    if (pageInfo.getIsELIgnored() == null) {
+                        pageInfo.setIsELIgnored(value, n, err, true);
+                    } else if (!pageInfo.getIsELIgnored().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.iselignored",
+                                pageInfo.getIsELIgnored(), value);
+                    }
+                } else if ("isErrorPage".equals(attr)) {
+                    if (pageInfo.getIsErrorPage() == null) {
+                        pageInfo.setIsErrorPage(value, n, err);
+                    } else if (!pageInfo.getIsErrorPage().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.iserrorpage",
+                                pageInfo.getIsErrorPage(), value);
+                    }
+                } else if ("errorPage".equals(attr)) {
+                    if (pageInfo.getErrorPage() == null) {
+                        pageInfo.setErrorPage(value);
+                    } else if (!pageInfo.getErrorPage().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.errorpage",
+                                pageInfo.getErrorPage(), value);
+                    }
+                } else if ("info".equals(attr)) {
+                    if (pageInfo.getInfo() == null) {
+                        pageInfo.setInfo(value);
+                    } else if (!pageInfo.getInfo().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.info",
+                                pageInfo.getInfo(), value);
+                    }
+                } else if ("pageEncoding".equals(attr)) {
+                    if (pageEncodingSeen)
+                        err.jspError(n, "jsp.error.page.multi.pageencoding");
+                    // 'pageEncoding' can occur at most once per file
+                    pageEncodingSeen = true;
+                    String actual = comparePageEncodings(value, n);
+                    n.getRoot().setPageEncoding(actual);
+                } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+                    if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+                        pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+                                err, true);
+                    } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+                            .equals(value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.page.conflict.deferredsyntaxallowedasliteral",
+                                        pageInfo
+                                                .getDeferredSyntaxAllowedAsLiteral(),
+                                        value);
+                    }
+                } else if ("trimDirectiveWhitespaces".equals(attr)) {
+                    if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+                        pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+                                true);
+                    } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+                            value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.page.conflict.trimdirectivewhitespaces",
+                                        pageInfo.getTrimDirectiveWhitespaces(),
+                                        value);
+                    }
+                }
+            }
+
+            // Check for bad combinations
+            if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush())
+                err.jspError(n, "jsp.error.page.badCombo");
+
+            // Attributes for imports for this node have been processed by
+            // the parsers, just add them to pageInfo.
+            pageInfo.addImports(n.getImports());
+        }
+
+        public void visit(Node.TagDirective n) throws JasperException {
+            // Note: Most of the validation is done in TagFileProcessor
+            // when it created a TagInfo object from the
+            // tag file in which the directive appeared.
+
+            // This method does additional processing to collect page info
+
+            Attributes attrs = n.getAttributes();
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                String attr = attrs.getQName(i);
+                String value = attrs.getValue(i);
+
+                if ("language".equals(attr)) {
+                    if (pageInfo.getLanguage(false) == null) {
+                        pageInfo.setLanguage(value, n, err, false);
+                    } else if (!pageInfo.getLanguage(false).equals(value)) {
+                        err.jspError(n, "jsp.error.tag.conflict.language",
+                                pageInfo.getLanguage(false), value);
+                    }
+                } else if ("isELIgnored".equals(attr)) {
+                    if (pageInfo.getIsELIgnored() == null) {
+                        pageInfo.setIsELIgnored(value, n, err, false);
+                    } else if (!pageInfo.getIsELIgnored().equals(value)) {
+                        err.jspError(n, "jsp.error.tag.conflict.iselignored",
+                                pageInfo.getIsELIgnored(), value);
+                    }
+                } else if ("pageEncoding".equals(attr)) {
+                    if (pageEncodingSeen)
+                        err.jspError(n, "jsp.error.tag.multi.pageencoding");
+                    pageEncodingSeen = true;
+                    compareTagEncodings(value, n);
+                    n.getRoot().setPageEncoding(value);
+                } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+                    if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+                        pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+                                err, false);
+                    } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+                            .equals(value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.tag.conflict.deferredsyntaxallowedasliteral",
+                                        pageInfo
+                                                .getDeferredSyntaxAllowedAsLiteral(),
+                                        value);
+                    }
+                } else if ("trimDirectiveWhitespaces".equals(attr)) {
+                    if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+                        pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+                                false);
+                    } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+                            value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.tag.conflict.trimdirectivewhitespaces",
+                                        pageInfo.getTrimDirectiveWhitespaces(),
+                                        value);
+                    }
+                }
+            }
+
+            // Attributes for imports for this node have been processed by
+            // the parsers, just add them to pageInfo.
+            pageInfo.addImports(n.getImports());
+        }
+
+        public void visit(Node.AttributeDirective n) throws JasperException {
+            // Do nothing, since this attribute directive has already been
+            // validated by TagFileProcessor when it created a TagInfo object
+            // from the tag file in which the directive appeared
+        }
+
+        public void visit(Node.VariableDirective n) throws JasperException {
+            // Do nothing, since this variable directive has already been
+            // validated by TagFileProcessor when it created a TagInfo object
+            // from the tag file in which the directive appeared
+        }
+
+        /*
+         * Compares page encodings specified in various places, and throws
+         * exception in case of page encoding mismatch.
+         * 
+         * @param pageDirEnc The value of the pageEncoding attribute of the page
+         * directive @param pageDir The page directive node
+         * 
+         * @throws JasperException in case of page encoding mismatch
+         */
+        private String comparePageEncodings(String thePageDirEnc,
+                Node.PageDirective pageDir) throws JasperException {
+
+            Node.Root root = pageDir.getRoot();
+            String configEnc = root.getJspConfigPageEncoding();
+            String pageDirEnc = thePageDirEnc.toUpperCase();
+
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the JSP config element whose URL
+             * pattern matches this page. Treat "UTF-16", "UTF-16BE", and
+             * "UTF-16LE" as identical.
+             */
+            if (configEnc != null) {
+                configEnc = configEnc.toUpperCase();
+                if (!pageDirEnc.equals(configEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !configEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.config_pagedir_encoding_mismatch",
+                            configEnc, pageDirEnc);
+                } else {
+                    return configEnc;
+                }
+            }
+
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the XML prolog (only for XML syntax,
+             * and only if JSP document contains XML prolog with encoding
+             * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+                String pageEnc = root.getPageEncoding().toUpperCase();
+                if (!pageDirEnc.equals(pageEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.prolog_pagedir_encoding_mismatch",
+                            pageEnc, pageDirEnc);
+                } else {
+                    return pageEnc;
+                }
+            }
+            
+            return pageDirEnc;
+        }
+        
+        /*
+         * Compares page encodings specified in various places, and throws
+         * exception in case of page encoding mismatch.
+         * 
+         * @param thePageDirEnc The value of the pageEncoding attribute of the page
+         * directive @param pageDir The page directive node
+         * 
+         * @throws JasperException in case of page encoding mismatch
+         */
+        private void compareTagEncodings(String thePageDirEnc,
+                Node.TagDirective pageDir) throws JasperException {
+
+            Node.Root root = pageDir.getRoot();
+            String pageDirEnc = thePageDirEnc.toUpperCase();
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the XML prolog (only for XML syntax,
+             * and only if JSP document contains XML prolog with encoding
+             * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+                String pageEnc = root.getPageEncoding().toUpperCase();
+                if (!pageDirEnc.equals(pageEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.prolog_pagedir_encoding_mismatch",
+                            pageEnc, pageDirEnc);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * A visitor for validating nodes other than page directives
+     */
+    static class ValidateVisitor extends Node.Visitor {
+
+        private PageInfo pageInfo;
+
+        private ErrorDispatcher err;
+
+        private TagInfo tagInfo;
+
+        private ClassLoader loader;
+
+        private final StringBuffer buf = new StringBuffer(32);
+
+        private static final JspUtil.ValidAttribute[] jspRootAttrs = {
+                new JspUtil.ValidAttribute("xsi:schemaLocation"),
+                new JspUtil.ValidAttribute("version", true) };
+
+        private static final JspUtil.ValidAttribute[] includeDirectiveAttrs = { new JspUtil.ValidAttribute(
+                "file", true) };
+
+        private static final JspUtil.ValidAttribute[] taglibDirectiveAttrs = {
+                new JspUtil.ValidAttribute("uri"),
+                new JspUtil.ValidAttribute("tagdir"),
+                new JspUtil.ValidAttribute("prefix", true) };
+
+        private static final JspUtil.ValidAttribute[] includeActionAttrs = {
+                new JspUtil.ValidAttribute("page", true, true),
+                new JspUtil.ValidAttribute("flush") };
+
+        private static final JspUtil.ValidAttribute[] paramActionAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("value", true, true) };
+
+        private static final JspUtil.ValidAttribute[] forwardActionAttrs = { new JspUtil.ValidAttribute(
+                "page", true, true) };
+
+        private static final JspUtil.ValidAttribute[] getPropertyAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("property", true) };
+
+        private static final JspUtil.ValidAttribute[] setPropertyAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("property", true),
+                new JspUtil.ValidAttribute("value", false, true),
+                new JspUtil.ValidAttribute("param") };
+
+        private static final JspUtil.ValidAttribute[] useBeanAttrs = {
+                new JspUtil.ValidAttribute("id", true),
+                new JspUtil.ValidAttribute("scope"),
+                new JspUtil.ValidAttribute("class"),
+                new JspUtil.ValidAttribute("type"),
+                new JspUtil.ValidAttribute("beanName", false, true) };
+
+        private static final JspUtil.ValidAttribute[] plugInAttrs = {
+                new JspUtil.ValidAttribute("type", true),
+                new JspUtil.ValidAttribute("code", true),
+                new JspUtil.ValidAttribute("codebase"),
+                new JspUtil.ValidAttribute("align"),
+                new JspUtil.ValidAttribute("archive"),
+                new JspUtil.ValidAttribute("height", false, true),
+                new JspUtil.ValidAttribute("hspace"),
+                new JspUtil.ValidAttribute("jreversion"),
+                new JspUtil.ValidAttribute("name"),
+                new JspUtil.ValidAttribute("vspace"),
+                new JspUtil.ValidAttribute("width", false, true),
+                new JspUtil.ValidAttribute("nspluginurl"),
+                new JspUtil.ValidAttribute("iepluginurl") };
+
+        private static final JspUtil.ValidAttribute[] attributeAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("trim") };
+
+        private static final JspUtil.ValidAttribute[] invokeAttrs = {
+                new JspUtil.ValidAttribute("fragment", true),
+                new JspUtil.ValidAttribute("var"),
+                new JspUtil.ValidAttribute("varReader"),
+                new JspUtil.ValidAttribute("scope") };
+
+        private static final JspUtil.ValidAttribute[] doBodyAttrs = {
+                new JspUtil.ValidAttribute("var"),
+                new JspUtil.ValidAttribute("varReader"),
+                new JspUtil.ValidAttribute("scope") };
+
+        private static final JspUtil.ValidAttribute[] jspOutputAttrs = {
+                new JspUtil.ValidAttribute("omit-xml-declaration"),
+                new JspUtil.ValidAttribute("doctype-root-element"),
+                new JspUtil.ValidAttribute("doctype-public"),
+                new JspUtil.ValidAttribute("doctype-system") };
+
+        /*
+         * Constructor
+         */
+        ValidateVisitor(Compiler compiler) {
+            this.pageInfo = compiler.getPageInfo();
+            this.err = compiler.getErrorDispatcher();
+            this.tagInfo = compiler.getCompilationContext().getTagInfo();
+            this.loader = compiler.getCompilationContext().getClassLoader();
+        }
+
+        public void visit(Node.JspRoot n) throws JasperException {
+            JspUtil.checkAttributes("Jsp:root", n, jspRootAttrs, err);
+            String version = n.getTextAttribute("version");
+            if (!version.equals("1.2") && !version.equals("2.0") && !version.equals("2.1")) {
+                err.jspError(n, "jsp.error.jsproot.version.invalid", version);
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.IncludeDirective n) throws JasperException {
+            JspUtil.checkAttributes("Include directive", n,
+                    includeDirectiveAttrs, err);
+            visitBody(n);
+        }
+
+        public void visit(Node.TaglibDirective n) throws JasperException {
+            JspUtil.checkAttributes("Taglib directive", n,
+                    taglibDirectiveAttrs, err);
+            // Either 'uri' or 'tagdir' attribute must be specified
+            String uri = n.getAttributeValue("uri");
+            String tagdir = n.getAttributeValue("tagdir");
+            if (uri == null && tagdir == null) {
+                err.jspError(n, "jsp.error.taglibDirective.missing.location");
+            }
+            if (uri != null && tagdir != null) {
+                err
+                        .jspError(n,
+                                "jsp.error.taglibDirective.both_uri_and_tagdir");
+            }
+        }
+
+        public void visit(Node.ParamAction n) throws JasperException {
+            JspUtil.checkAttributes("Param action", n, paramActionAttrs, err);
+            // make sure the value of the 'name' attribute is not a
+            // request-time expression
+            throwErrorIfExpression(n, "name", "jsp:param");
+            n.setValue(getJspAttribute(null, "value", null, null, n
+                    .getAttributeValue("value"), java.lang.String.class, n,
+                    false));
+            visitBody(n);
+        }
+
+        public void visit(Node.ParamsAction n) throws JasperException {
+            // Make sure we've got at least one nested jsp:param
+            Node.Nodes subElems = n.getBody();
+            if (subElems == null) {
+                err.jspError(n, "jsp.error.params.emptyBody");
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            JspUtil.checkAttributes("Include action", n, includeActionAttrs,
+                    err);
+            n.setPage(getJspAttribute(null, "page", null, null, n
+                    .getAttributeValue("page"), java.lang.String.class, n,
+                    false));
+            visitBody(n);
+        };
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err);
+            n.setPage(getJspAttribute(null, "page", null, null, n
+                    .getAttributeValue("page"), java.lang.String.class, n,
+                    false));
+            visitBody(n);
+        }
+
+        public void visit(Node.GetProperty n) throws JasperException {
+            JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            JspUtil.checkAttributes("SetProperty", n, setPropertyAttrs, err);
+            String property = n.getTextAttribute("property");
+            String param = n.getTextAttribute("param");
+            String value = n.getAttributeValue("value");
+
+            n.setValue(getJspAttribute(null, "value", null, null, value,
+                    java.lang.Object.class, n, false));
+
+            boolean valueSpecified = n.getValue() != null;
+
+            if ("*".equals(property)) {
+                if (param != null || valueSpecified)
+                    err.jspError(n, "jsp.error.setProperty.invalid");
+
+            } else if (param != null && valueSpecified) {
+                err.jspError(n, "jsp.error.setProperty.invalid");
+            }
+
+            visitBody(n);
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            JspUtil.checkAttributes("UseBean", n, useBeanAttrs, err);
+
+            String name = n.getTextAttribute("id");
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+            String className = n.getTextAttribute("class");
+            String type = n.getTextAttribute("type");
+            BeanRepository beanInfo = pageInfo.getBeanRepository();
+
+            if (className == null && type == null)
+                err.jspError(n, "jsp.error.usebean.missingType");
+
+            if (beanInfo.checkVariable(name))
+                err.jspError(n, "jsp.error.usebean.duplicate");
+
+            if ("session".equals(scope) && !pageInfo.isSession())
+                err.jspError(n, "jsp.error.usebean.noSession");
+
+            Node.JspAttribute jattr = getJspAttribute(null, "beanName", null,
+                    null, n.getAttributeValue("beanName"),
+                    java.lang.String.class, n, false);
+            n.setBeanName(jattr);
+            if (className != null && jattr != null)
+                err.jspError(n, "jsp.error.usebean.notBoth");
+
+            if (className == null)
+                className = type;
+
+            beanInfo.addBean(n, name, className, scope);
+
+            visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            JspUtil.checkAttributes("Plugin", n, plugInAttrs, err);
+
+            throwErrorIfExpression(n, "type", "jsp:plugin");
+            throwErrorIfExpression(n, "code", "jsp:plugin");
+            throwErrorIfExpression(n, "codebase", "jsp:plugin");
+            throwErrorIfExpression(n, "align", "jsp:plugin");
+            throwErrorIfExpression(n, "archive", "jsp:plugin");
+            throwErrorIfExpression(n, "hspace", "jsp:plugin");
+            throwErrorIfExpression(n, "jreversion", "jsp:plugin");
+            throwErrorIfExpression(n, "name", "jsp:plugin");
+            throwErrorIfExpression(n, "vspace", "jsp:plugin");
+            throwErrorIfExpression(n, "nspluginurl", "jsp:plugin");
+            throwErrorIfExpression(n, "iepluginurl", "jsp:plugin");
+
+            String type = n.getTextAttribute("type");
+            if (type == null)
+                err.jspError(n, "jsp.error.plugin.notype");
+            if (!type.equals("bean") && !type.equals("applet"))
+                err.jspError(n, "jsp.error.plugin.badtype");
+            if (n.getTextAttribute("code") == null)
+                err.jspError(n, "jsp.error.plugin.nocode");
+
+            Node.JspAttribute width = getJspAttribute(null, "width", null,
+                    null, n.getAttributeValue("width"), java.lang.String.class,
+                    n, false);
+            n.setWidth(width);
+
+            Node.JspAttribute height = getJspAttribute(null, "height", null,
+                    null, n.getAttributeValue("height"),
+                    java.lang.String.class, n, false);
+            n.setHeight(height);
+
+            visitBody(n);
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            JspUtil.checkAttributes("Attribute", n, attributeAttrs, err);
+            visitBody(n);
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            visitBody(n);
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            // exit if we are ignoring EL all together
+            if (pageInfo.isELIgnored())
+                return;
+
+            // JSP.2.2 - '#{' not allowed in template text
+            if (n.getType() == '#') {
+                if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()
+                        && (tagInfo == null 
+                                || ((tagInfo != null) && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
+                                        || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"))))) {
+                    err.jspError(n, "jsp.error.el.template.deferred");
+                } else {
+                    return;
+                }
+            }
+
+            // build expression
+            StringBuffer expr = this.getBuffer();
+            expr.append(n.getType()).append('{').append(n.getText())
+                    .append('}');
+            ELNode.Nodes el = ELParser.parse(expr.toString());
+
+            // validate/prepare expression
+            prepareExpression(el, n, expr.toString());
+
+            // store it
+            n.setEL(el);
+        }
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            if (n.getNamedAttributeNodes().size() != 0) {
+                err.jspError(n, "jsp.error.namedAttribute.invalidUse");
+            }
+
+            Attributes attrs = n.getAttributes();
+            if (attrs != null) {
+                int attrSize = attrs.getLength();
+                Node.JspAttribute[] jspAttrs = new Node.JspAttribute[attrSize];
+                for (int i = 0; i < attrSize; i++) {
+                    jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+                            attrs.getURI(i), attrs.getLocalName(i), attrs
+                                    .getValue(i), java.lang.Object.class, n,
+                            false);
+                }
+                n.setJspAttributes(jspAttrs);
+            }
+
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+
+            /*
+             * The bodyconet of a SimpleTag cannot be JSP.
+             */
+            if (n.implementsSimpleTag()
+                    && tagInfo.getBodyContent().equalsIgnoreCase(
+                            TagInfo.BODY_CONTENT_JSP)) {
+                err.jspError(n, "jsp.error.simpletag.badbodycontent", tagInfo
+                        .getTagClassName());
+            }
+
+            /*
+             * If the tag handler declares in the TLD that it supports dynamic
+             * attributes, it also must implement the DynamicAttributes
+             * interface.
+             */
+            if (tagInfo.hasDynamicAttributes()
+                    && !n.implementsDynamicAttributes()) {
+                err.jspError(n, "jsp.error.dynamic.attributes.not.implemented",
+                        n.getQName());
+            }
+
+            /*
+             * Make sure all required attributes are present, either as
+             * attributes or named attributes (<jsp:attribute>). Also make sure
+             * that the same attribute is not specified in both attributes or
+             * named attributes.
+             */
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            String customActionUri = n.getURI();
+            Attributes attrs = n.getAttributes();
+            int attrsSize = (attrs == null) ? 0 : attrs.getLength();
+            for (int i = 0; i < tldAttrs.length; i++) {
+                String attr = null;
+                if (attrs != null) {
+                    attr = attrs.getValue(tldAttrs[i].getName());
+                    if (attr == null) {
+                        attr = attrs.getValue(customActionUri, tldAttrs[i]
+                                .getName());
+                    }
+                }
+                Node.NamedAttribute na = n.getNamedAttributeNode(tldAttrs[i]
+                        .getName());
+
+                if (tldAttrs[i].isRequired() && attr == null && na == null) {
+                    err.jspError(n, "jsp.error.missing_attribute", tldAttrs[i]
+                            .getName(), n.getLocalName());
+                }
+                if (attr != null && na != null) {
+                    err.jspError(n, "jsp.error.duplicate.name.jspattribute",
+                            tldAttrs[i].getName());
+                }
+            }
+
+            Node.Nodes naNodes = n.getNamedAttributeNodes();
+            int jspAttrsSize = naNodes.size() + attrsSize;
+            Node.JspAttribute[] jspAttrs = null;
+            if (jspAttrsSize > 0) {
+                jspAttrs = new Node.JspAttribute[jspAttrsSize];
+            }
+            Hashtable<String, Object> tagDataAttrs = new Hashtable<String, Object>(attrsSize);
+
+            checkXmlAttributes(n, jspAttrs, tagDataAttrs);
+            checkNamedAttributes(n, jspAttrs, attrsSize, tagDataAttrs);
+
+            TagData tagData = new TagData(tagDataAttrs);
+
+            // JSP.C1: It is a (translation time) error for an action that
+            // has one or more variable subelements to have a TagExtraInfo
+            // class that returns a non-null object.
+            TagExtraInfo tei = tagInfo.getTagExtraInfo();
+            if (tei != null && tei.getVariableInfo(tagData) != null
+                    && tei.getVariableInfo(tagData).length > 0
+                    && tagInfo.getTagVariableInfos().length > 0) {
+                err.jspError("jsp.error.non_null_tei_and_var_subelems", n
+                        .getQName());
+            }
+
+            n.setTagData(tagData);
+            n.setJspAttributes(jspAttrs);
+
+            visitBody(n);
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+
+            Attributes attrs = n.getAttributes();
+            if (attrs == null) {
+                err.jspError(n, "jsp.error.jspelement.missing.name");
+            }
+            int xmlAttrLen = attrs.getLength();
+
+            Node.Nodes namedAttrs = n.getNamedAttributeNodes();
+
+            // XML-style 'name' attribute, which is mandatory, must not be
+            // included in JspAttribute array
+            int jspAttrSize = xmlAttrLen - 1 + namedAttrs.size();
+
+            Node.JspAttribute[] jspAttrs = new Node.JspAttribute[jspAttrSize];
+            int jspAttrIndex = 0;
+
+            // Process XML-style attributes
+            for (int i = 0; i < xmlAttrLen; i++) {
+                if ("name".equals(attrs.getLocalName(i))) {
+                    n.setNameAttribute(getJspAttribute(null, attrs.getQName(i),
+                            attrs.getURI(i), attrs.getLocalName(i), attrs
+                                    .getValue(i), java.lang.String.class, n,
+                            false));
+                } else {
+                    if (jspAttrIndex < jspAttrSize) {
+                        jspAttrs[jspAttrIndex++] = getJspAttribute(null, attrs
+                                .getQName(i), attrs.getURI(i), attrs
+                                .getLocalName(i), attrs.getValue(i),
+                                java.lang.Object.class, n, false);
+                    }
+                }
+            }
+            if (n.getNameAttribute() == null) {
+                err.jspError(n, "jsp.error.jspelement.missing.name");
+            }
+
+            // Process named attributes
+            for (int i = 0; i < namedAttrs.size(); i++) {
+                Node.NamedAttribute na = (Node.NamedAttribute) namedAttrs
+                        .getNode(i);
+                jspAttrs[jspAttrIndex++] = new Node.JspAttribute(na, null,
+                        false);
+            }
+
+            n.setJspAttributes(jspAttrs);
+
+            visitBody(n);
+        }
+
+        public void visit(Node.JspOutput n) throws JasperException {
+            JspUtil.checkAttributes("jsp:output", n, jspOutputAttrs, err);
+
+            if (n.getBody() != null) {
+                err.jspError(n, "jsp.error.jspoutput.nonemptybody");
+            }
+
+            String omitXmlDecl = n.getAttributeValue("omit-xml-declaration");
+            String doctypeName = n.getAttributeValue("doctype-root-element");
+            String doctypePublic = n.getAttributeValue("doctype-public");
+            String doctypeSystem = n.getAttributeValue("doctype-system");
+
+            String omitXmlDeclOld = pageInfo.getOmitXmlDecl();
+            String doctypeNameOld = pageInfo.getDoctypeName();
+            String doctypePublicOld = pageInfo.getDoctypePublic();
+            String doctypeSystemOld = pageInfo.getDoctypeSystem();
+
+            if (omitXmlDecl != null && omitXmlDeclOld != null
+                    && !omitXmlDecl.equals(omitXmlDeclOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl);
+            }
+
+            if (doctypeName != null && doctypeNameOld != null
+                    && !doctypeName.equals(doctypeNameOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-root-element", doctypeNameOld, doctypeName);
+            }
+
+            if (doctypePublic != null && doctypePublicOld != null
+                    && !doctypePublic.equals(doctypePublicOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-public", doctypePublicOld, doctypePublic);
+            }
+
+            if (doctypeSystem != null && doctypeSystemOld != null
+                    && !doctypeSystem.equals(doctypeSystemOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-system", doctypeSystemOld, doctypeSystem);
+            }
+
+            if (doctypeName == null && doctypeSystem != null
+                    || doctypeName != null && doctypeSystem == null) {
+                err.jspError(n, "jsp.error.jspoutput.doctypenamesystem");
+            }
+
+            if (doctypePublic != null && doctypeSystem == null) {
+                err.jspError(n, "jsp.error.jspoutput.doctypepulicsystem");
+            }
+
+            if (omitXmlDecl != null) {
+                pageInfo.setOmitXmlDecl(omitXmlDecl);
+            }
+            if (doctypeName != null) {
+                pageInfo.setDoctypeName(doctypeName);
+            }
+            if (doctypeSystem != null) {
+                pageInfo.setDoctypeSystem(doctypeSystem);
+            }
+            if (doctypePublic != null) {
+                pageInfo.setDoctypePublic(doctypePublic);
+            }
+        }
+
+        public void visit(Node.InvokeAction n) throws JasperException {
+
+            JspUtil.checkAttributes("Invoke", n, invokeAttrs, err);
+
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+
+            String var = n.getTextAttribute("var");
+            String varReader = n.getTextAttribute("varReader");
+            if (scope != null && var == null && varReader == null) {
+                err.jspError(n, "jsp.error.missing_var_or_varReader");
+            }
+            if (var != null && varReader != null) {
+                err.jspError(n, "jsp.error.var_and_varReader");
+            }
+        }
+
+        public void visit(Node.DoBodyAction n) throws JasperException {
+
+            JspUtil.checkAttributes("DoBody", n, doBodyAttrs, err);
+
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+
+            String var = n.getTextAttribute("var");
+            String varReader = n.getTextAttribute("varReader");
+            if (scope != null && var == null && varReader == null) {
+                err.jspError(n, "jsp.error.missing_var_or_varReader");
+            }
+            if (var != null && varReader != null) {
+                err.jspError(n, "jsp.error.var_and_varReader");
+            }
+        }
+
+        /*
+         * Make sure the given custom action does not have any invalid
+         * attributes.
+         * 
+         * A custom action and its declared attributes always belong to the same
+         * namespace, which is identified by the prefix name of the custom tag
+         * invocation. For example, in this invocation:
+         * 
+         * <my:test a="1" b="2" c="3"/>, the action
+         * 
+         * "test" and its attributes "a", "b", and "c" all belong to the
+         * namespace identified by the prefix "my". The above invocation would
+         * be equivalent to:
+         * 
+         * <my:test my:a="1" my:b="2" my:c="3"/>
+         * 
+         * An action attribute may have a prefix different from that of the
+         * action invocation only if the underlying tag handler supports dynamic
+         * attributes, in which case the attribute with the different prefix is
+         * considered a dynamic attribute.
+         */
+        private void checkXmlAttributes(Node.CustomTag n,
+                Node.JspAttribute[] jspAttrs, Hashtable<String, Object> tagDataAttrs)
+                throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            Attributes attrs = n.getAttributes();
+
+            boolean checkDeferred = !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+                && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
+                        || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"));
+
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                boolean found = false;
+                
+                boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("%="))
+                        || (!n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("<%=")));
+                boolean elExpression = false;
+                boolean deferred = false;
+                boolean deferredValueIsLiteral = false;
+
+                ELNode.Nodes el = null;
+                if (!runtimeExpression) {
+                    el = ELParser.parse(attrs.getValue(i));
+                    Iterator<ELNode> nodes = el.iterator();
+                    while (nodes.hasNext()) {
+                        ELNode node = nodes.next();
+                        if (node instanceof ELNode.Root) {
+                            if (((ELNode.Root) node).getType() == '$') {
+                                elExpression = true;
+                            } else if (checkDeferred && ((ELNode.Root) node).getType() == '#') {
+                                elExpression = true;
+                                deferred = true;
+                                if (pageInfo.isELIgnored()) {
+                                    deferredValueIsLiteral = true;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                boolean expression = runtimeExpression 
+                    || (elExpression  && (!pageInfo.isELIgnored() || (!"true".equalsIgnoreCase(pageInfo.getIsELIgnored()) && checkDeferred && deferred)));
+                
+                for (int j = 0; tldAttrs != null && j < tldAttrs.length; j++) {
+                    if (attrs.getLocalName(i).equals(tldAttrs[j].getName())
+                            && (attrs.getURI(i) == null
+                                    || attrs.getURI(i).length() == 0 || attrs
+                                    .getURI(i).equals(n.getURI()))) {
+                        
+                        if (tldAttrs[j].canBeRequestTime()
+                                || tldAttrs[j].isDeferredMethod() || tldAttrs[j].isDeferredValue()) { // JSP 2.1
+                            
+                            if (!expression) {
+                                
+                                if (deferredValueIsLiteral && !pageInfo.isDeferredSyntaxAllowedAsLiteral()) {
+                                    err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                            tldAttrs[j].getName());
+                                }
+                                
+                                String expectedType = null;
+                                if (tldAttrs[j].isDeferredMethod()) {
+                                    // The String litteral must be castable to what is declared as type
+                                    // for the attribute
+                                    String m = tldAttrs[j].getMethodSignature();
+                                    if (m != null) {
+                                        int rti = m.trim().indexOf(' ');
+                                        if (rti > 0) {
+                                            expectedType = m.substring(0, rti).trim();
+                                        }
+                                    } else {
+                                        expectedType = "java.lang.Object";
+                                    }
+                                }
+                                if (tldAttrs[j].isDeferredValue()) {
+                                    // The String litteral must be castable to what is declared as type
+                                    // for the attribute
+                                    expectedType = tldAttrs[j].getExpectedTypeName();
+                                }
+                                if (expectedType != null) {
+                                    Class expectedClass = String.class;
+                                    try {
+                                        expectedClass = JspUtil.toClass(expectedType, loader);
+                                    } catch (ClassNotFoundException e) {
+                                        err.jspError
+                                            (n, "jsp.error.unknown_attribute_type",
+                                             tldAttrs[j].getName(), expectedType);
+                                    }
+                                    // Check casting
+                                    try {
+                                        ELSupport.checkType(attrs.getValue(i), expectedClass);
+                                    } catch (Exception e) {
+                                        err.jspError
+                                            (n, "jsp.error.coerce_to_type",
+                                             tldAttrs[j].getName(), expectedType, attrs.getValue(i));
+                                    }
+                                }
+
+                                jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+                                        attrs.getQName(i), attrs.getURI(i), attrs
+                                                .getLocalName(i),
+                                        attrs.getValue(i), false, null, false);
+                            } else {
+                                
+                                if (deferred && !tldAttrs[j].isDeferredMethod() && !tldAttrs[j].isDeferredValue()) {
+                                    // No deferred expressions allowed for this attribute
+                                    err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                            tldAttrs[j].getName());
+                                }
+                                if (!deferred && !tldAttrs[j].canBeRequestTime()) {
+                                    // Only deferred expressions are allowed for this attribute
+                                    err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                            tldAttrs[j].getName());
+                                }
+                                
+                                Class expectedType = String.class;
+                                try {
+                                    String typeStr = tldAttrs[j].getTypeName();
+                                    if (tldAttrs[j].isFragment()) {
+                                        expectedType = JspFragment.class;
+                                    } else if (typeStr != null) {
+                                        expectedType = JspUtil.toClass(typeStr,
+                                                loader);
+                                    }
+                                    if (elExpression) {
+                                        // El expression
+                                        validateFunctions(el, n);
+                                        jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+                                                attrs.getQName(i), attrs.getURI(i), 
+                                                attrs.getLocalName(i),
+                                                attrs.getValue(i), false, el, false);
+                                        ELContextImpl ctx = new ELContextImpl();
+                                        ctx.setFunctionMapper(getFunctionMapper(el));
+                                        try {
+                                            jspAttrs[i].validateEL(this.pageInfo.getExpressionFactory(), ctx);
+                                        } catch (ELException e) {
+                                            this.err.jspError(n.getStart(),
+                                                    "jsp.error.invalid.expression", 
+                                                    attrs.getValue(i), e.toString());
+                                        }
+                                    } else {
+                                        // Runtime expression
+                                        jspAttrs[i] = getJspAttribute(tldAttrs[j],
+                                                attrs.getQName(i), attrs.getURI(i),
+                                                attrs.getLocalName(i), attrs
+                                                .getValue(i), expectedType, n,
+                                                false);
+                                    }
+                                } catch (ClassNotFoundException e) {
+                                    err.jspError
+                                        (n, "jsp.error.unknown_attribute_type",
+                                         tldAttrs[j].getName(), tldAttrs[j].getTypeName());
+                                }
+                            }
+                            
+                        } else {
+                            // Attribute does not accept any expressions.
+                            // Make sure its value does not contain any.
+                            if (expression) {
+                                err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                                tldAttrs[j].getName());
+                            }
+                            jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
+                                    attrs.getQName(i), attrs.getURI(i), attrs
+                                            .getLocalName(i),
+                                    attrs.getValue(i), false, null, false);
+                        }
+                        if (expression) {
+                            tagDataAttrs.put(attrs.getQName(i),
+                                    TagData.REQUEST_TIME_VALUE);
+                        } else {
+                            tagDataAttrs.put(attrs.getQName(i), attrs
+                                    .getValue(i));
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    if (tagInfo.hasDynamicAttributes()) {
+                        jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+                                attrs.getURI(i), attrs.getLocalName(i), attrs
+                                        .getValue(i), java.lang.Object.class,
+                                n, true);
+                    } else {
+                        err.jspError(n, "jsp.error.bad_attribute", attrs
+                                .getQName(i), n.getLocalName());
+                    }
+                }
+            }
+        }
+
+        /*
+         * Make sure the given custom action does not have any invalid named
+         * attributes
+         */
+        private void checkNamedAttributes(Node.CustomTag n,
+                Node.JspAttribute[] jspAttrs, int start, 
+                Hashtable<String, Object> tagDataAttrs)
+                throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            Node.Nodes naNodes = n.getNamedAttributeNodes();
+
+            for (int i = 0; i < naNodes.size(); i++) {
+                Node.NamedAttribute na = (Node.NamedAttribute) naNodes
+                        .getNode(i);
+                boolean found = false;
+                for (int j = 0; j < tldAttrs.length; j++) {
+                    /*
+                     * See above comment about namespace matches. For named
+                     * attributes, we use the prefix instead of URI as the match
+                     * criterion, because in the case of a JSP document, we'd
+                     * have to keep track of which namespaces are in scope when
+                     * parsing a named attribute, in order to determine the URI
+                     * that the prefix of the named attribute's name matches to.
+                     */
+                    String attrPrefix = na.getPrefix();
+                    if (na.getLocalName().equals(tldAttrs[j].getName())
+                            && (attrPrefix == null || attrPrefix.length() == 0 || attrPrefix
+                                    .equals(n.getPrefix()))) {
+                        jspAttrs[start + i] = new Node.JspAttribute(na,
+                                tldAttrs[j], false);
+                        NamedAttributeVisitor nav = null;
+                        if (na.getBody() != null) {
+                            nav = new NamedAttributeVisitor();
+                            na.getBody().visit(nav);
+                        }
+                        if (nav != null && nav.hasDynamicContent()) {
+                            tagDataAttrs.put(na.getName(),
+                                    TagData.REQUEST_TIME_VALUE);
+                        } else {
+                            tagDataAttrs.put(na.getName(), na.getText());
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    if (tagInfo.hasDynamicAttributes()) {
+                        jspAttrs[start + i] = new Node.JspAttribute(na, null,
+                                true);
+                    } else {
+                        err.jspError(n, "jsp.error.bad_attribute",
+                                na.getName(), n.getLocalName());
+                    }
+                }
+            }
+        }
+
+        /**
+         * Preprocess attributes that can be expressions. Expression delimiters
+         * are stripped.
+         * <p>
+         * If value is null, checks if there are any NamedAttribute subelements
+         * in the tree node, and if so, constructs a JspAttribute out of a child
+         * NamedAttribute node.
+         */
+        private Node.JspAttribute getJspAttribute(TagAttributeInfo tai,
+                String qName, String uri, String localName, String value,
+                Class expectedType, Node n, boolean dynamic)
+                throws JasperException {
+
+            Node.JspAttribute result = null;
+
+            // XXX Is it an error to see "%=foo%" in non-Xml page?
+            // (We won't see "<%=foo%> in xml page because '<' is not a
+            // valid attribute value in xml).
+
+            if (value != null) {
+                if (n.getRoot().isXmlSyntax() && value.startsWith("%=")) {
+                    result = new Node.JspAttribute(tai, qName, uri, localName,
+                            value.substring(2, value.length() - 1), true, null,
+                            dynamic);
+                } else if (!n.getRoot().isXmlSyntax()
+                        && value.startsWith("<%=")) {
+                    result = new Node.JspAttribute(tai, qName, uri, localName,
+                            value.substring(3, value.length() - 2), true, null,
+                            dynamic);
+                } else {
+                    // The attribute can contain expressions but is not a
+                    // scriptlet expression; thus, we want to run it through
+                    // the expression interpreter
+
+                    // validate expression syntax if string contains
+                    // expression(s)
+                    ELNode.Nodes el = ELParser.parse(value);
+                    
+                    boolean deferred = false;
+                    Iterator<ELNode> nodes = el.iterator();
+                    while (nodes.hasNext()) {
+                        ELNode node = nodes.next();
+                        if (node instanceof ELNode.Root) {
+                            if (((ELNode.Root) node).getType() == '#') {
+                                deferred = true;
+                            }
+                        }
+                    }
+
+                    if (el.containsEL() && !pageInfo.isELIgnored()
+                            && ((!pageInfo.isDeferredSyntaxAllowedAsLiteral() && deferred)
+                                    || !deferred)) {
+
+                        validateFunctions(el, n);
+
+                        result = new Node.JspAttribute(tai, qName, uri,
+                                localName, value, false, el, dynamic);
+
+                        ELContextImpl ctx = new ELContextImpl();
+                        ctx.setFunctionMapper(getFunctionMapper(el));
+
+                        try {
+                            result.validateEL(this.pageInfo
+                                    .getExpressionFactory(), ctx);
+                        } catch (ELException e) {
+                            this.err.jspError(n.getStart(),
+                                    "jsp.error.invalid.expression", value, e
+                                            .toString());
+                        }
+
+                    } else {
+                        result = new Node.JspAttribute(tai, qName, uri,
+                                localName, value, false, null, dynamic);
+                    }
+                }
+            } else {
+                // Value is null. Check for any NamedAttribute subnodes
+                // that might contain the value for this attribute.
+                // Otherwise, the attribute wasn't found so we return null.
+
+                Node.NamedAttribute namedAttributeNode = n
+                        .getNamedAttributeNode(qName);
+                if (namedAttributeNode != null) {
+                    result = new Node.JspAttribute(namedAttributeNode, tai,
+                            dynamic);
+                }
+            }
+
+            return result;
+        }
+
+        /*
+         * Return an empty StringBuffer [not thread-safe]
+         */
+        private StringBuffer getBuffer() {
+            this.buf.setLength(0);
+            return this.buf;
+        }
+
+        /*
+         * Checks to see if the given attribute value represents a runtime or EL
+         * expression.
+         */
+        private boolean isExpression(Node n, String value, boolean checkDeferred) {
+            
+            boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && value.startsWith("%="))
+                    || (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")));
+            boolean elExpression = false;
+
+            if (!runtimeExpression && !pageInfo.isELIgnored()) {
+                Iterator<ELNode> nodes = ELParser.parse(value).iterator();
+                while (nodes.hasNext()) {
+                    ELNode node = nodes.next();
+                    if (node instanceof ELNode.Root) {
+                        if (((ELNode.Root) node).getType() == '$') {
+                            elExpression = true;
+                        } else if (checkDeferred && !pageInfo.isDeferredSyntaxAllowedAsLiteral() 
+                                && ((ELNode.Root) node).getType() == '#') {
+                            elExpression = true;
+                        }
+                    }
+                }
+            }
+
+            return runtimeExpression || elExpression;
+
+        }
+
+        /*
+         * Throws exception if the value of the attribute with the given name in
+         * the given node is given as an RT or EL expression, but the spec
+         * requires a static value.
+         */
+        private void throwErrorIfExpression(Node n, String attrName,
+                String actionName) throws JasperException {
+            if (n.getAttributes() != null
+                    && n.getAttributes().getValue(attrName) != null
+                    && isExpression(n, n.getAttributes().getValue(attrName), true)) {
+                err.jspError(n,
+                        "jsp.error.attribute.standard.non_rt_with_expr",
+                        attrName, actionName);
+            }
+        }
+
+        private static class NamedAttributeVisitor extends Node.Visitor {
+            private boolean hasDynamicContent;
+
+            public void doVisit(Node n) throws JasperException {
+                if (!(n instanceof Node.JspText)
+                        && !(n instanceof Node.TemplateText)) {
+                    hasDynamicContent = true;
+                }
+                visitBody(n);
+            }
+
+            public boolean hasDynamicContent() {
+                return hasDynamicContent;
+            }
+        }
+
+        private String findUri(String prefix, Node n) {
+
+            for (Node p = n; p != null; p = p.getParent()) {
+                Attributes attrs = p.getTaglibAttributes();
+                if (attrs == null) {
+                    continue;
+                }
+                for (int i = 0; i < attrs.getLength(); i++) {
+                    String name = attrs.getQName(i);
+                    int k = name.indexOf(':');
+                    if (prefix == null && k < 0) {
+                        // prefix not specified and a default ns found
+                        return attrs.getValue(i);
+                    }
+                    if (prefix != null && k >= 0
+                            && prefix.equals(name.substring(k + 1))) {
+                        return attrs.getValue(i);
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Validate functions in EL expressions
+         */
+        private void validateFunctions(ELNode.Nodes el, Node n)
+                throws JasperException {
+
+            class FVVisitor extends ELNode.Visitor {
+
+                Node n;
+
+                FVVisitor(Node n) {
+                    this.n = n;
+                }
+
+                public void visit(ELNode.Function func) throws JasperException {
+                    String prefix = func.getPrefix();
+                    String function = func.getName();
+                    String uri = null;
+
+                    if (n.getRoot().isXmlSyntax()) {
+                        uri = findUri(prefix, n);
+                    } else if (prefix != null) {
+                        uri = pageInfo.getURI(prefix);
+                    }
+
+                    if (uri == null) {
+                        if (prefix == null) {
+                            err.jspError(n, "jsp.error.noFunctionPrefix",
+                                    function);
+                        } else {
+                            err
+                                    .jspError(
+                                            n,
+                                            "jsp.error.attribute.invalidPrefix",
+                                            prefix);
+                        }
+                    }
+                    TagLibraryInfo taglib = pageInfo.getTaglib(uri);
+                    FunctionInfo funcInfo = null;
+                    if (taglib != null) {
+                        funcInfo = taglib.getFunction(function);
+                    }
+                    if (funcInfo == null) {
+                        err.jspError(n, "jsp.error.noFunction", function);
+                    }
+                    // Skip TLD function uniqueness check. Done by Schema ?
+                    func.setUri(uri);
+                    func.setFunctionInfo(funcInfo);
+                    processSignature(func);
+                }
+            }
+
+            el.visit(new FVVisitor(n));
+        }
+
+        private void prepareExpression(ELNode.Nodes el, Node n, String expr)
+                throws JasperException {
+            validateFunctions(el, n);
+
+            // test it out
+            ELContextImpl ctx = new ELContextImpl();
+            ctx.setFunctionMapper(this.getFunctionMapper(el));
+            ExpressionFactory ef = this.pageInfo.getExpressionFactory();
+            try {
+                ef.createValueExpression(ctx, expr, Object.class);
+            } catch (ELException e) {
+
+            }
+        }
+
+        private void processSignature(ELNode.Function func)
+                throws JasperException {
+            func.setMethodName(getMethod(func));
+            func.setParameters(getParameters(func));
+        }
+
+        /**
+         * Get the method name from the signature.
+         */
+        private String getMethod(ELNode.Function func) throws JasperException {
+            FunctionInfo funcInfo = func.getFunctionInfo();
+            String signature = funcInfo.getFunctionSignature();
+
+            int start = signature.indexOf(' ');
+            if (start < 0) {
+                err.jspError("jsp.error.tld.fn.invalid.signature", func
+                        .getPrefix(), func.getName());
+            }
+            int end = signature.indexOf('(');
+            if (end < 0) {
+                err.jspError(
+                        "jsp.error.tld.fn.invalid.signature.parenexpected",
+                        func.getPrefix(), func.getName());
+            }
+            return signature.substring(start + 1, end).trim();
+        }
+
+        /**
+         * Get the parameters types from the function signature.
+         * 
+         * @return An array of parameter class names
+         */
+        private String[] getParameters(ELNode.Function func)
+                throws JasperException {
+            FunctionInfo funcInfo = func.getFunctionInfo();
+            String signature = funcInfo.getFunctionSignature();
+            ArrayList<String> params = new ArrayList<String>();
+            // Signature is of the form
+            // <return-type> S <method-name S? '('
+            // < <arg-type> ( ',' <arg-type> )* )? ')'
+            int start = signature.indexOf('(') + 1;
+            boolean lastArg = false;
+            while (true) {
+                int p = signature.indexOf(',', start);
+                if (p < 0) {
+                    p = signature.indexOf(')', start);
+                    if (p < 0) {
+                        err.jspError("jsp.error.tld.fn.invalid.signature", func
+                                .getPrefix(), func.getName());
+                    }
+                    lastArg = true;
+                }
+                String arg = signature.substring(start, p).trim();
+                if (!"".equals(arg)) {
+                    params.add(arg);
+                }
+                if (lastArg) {
+                    break;
+                }
+                start = p + 1;
+            }
+            return (String[]) params.toArray(new String[params.size()]);
+        }
+
+        private FunctionMapper getFunctionMapper(ELNode.Nodes el)
+                throws JasperException {
+
+            class ValidateFunctionMapper extends FunctionMapper {
+
+                private HashMap<String, Method> fnmap = new HashMap<String, Method>();
+
+                public void mapFunction(String fnQName, Method method) {
+                    fnmap.put(fnQName, method);
+                }
+
+                public Method resolveFunction(String prefix, String localName) {
+                    return this.fnmap.get(prefix + ":" + localName);
+                }
+            }
+
+            class MapperELVisitor extends ELNode.Visitor {
+                ValidateFunctionMapper fmapper;
+
+                MapperELVisitor(ValidateFunctionMapper fmapper) {
+                    this.fmapper = fmapper;
+                }
+
+                public void visit(ELNode.Function n) throws JasperException {
+
+                    Class c = null;
+                    Method method = null;
+                    try {
+                        c = loader.loadClass(n.getFunctionInfo()
+                                .getFunctionClass());
+                    } catch (ClassNotFoundException e) {
+                        err.jspError("jsp.error.function.classnotfound", n
+                                .getFunctionInfo().getFunctionClass(), n
+                                .getPrefix()
+                                + ':' + n.getName(), e.getMessage());
+                    }
+                    String paramTypes[] = n.getParameters();
+                    int size = paramTypes.length;
+                    Class params[] = new Class[size];
+                    int i = 0;
+                    try {
+                        for (i = 0; i < size; i++) {
+                            params[i] = JspUtil.toClass(paramTypes[i], loader);
+                        }
+                        method = c.getDeclaredMethod(n.getMethodName(), params);
+                    } catch (ClassNotFoundException e) {
+                        err.jspError("jsp.error.signature.classnotfound",
+                                paramTypes[i], n.getPrefix() + ':'
+                                        + n.getName(), e.getMessage());
+                    } catch (NoSuchMethodException e) {
+                        err.jspError("jsp.error.noFunctionMethod", n
+                                .getMethodName(), n.getName(), c.getName());
+                    }
+                    fmapper.mapFunction(n.getPrefix() + ':' + n.getName(),
+                            method);
+                }
+            }
+
+            ValidateFunctionMapper fmapper = new ValidateFunctionMapper();
+            el.visit(new MapperELVisitor(fmapper));
+            return fmapper;
+        }
+    } // End of ValidateVisitor
+
+    /**
+     * A visitor for validating TagExtraInfo classes of all tags
+     */
+    static class TagExtraInfoVisitor extends Node.Visitor {
+
+        private ErrorDispatcher err;
+
+        /*
+         * Constructor
+         */
+        TagExtraInfoVisitor(Compiler compiler) {
+            this.err = compiler.getErrorDispatcher();
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+
+            ValidationMessage[] errors = tagInfo.validate(n.getTagData());
+            if (errors != null && errors.length != 0) {
+                StringBuffer errMsg = new StringBuffer();
+                errMsg.append("<h3>");
+                errMsg.append(Localizer.getMessage(
+                        "jsp.error.tei.invalid.attributes", n.getQName()));
+                errMsg.append("</h3>");
+                for (int i = 0; i < errors.length; i++) {
+                    errMsg.append("<p>");
+                    if (errors[i].getId() != null) {
+                        errMsg.append(errors[i].getId());
+                        errMsg.append(": ");
+                    }
+                    errMsg.append(errors[i].getMessage());
+                    errMsg.append("</p>");
+                }
+
+                err.jspError(n, errMsg.toString());
+            }
+
+            visitBody(n);
+        }
+    }
+
+    public static void validateDirectives(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+        page.visit(new DirectiveVisitor(compiler));
+    }
+
+    public static void validateExDirectives(Compiler compiler, Node.Nodes page)
+        throws JasperException {
+        // Determine the default output content type
+        PageInfo pageInfo = compiler.getPageInfo();
+        String contentType = pageInfo.getContentType();
+
+        if (contentType == null || contentType.indexOf("charset=") < 0) {
+            boolean isXml = page.getRoot().isXmlSyntax();
+            String defaultType;
+            if (contentType == null) {
+                defaultType = isXml ? "text/xml" : "text/html";
+            } else {
+                defaultType = contentType;
+            }
+
+            String charset = null;
+            if (isXml) {
+                charset = "UTF-8";
+            } else {
+                if (!page.getRoot().isDefaultPageEncoding()) {
+                    charset = page.getRoot().getPageEncoding();
+                }
+            }
+
+            if (charset != null) {
+                pageInfo.setContentType(defaultType + ";charset=" + charset);
+            } else {
+                pageInfo.setContentType(defaultType);
+            }
+        }
+
+        /*
+         * Validate all other nodes. This validation step includes checking a
+         * custom tag's mandatory and optional attributes against information in
+         * the TLD (first validation step for custom tags according to
+         * JSP.10.5).
+         */
+        page.visit(new ValidateVisitor(compiler));
+
+        /*
+         * Invoke TagLibraryValidator classes of all imported tags (second
+         * validation step for custom tags according to JSP.10.5).
+         */
+        validateXmlView(new PageDataImpl(page, compiler), compiler);
+
+        /*
+         * Invoke TagExtraInfo method isValid() for all imported tags (third
+         * validation step for custom tags according to JSP.10.5).
+         */
+        page.visit(new TagExtraInfoVisitor(compiler));
+
+    }
+
+    // *********************************************************************
+    // Private (utility) methods
+
+    /**
+     * Validate XML view against the TagLibraryValidator classes of all imported
+     * tag libraries.
+     */
+    private static void validateXmlView(PageData xmlView, Compiler compiler)
+            throws JasperException {
+
+        StringBuffer errMsg = null;
+        ErrorDispatcher errDisp = compiler.getErrorDispatcher();
+
+        for (Iterator iter = compiler.getPageInfo().getTaglibs().iterator(); iter
+                .hasNext();) {
+
+            Object o = iter.next();
+            if (!(o instanceof TagLibraryInfoImpl))
+                continue;
+            TagLibraryInfoImpl tli = (TagLibraryInfoImpl) o;
+
+            ValidationMessage[] errors = tli.validate(xmlView);
+            if ((errors != null) && (errors.length != 0)) {
+                if (errMsg == null) {
+                    errMsg = new StringBuffer();
+                }
+                errMsg.append("<h3>");
+                errMsg.append(Localizer.getMessage(
+                        "jsp.error.tlv.invalid.page", tli.getShortName(),
+                        compiler.getPageInfo().getJspFile()));
+                errMsg.append("</h3>");
+                for (int i = 0; i < errors.length; i++) {
+                    if (errors[i] != null) {
+                        errMsg.append("<p>");
+                        errMsg.append(errors[i].getId());
+                        errMsg.append(": ");
+                        errMsg.append(errors[i].getMessage());
+                        errMsg.append("</p>");
+                    }
+                }
+            }
+        }
+
+        if (errMsg != null) {
+            errDisp.jspError(errMsg.toString());
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java
new file mode 100644
index 0000000..49cbc7c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java
@@ -0,0 +1,37 @@
+/*
+ * 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.jasper.compiler.tagplugin;
+
+/**
+ * This interface is to be implemented by the plugin author, to supply
+ * an alternate implementation of the tag handlers.  It can be used to
+ * specify the Java codes to be generated when a tag is invoked.
+ *
+ * An implementation of this interface must be registered in a file
+ * named "tagPlugins.xml" under WEB-INF.
+ */
+
+public interface TagPlugin {
+
+    /**
+     * Generate codes for a custom tag.
+     * @param ctxt a TagPluginContext for accessing Jasper functions
+     */
+    void doTag(TagPluginContext ctxt);
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java
new file mode 100644
index 0000000..8d1c558
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java
@@ -0,0 +1,124 @@
+/*
+ * 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.jasper.compiler.tagplugin;
+
+
+/**
+ * This interface allows the plugin author to make inqueries about the
+ * properties of the current tag, and to use Jasper resources to generate
+ * direct Java codes in place of tag handler invocations.
+ */
+
+public interface TagPluginContext {
+    /**
+     * @return true if the body of the tag is scriptless.
+     */
+    boolean isScriptless();
+
+    /**
+     * @param attribute Name of the attribute
+     * @return true if the attribute is specified in the tag
+     */
+    boolean isAttributeSpecified(String attribute);
+
+    /**
+     * @return An unique temporary variable name that the plugin can use.
+     */
+    String getTemporaryVariableName();
+
+    /**
+     * Generate an import statement
+     * @param s Name of the import class, '*' allowed.
+     */
+    void generateImport(String s);
+
+    /**
+     * Generate a declaration in the of the generated class.  This can be
+     * used to declare an innter class, a method, or a class variable.
+     * @param id An unique ID identifying the declaration.  It is not
+     *           part of the declaration, and is used to ensure that the
+     *           declaration will only appear once.  If this method is
+     *           invoked with the same id more than once in the translation
+     *           unit, only the first declaration will be taken.
+     * @param text The text of the declaration.
+     **/
+    void generateDeclaration(String id, String text);
+
+    /**
+     * Generate Java source codes
+     */
+    void generateJavaSource(String s);
+
+    /**
+     * @return true if the attribute is specified and its value is a
+     *         translation-time constant.
+     */
+    boolean isConstantAttribute(String attribute);
+
+    /**
+     * @return A string that is the value of a constant attribute.  Undefined
+     *         if the attribute is not a (translation-time) constant.
+     *         null if the attribute is not specified.
+     */
+    String getConstantAttribute(String attribute);
+
+    /**
+     * Generate codesto evaluate value of a attribute in the custom tag
+     * The codes is a Java expression.
+     * NOTE: Currently cannot handle attributes that are fragments.
+     * @param attribute The specified attribute
+     */
+    void generateAttribute(String attribute);
+
+    /**
+     * Generate codes for the body of the custom tag
+     */
+    void generateBody();
+
+    /**
+     * Abandon optimization for this tag handler, and instruct
+     * Jasper to generate the tag handler calls, as usual.
+     * Should be invoked if errors are detected, or when the tag body
+     * is deemed too compilicated for optimization.
+     */
+    void dontUseTagPlugin();
+
+    /**
+     * Get the PluginContext for the parent of this custom tag.  NOTE:
+     * The operations available for PluginContext so obtained is limited
+     * to getPluginAttribute and setPluginAttribute, and queries (e.g.
+     * isScriptless().  There should be no calls to generate*().
+     * @return The pluginContext for the parent node.
+     *         null if the parent is not a custom tag, or if the pluginConxt
+     *         if not available (because useTagPlugin is false, e.g).
+     */
+    TagPluginContext getParentContext();
+
+    /**
+     * Associate the attribute with a value in the current tagplugin context.
+     * The plugin attributes can be used for communication among tags that
+     * must work together as a group.  See <c:when> for an example.
+     */
+    void setPluginAttribute(String attr, Object value);
+
+    /**
+     * Get the value of an attribute in the current tagplugin context.
+     */
+    Object getPluginAttribute(String attr);
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java
new file mode 100644
index 0000000..34e550c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextImpl.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jasper.el;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+/**
+ * Implementation of ELContext
+ * 
+ * @author Jacob Hookom
+ */
+public final class ELContextImpl extends ELContext {
+
+    private final static FunctionMapper NullFunctionMapper = new FunctionMapper() {
+        public Method resolveFunction(String prefix, String localName) {
+            return null;
+        }
+    };
+
+    private final static class VariableMapperImpl extends VariableMapper {
+
+        private Map<String, ValueExpression> vars;
+
+        public ValueExpression resolveVariable(String variable) {
+            if (vars == null) {
+                return null;
+            }
+            return vars.get(variable);
+        }
+
+        public ValueExpression setVariable(String variable,
+                ValueExpression expression) {
+            if (vars == null)
+                vars = new HashMap<String, ValueExpression>();
+            return vars.put(variable, expression);
+        }
+
+    }
+
+    private final ELResolver resolver;
+
+    private FunctionMapper functionMapper = NullFunctionMapper; // immutable
+
+    private VariableMapper variableMapper;
+
+    public ELContextImpl() {
+        this(ELResolverImpl.DefaultResolver);
+    }
+
+    public ELContextImpl(ELResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public ELResolver getELResolver() {
+        return this.resolver;
+    }
+
+    public FunctionMapper getFunctionMapper() {
+        return this.functionMapper;
+    }
+
+    public VariableMapper getVariableMapper() {
+        if (this.variableMapper == null) {
+            this.variableMapper = new VariableMapperImpl();
+        }
+        return this.variableMapper;
+    }
+
+    public void setFunctionMapper(FunctionMapper functionMapper) {
+        this.functionMapper = functionMapper;
+    }
+
+    public void setVariableMapper(VariableMapper variableMapper) {
+        this.variableMapper = variableMapper;
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java
new file mode 100644
index 0000000..a0754c3
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELContextWrapper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.jasper.el;
+
+import java.util.Locale;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+
+/**
+ * Simple ELContextWrapper for runtime evaluation of EL w/ dynamic FunctionMappers
+ * 
+ * @author jhook
+ */
+public final class ELContextWrapper extends ELContext {
+
+    private final ELContext target;
+    private final FunctionMapper fnMapper;
+    
+    public ELContextWrapper(ELContext target, FunctionMapper fnMapper) {
+        this.target = target;
+        this.fnMapper = fnMapper;
+    }
+
+    public ELResolver getELResolver() {
+        return this.target.getELResolver();
+    }
+
+    public FunctionMapper getFunctionMapper() {
+        if (this.fnMapper != null) return this.fnMapper;
+        return this.target.getFunctionMapper();
+    }
+
+    public VariableMapper getVariableMapper() {
+        return this.target.getVariableMapper();
+    }
+
+    public Object getContext(Class key) {
+        return this.target.getContext(key);
+    }
+
+    public Locale getLocale() {
+        return this.target.getLocale();
+    }
+
+    public boolean isPropertyResolved() {
+        return this.target.isPropertyResolved();
+    }
+
+    public void putContext(Class key, Object contextObject) throws NullPointerException {
+        this.target.putContext(key, contextObject);
+    }
+
+    public void setLocale(Locale locale) {
+        this.target.setLocale(locale);
+    }
+
+    public void setPropertyResolved(boolean resolved) {
+        this.target.setPropertyResolved(resolved);
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java
new file mode 100644
index 0000000..0c13095
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ELResolverImpl.java
@@ -0,0 +1,146 @@
+/*
+ * 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.jasper.el;
+
+import java.util.Iterator;
+
+import javax.el.ArrayELResolver;
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.el.ResourceBundleELResolver;
+import javax.servlet.jsp.el.VariableResolver;
+
+public final class ELResolverImpl extends ELResolver {
+	
+	public final static ELResolver DefaultResolver = new CompositeELResolver();
+
+	static {
+		((CompositeELResolver) DefaultResolver).add(new MapELResolver());
+		((CompositeELResolver) DefaultResolver).add(new ResourceBundleELResolver());
+		((CompositeELResolver) DefaultResolver).add(new ListELResolver());
+		((CompositeELResolver) DefaultResolver).add(new ArrayELResolver());
+		((CompositeELResolver) DefaultResolver).add(new BeanELResolver());
+	}
+
+	private final VariableResolver variableResolver;
+
+	public ELResolverImpl(VariableResolver variableResolver) {
+		this.variableResolver = variableResolver;
+	}
+
+	public Object getValue(ELContext context, Object base, Object property)
+			throws NullPointerException, PropertyNotFoundException, ELException {
+		if (context == null) {
+			throw new NullPointerException();
+		}
+
+		if (base == null) {
+			context.setPropertyResolved(true);
+			if (property != null) {
+				try {
+					return this.variableResolver.resolveVariable(property
+							.toString());
+				} catch (javax.servlet.jsp.el.ELException e) {
+					throw new ELException(e.getMessage(), e.getCause());
+				}
+			}
+		}
+
+		if (!context.isPropertyResolved()) {
+			return DefaultResolver.getValue(context, base, property);
+		}
+		return null;
+	}
+
+	public Class<?> getType(ELContext context, Object base, Object property)
+			throws NullPointerException, PropertyNotFoundException, ELException {
+		if (context == null) {
+			throw new NullPointerException();
+		}
+
+		if (base == null) {
+			context.setPropertyResolved(true);
+			if (property != null) {
+				try {
+					Object obj = this.variableResolver.resolveVariable(property
+							.toString());
+					return (obj != null) ? obj.getClass() : null;
+				} catch (javax.servlet.jsp.el.ELException e) {
+					throw new ELException(e.getMessage(), e.getCause());
+				}
+			}
+		}
+
+		if (!context.isPropertyResolved()) {
+			return DefaultResolver.getType(context, base, property);
+		}
+		return null;
+	}
+
+	public void setValue(ELContext context, Object base, Object property,
+			Object value) throws NullPointerException,
+			PropertyNotFoundException, PropertyNotWritableException,
+			ELException {
+		if (context == null) {
+			throw new NullPointerException();
+		}
+
+		if (base == null) {
+			context.setPropertyResolved(true);
+			throw new PropertyNotWritableException(
+					"Legacy VariableResolver wrapped, not writable");
+		}
+
+		if (!context.isPropertyResolved()) {
+			DefaultResolver.setValue(context, base, property, value);
+		}
+	}
+
+	public boolean isReadOnly(ELContext context, Object base, Object property)
+			throws NullPointerException, PropertyNotFoundException, ELException {
+		if (context == null) {
+			throw new NullPointerException();
+		}
+
+		if (base == null) {
+			context.setPropertyResolved(true);
+			return true;
+		}
+
+		return DefaultResolver.isReadOnly(context, base, property);
+	}
+
+	public Iterator<java.beans.FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+		return DefaultResolver.getFeatureDescriptors(context, base);
+	}
+
+	public Class<?> getCommonPropertyType(ELContext context, Object base) {
+		if (base == null) {
+			return String.class;
+		}
+		return DefaultResolver.getCommonPropertyType(context, base);
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java
new file mode 100644
index 0000000..21dbefc
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionEvaluatorImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ELParseException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.FunctionMapper;
+import javax.servlet.jsp.el.VariableResolver;
+
+
+public final class ExpressionEvaluatorImpl extends ExpressionEvaluator {
+
+	private final ExpressionFactory factory;
+	
+	public ExpressionEvaluatorImpl(ExpressionFactory factory) {
+		this.factory = factory;
+	}
+
+	public Expression parseExpression(String expression, Class expectedType,
+			FunctionMapper fMapper) throws ELException {
+		try {
+			ELContextImpl ctx = new ELContextImpl(ELResolverImpl.DefaultResolver);
+            if (fMapper != null) {
+                ctx.setFunctionMapper(new FunctionMapperImpl(fMapper));
+            }
+			ValueExpression ve = this.factory.createValueExpression(ctx, expression, expectedType);
+			return new ExpressionImpl(ve);
+		} catch (javax.el.ELException e) {
+			throw new ELParseException(e.getMessage());
+		}
+	}
+
+	public Object evaluate(String expression, Class expectedType,
+			VariableResolver vResolver, FunctionMapper fMapper)
+			throws ELException {
+		return this.parseExpression(expression, expectedType, fMapper).evaluate(vResolver);
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java
new file mode 100644
index 0000000..107326d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/ExpressionImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.VariableResolver;
+
+public final class ExpressionImpl extends Expression {
+
+	private final ValueExpression ve;
+	
+	public ExpressionImpl(ValueExpression ve) {
+		this.ve = ve;
+	}
+
+	public Object evaluate(VariableResolver vResolver) throws ELException {
+		ELContext ctx = new ELContextImpl(new ELResolverImpl(vResolver));
+		return ve.getValue(ctx);
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java
new file mode 100644
index 0000000..0cf84ec
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/FunctionMapperImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.jasper.el;
+
+import java.lang.reflect.Method;
+
+import javax.servlet.jsp.el.FunctionMapper;
+
+public final class FunctionMapperImpl extends javax.el.FunctionMapper {
+	
+	private final FunctionMapper fnMapper;
+
+	public FunctionMapperImpl(FunctionMapper fnMapper) {
+		this.fnMapper = fnMapper;
+	}
+
+	public Method resolveFunction(String prefix, String localName) {
+		return this.fnMapper.resolveFunction(prefix, localName);
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspELException.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspELException.java
new file mode 100644
index 0000000..c9cf302
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspELException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.ELException;
+
+public class JspELException extends ELException {
+
+    public JspELException(String mark, ELException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java
new file mode 100644
index 0000000..b48e003
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodExpression.java
@@ -0,0 +1,108 @@
+/*
+ * 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.jasper.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+import javax.el.MethodNotFoundException;
+import javax.el.PropertyNotFoundException;
+
+public final class JspMethodExpression extends MethodExpression implements
+        Externalizable {
+
+    private String mark;
+
+    private MethodExpression target;
+
+    public JspMethodExpression() {
+        super();
+    }
+
+    public JspMethodExpression(String mark, MethodExpression target) {
+        this.target = target;
+        this.mark = mark;
+    }
+
+    public MethodInfo getMethodInfo(ELContext context)
+            throws NullPointerException, PropertyNotFoundException,
+            MethodNotFoundException, ELException {
+        try {
+            return this.target.getMethodInfo(context);
+        } catch (MethodNotFoundException e) {
+            if (e instanceof JspMethodNotFoundException) throw e;
+            throw new JspMethodNotFoundException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public Object invoke(ELContext context, Object[] params)
+            throws NullPointerException, PropertyNotFoundException,
+            MethodNotFoundException, ELException {
+        try {
+            return this.target.invoke(context, params);
+        } catch (MethodNotFoundException e) {
+            if (e instanceof JspMethodNotFoundException) throw e;
+            throw new JspMethodNotFoundException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public boolean equals(Object obj) {
+        return this.target.equals(obj);
+    }
+
+    public int hashCode() {
+        return this.target.hashCode();
+    }
+
+    public String getExpressionString() {
+        return this.target.getExpressionString();
+    }
+
+    public boolean isLiteralText() {
+        return this.target.isLiteralText();
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.mark);
+        out.writeObject(this.target);
+    }
+
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.mark = in.readUTF();
+        this.target = (MethodExpression) in.readObject();
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java
new file mode 100644
index 0000000..abb9d30
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspMethodNotFoundException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.MethodNotFoundException;
+
+public class JspMethodNotFoundException extends MethodNotFoundException {
+
+    public JspMethodNotFoundException(String mark, MethodNotFoundException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java
new file mode 100644
index 0000000..89ac40b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotFoundException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.PropertyNotFoundException;
+
+public final class JspPropertyNotFoundException extends
+        PropertyNotFoundException {
+
+    public JspPropertyNotFoundException(String mark, PropertyNotFoundException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java
new file mode 100644
index 0000000..70507a4
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspPropertyNotWritableException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.PropertyNotWritableException;
+
+public class JspPropertyNotWritableException extends
+        PropertyNotWritableException {
+
+    public JspPropertyNotWritableException(String mark, PropertyNotWritableException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java
new file mode 100644
index 0000000..d8c32a0
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/JspValueExpression.java
@@ -0,0 +1,137 @@
+/*
+ * 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.jasper.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+
+/**
+ * Wrapper for providing context to ValueExpressions
+ * 
+ * @author Jacob Hookom
+ */
+public final class JspValueExpression extends ValueExpression implements
+        Externalizable {
+
+    private ValueExpression target;
+
+    private String mark;
+
+    public JspValueExpression() {
+        super();
+    }
+
+    public JspValueExpression(String mark, ValueExpression target) {
+        this.target = target;
+        this.mark = mark;
+    }
+
+    public Class<?> getExpectedType() {
+        return this.target.getExpectedType();
+    }
+
+    public Class<?> getType(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.getType(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public boolean isReadOnly(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.isReadOnly(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public void setValue(ELContext context, Object value)
+            throws NullPointerException, PropertyNotFoundException,
+            PropertyNotWritableException, ELException {
+        try {
+            this.target.setValue(context, value);
+        } catch (PropertyNotWritableException e) {
+            if (e instanceof JspPropertyNotWritableException) throw e;
+            throw new JspPropertyNotWritableException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public Object getValue(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.getValue(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    public boolean equals(Object obj) {
+        return this.target.equals(obj);
+    }
+
+    public int hashCode() {
+        return this.target.hashCode();
+    }
+
+    public String getExpressionString() {
+        return this.target.getExpressionString();
+    }
+
+    public boolean isLiteralText() {
+        return this.target.isLiteralText();
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.mark);
+        out.writeObject(this.target);
+    }
+
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.mark = in.readUTF();
+        this.target = (ValueExpression) in.readObject();
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java
new file mode 100644
index 0000000..7808e0f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/el/VariableResolverImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.jasper.el;
+
+import javax.el.ELContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.VariableResolver;
+
+public final class VariableResolverImpl implements VariableResolver {
+
+	private final ELContext ctx;
+	
+	public VariableResolverImpl(ELContext ctx) {
+		this.ctx = ctx;
+	}
+
+	public Object resolveVariable(String pName) throws ELException {
+		return this.ctx.getELResolver().getValue(this.ctx, null, pName);
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties
new file mode 100644
index 0000000..f5ab6ff
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties
@@ -0,0 +1,456 @@
+# 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.
+
+# $Id$
+#
+# Default localized string information
+# Localized this the Default Locale as is en_US
+
+jsp.error.compiler=No Java compiler available
+jsp.error.bad.servlet.engine=Incorrect servlet engine version!
+jsp.error.no.scratch.dir=The JSP engine is not configured with a scratch dir.\
+\n Please add \"jsp.initparams=scratchdir=<dir-name>\" \
+\n in the servlets.properties file for this context.
+jsp.error.bad.scratch.dir=The scratchDir you specified: {0} is unusable.
+jsp.message.scratch.dir.is=Scratch dir for the JSP engine is: {0}
+jsp.message.parent_class_loader_is=Parent class loader is: {0}
+jsp.message.dont.modify.servlets=IMPORTANT: Do not modify the generated servlets
+jsp.error.not.impl.comments=Internal error: Comments not implemented
+jsp.error.not.impl.directives=Internal error: Directives not implemented
+jsp.error.not.impl.declarations=Internal error: Declarations not implemented
+jsp.error.not.impl.expressions=Internal error: Expressions not implemented
+jsp.error.not.impl.scriptlets=Internal error: Scriptlets not implemented
+jsp.error.not.impl.usebean=Internal error: useBean not implemented
+jsp.error.not.impl.getp=Internal error: getProperty not implemented
+jsp.error.not.impl.setp=Internal error: setProperty not implemented
+jsp.error.not.impl.plugin=Internal error: plugin not implemented
+jsp.error.not.impl.forward=Internal error: forward not implemented
+jsp.error.not.impl.include=Internal error: include not implemented
+jsp.error.unavailable=JSP has been marked unavailable
+jsp.error.usebean.missing.attribute=useBean: id attribute missing or misspelled
+jsp.error.usebean.missing.type=useBean ({0}): Either class or type attribute must be \
+specified: 
+jsp.error.usebean.duplicate=useBean: Duplicate bean name: {0}
+jsp.error.usebean.prohibited.as.session=Can't use as session bean {0} since it is prohibited \
+by jsp directive defined earlier: 
+jsp.error.usebean.not.both=useBean: Can't specify both class and beanName attribute: 
+jsp.error.usebean.bad.type.cast=useBean ({0}): Type ({1}) is not assignable from class ({2}) 
+jsp.error.invalid.scope=Illegal value of \'scope\' attribute: {0} (must be one of \"page\", \"request\", \"session\", or \"application\")
+jsp.error.classname=Can't determine classname from .class file
+jsp.error.outputfolder=No output folder
+jsp.warning.bad.type=Warning: bad type in .class file
+jsp.error.data.file.write=Error while writing data file
+jsp.error.page.invalid.buffer=Page directive: invalid buffer size
+jsp.error.page.conflict.contenttype=Page directive: illegal to have multiple occurrences of 'contentType' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.contenttype=Page directive: invalid value for contentType
+jsp.error.page.conflict.session=Page directive: illegal to have multiple occurrences of 'session' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.session=Page directive: invalid value for session
+jsp.error.page.conflict.buffer=Page directive: illegal to have multiple occurrences of 'buffer' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.buffer=Page directive: invalid value for buffer
+jsp.error.page.conflict.autoflush=Page directive: illegal to have multiple occurrences of 'autoFlush' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.autoflush=Page directive: invalid value for autoFlush
+jsp.error.page.conflict.isthreadsafe=Page directive: illegal to have multiple occurrences of 'isThreadSafe' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.isthreadsafe=Page directive: invalid value for isThreadSafe
+jsp.error.page.conflict.info=Page directive: illegal to have multiple occurrences of 'info' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.info=Page directive: invalid value for info
+jsp.error.page.conflict.iserrorpage=Page directive: illegal to have multiple occurrences of 'isErrorPage' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.iserrorpage=Page directive: invalid value for isErrorPage
+jsp.error.page.conflict.errorpage=Page directive: illegal to have multiple occurrences of 'errorPage' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.language=Page directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.language=Tag directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
+jsp.error.page.language.nonjava=Page directive: invalid language attribute
+jsp.error.tag.language.nonjava=Tag directive: invalid language attribute
+jsp.error.page.defafteruse.language=Page directive: can't define language after a scriptlet 
+jsp.error.page.nomapping.language=Page directive: No mapping for language: 
+jsp.error.page.conflict.extends=Page directive: illegal to have multiple occurrences of 'extends' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.iselignored=Page directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.iselignored=Tag directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.iselignored=Page directive: invalid value for isELIgnored
+jsp.error.tag.invalid.iselignored=Tag directive: invalid value for isELIgnored
+jsp.error.page.multi.pageencoding=Page directive must not have multiple occurrences of pageencoding
+jsp.error.tag.conflict.attr=Tag directive: illegal to have multiple occurrences of the attribute \"{0}\" with different values (old: {1}, new: {2})
+jsp.error.tag.multi.pageencoding=Tag directive must not have multiple occurrences of pageencoding
+jsp.error.page.bad_b_and_a_combo=Page directive: Illegal combination of buffer=\"none\" && autoFlush=\"false\"
+jsp.error.not.impl.taglib=Internal error: Tag extensions not implemented
+jsp.error.include.missing.file=Missing file argument to include
+jsp.error.include.bad.file=Bad file argument to include 
+jsp.error.include.exception=Unable to include {0}
+jsp.error.stream.closed=Stream closed
+jsp.error.invalid.forward=Invalid forward tag
+jsp.error.unknownException=Unhandled error! You might want to consider having an error page \
+to report such errors more gracefully
+jsp.error.invalid.directive=Invalid directive
+jsp.error.invalid.implicit=Invalid implicit TLD for tag file at {0}
+jsp.error.invalid.implicit.version=Invalid JSP version defined in implicit TLD for tag file at {0}
+jsp.error.invalid.version=Invalid JSP version defined for tag file at {0}
+jsp.error.directive.istagfile={0} directive cannot be used in a tag file
+jsp.error.directive.isnottagfile={0} directive can only be used in a tag file
+jsp.error.tagfile.tld.name=The \"name\" attribute of the tag directive has a value {0} while the \"name\" tag of the \"tag-file\" element in the TLD is {1}
+jsp.error.action.istagfile={0} action cannot be used in a tag file
+jsp.error.action.isnottagfile={0} action can be used in tag files only
+jsp.error.unterminated=Unterminated {0} tag
+jsp.error.usebean.notinsamefile=useBean tag must begin and end in the same physical file
+jsp.error.loadclass.taghandler=Unable to load tag handler class \"{0}\" for tag \"{1}\"
+jsp.error.unable.compile=Unable to compile class for JSP
+jsp.error.unable.load=Unable to load class for JSP
+jsp.error.unable.rename=Unable to rename class file {0} to {1}
+jsp.error.mandatory.attribute={0}: Mandatory attribute {1} missing
+jsp.error.flush=Exception occurred when flushing data
+jsp.engine.info=Jasper JSP 2.1 Engine
+jsp.error.invalid.expression="{0}" contains invalid expression(s): {1}
+jsp.error.invalid.attribute={0} has invalid attribute: {1}
+jsp.error.usebean.class.notfound=Class: {0} not found
+jsp.error.file.cannot.read=Cannot read file: {0}
+jsp.error.file.already.registered=Recursive include of file {0}
+jsp.error.file.not.registered=file {0} not seen in include
+jsp.error.quotes.unterminated=Unterminated quotes
+jsp.error.attr.quoted=Attribute value should be quoted
+jsp.error.attr.novalue=Attribute {0} has no value
+jsp.error.tag.attr.unterminated=Unterminated tag attribute list
+jsp.error.param.noname=No name in PARAM tag
+jsp.error.param.novalue=No value in PARAM tag
+jsp.error.beans.nullbean=Attempted a bean operation on a null object.
+jsp.error.beans.nobeaninfo=No BeanInfo for the bean of type ''{0}'' could be found, the class likely does not exist.
+jsp.error.beans.introspection=An exception occurred while introspecting the read method of property ''{0}'' in a bean of type ''{1}'':\n{2}
+jsp.error.beans.nomethod=Cannot find a method to read property ''{0}'' in a bean of type ''{1}''
+jsp.error.beans.nomethod.setproperty=Can''t find a method to write property ''{0}'' of type ''{1}'' in a bean of type ''{2}''
+jsp.error.beans.noproperty=Cannot find any information on property ''{0}'' in a bean of type ''{1}''
+jsp.error.beans.property.conversion=Unable to convert string \"{0}\" to class \"{1}\" for attribute \"{2}\": {3}
+jsp.error.beans.propertyeditor.notregistered=Property Editor not registered with the PropertyEditorManager
+jsp.error.beans.setproperty.noindexset=Cannot set indexed property
+jsp.error.include.tag=Invalid jsp:include tag
+jsp.error.include.noflush=jsp:include needs to have \"flush=true\"
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" is the only valid combination in JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer=Error: Attempt to clear a buffer that's already been flushed
+jsp.error.overflow=Error: JSP Buffer overflow
+jsp.error.paramexpected=Expecting \"jsp:param\" standard action with \"name\" and \"value\" attributes
+jsp.error.param.invalidUse=The jsp:param action must not be used outside the jsp:include, jsp:forward, or jsp:params elements
+jsp.error.params.invalidUse=jsp:params must be a direct child of jsp:plugin
+jsp.error.fallback.invalidUse=jsp:fallback must be a direct child of jsp:plugin
+jsp.error.namedAttribute.invalidUse=jsp:attribute must be the subelement of a standard or custom action
+jsp.error.jspbody.invalidUse=jsp:body must be the subelement of a standard or custom action
+jsp.error.closeindividualparam=param tag needs to be closed with \"/>\"
+jsp.error.closeparams=param tag needs to be closed with /params
+jsp.error.params.emptyBody=jsp:params must contain at least one nested jsp:param
+jsp.error.params.illegalChild=jsp:params must not have any nested elements other than jsp:param
+jsp.error.plugin.notype=type not declared in jsp:plugin
+jsp.error.plugin.badtype=Illegal value for 'type' attribute in jsp:plugin: must be 'bean' or 'applet'
+jsp.error.plugin.nocode=code not declared in jsp:plugin
+jsp.error.ise_on_clear=Illegal to clear() when buffer size == 0
+jsp.error.setproperty.beanNotFound=setProperty: Bean {0} not found
+jsp.error.getproperty.beanNotFound=getProperty: Bean {0} not found
+jsp.error.setproperty.ClassNotFound=setProperty: Class {0} not found
+jsp.error.javac=Javac exception
+jsp.error.javac.env=Environment: 
+jsp.error.compilation=Error compiling file: {0} {1}
+# typo ?
+#jsp.error.setproperty.invalidSayntax=setProperty: can't have non-null value when property=*
+jsp.error.setproperty.invalidSyntax=setProperty: can't have non-null value when property=*
+jsp.error.setproperty.beanInfoNotFound=setproperty: beanInfo for bean {0} not found
+jsp.error.setproperty.paramOrValue=setProperty: either param or value can be present
+jsp.error.setproperty.arrayVal=setProperty: can't set array property {0} through a string constant value
+jsp.warning.keepgen=Warning: Invalid value for the initParam keepgenerated. Will use the default value of \"false\"
+jsp.warning.xpoweredBy=Warning: Invalid value for the initParam xpoweredBy. Will use the default value of \"false\"
+jsp.warning.enablePooling=Warning: Invalid value for the initParam enablePooling. Will use the default value of \"true\"
+jsp.warning.invalidTagPoolSize=Warning: Invalid value for the init parameter named tagPoolSize. Will use default size of {0}
+jsp.warning.mappedFile=Warning: Invalid value for the initParam mappedFile. Will use the default value of \"false\"
+jsp.warning.classDebugInfo=Warning: Invalid value for the initParam classdebuginfo. Will use the default value of \"false\"
+jsp.warning.checkInterval=Warning: Invalid value for the initParam checkInterval. Will use the default value of \"300\" seconds
+jsp.warning.modificationTestInterval=Warning: Invalid value for the initParam modificationTestInterval. Will use the default value of \"4\" seconds
+jsp.warning.development=Warning: Invalid value for the initParam development. Will use the default value of \"true\"
+jsp.warning.fork=Warning: Invalid value for the initParam fork. Will use the default value of \"true\"
+jsp.warning.reloading=Warning: Invalid value for the initParam reloading. Will use the default value of \"true\"
+jsp.warning.dumpSmap=Warning: Invalid value for the initParam dumpSmap. Will use the default value of \"false\"
+jsp.warning.genchararray=Warning: Invalid value for the initParam genStrAsCharArray. Will use the default value of \"false\"
+jsp.warning.suppressSmap=Warning: Invalid value for the initParam suppressSmap. Will use the default value of \"false\"
+jsp.warning.displaySourceFragment=Warning: Invalid value for the initParam displaySourceFragment. Will use the default value of \"true\"
+jsp.error.badtaglib=Unable to open taglibrary {0} : {1}
+jsp.error.badGetReader=Cannot create a reader when the stream is not buffered
+jsp.warning.unknown.element.in.taglib=Unknown element ({0}) in taglib
+jsp.warning.unknown.element.in.tag=Unknown element ({0}) in tag
+jsp.warning.unknown.element.in.tagfile=Unknown element ({0}) in tag-file
+jsp.warning.unknown.element.in.attribute=Unknown element ({0}) in attribute
+jsp.warning.unknown.element.in.variable=Unknown element ({0}) in variable
+jsp.warning.unknown.element.in.validator=Unknown element ({0}) in validator
+jsp.warning.unknown.element.in.initParam=Unknown element ({0}) in validator's init-param
+jsp.warning.unknown.element.in.function=Unknown element ({0}) in function
+jsp.error.more.than.one.taglib=More than one taglib in the TLD: {0}
+jsp.error.teiclass.instantiation=Failed to load or instantiate TagExtraInfo class: {0}
+jsp.error.non_null_tei_and_var_subelems=Tag {0} has one or more variable subelements and a TagExtraInfo class that returns one or more VariableInfo
+jsp.error.parse.error.in.TLD=Parse Error in the tag library descriptor: {0}
+jsp.error.unable.to.open.TLD=Unable to open the tag library descriptor: {0}
+jsp.buffer.size.zero=Buffer size <= 0
+jsp.error.file.not.found=File \"{0}\" not found
+jsp.message.copyinguri=Copying {0} into {1}
+jsp.message.htmlcomment=\nStripping Comment: \t{0}
+jsp.message.handling_directive=\nHandling Directive: {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=Package name is: {0}
+jsp.message.class_name_is=Class name is: {0}
+jsp.message.java_file_name_is=Java file name is: {0}
+jsp.message.class_file_name_is=Class file name is: {0}
+jsp.message.accepted=Accepted {0} at {1}
+jsp.message.adding_jar=Adding jar {0} to my classpath
+jsp.message.compiling_with=Compiling with: {0}
+jsp.message.template_text=template text
+jsp.error.missing_attribute=According to the TLD or the tag file, attribute {0} is mandatory for tag {1}
+jsp.error.bad_attribute=Attribute {0} invalid for tag {1} according to TLD
+jsp.error.tld.unable_to_read=Unable to read TLD \"{1}\" from JAR file \"{0}\": {2}
+jsp.error.tld.unable_to_get_jar=Unable to get JAR resource \"{0}\" containing TLD: {1}
+jsp.error.tld.missing_jar=Missing JAR resource \"{0}\" containing TLD
+jsp.error.webxml_not_found=Could not locate web.xml
+jsp.cmd_line.usage=Usage: jsptoservlet [-dd <path/to/outputDirectory>] [-keepgenerated] \
+<.jsp files>
+jsp.message.cp_is=Classpath {0} is: {1}
+jsp.error.unable.to_load_taghandler_class=Unable to load tag handler class {0} because of {1}
+jsp.error.unable.to_find_method=Unable to find setter method for attribute: {0}
+jsp.error.unable.to_convert_string=Unable to convert a String to {0} for attribute {1}
+jsp.error.unable.to_introspect=Unable to introspect on tag handler class: {0} because of {1}
+jsp.error.bad_tag=No tag \"{0}\" defined in tag library imported with prefix \"{1}\"
+jsp.error.xml.bad_tag=No tag \"{0}\" defined in tag library associated with uri \"{1}\"
+jsp.error.bad_string_Character=Cannot extract a Character from a zero length array
+jsp.error.bad_string_char=Cannot extract a char from a zero length array
+jsp.warning.compiler.class.cantcreate=Can't create an instance of specified compiler plugin class {0} due to {1}. Will default to Sun Java Compiler.
+jsp.warning.compiler.class.notfound=Specified compiler plugin class {0} not found. Will default to Sun Java Compiler.
+jsp.warning.compiler.path.notfound=Specified compiler path {0} not found. Will default to system PATH.
+jsp.error.jspc.uriroot_not_dir=The -uriroot option must specify a pre-existing directory
+jsp.error.jspc.missingTarget=Missing target: Must specify -webapp or -uriroot, or one or more JSP pages
+jsp.error.jspc.no_uriroot=The uriroot is not specified and cannot be located with the specified JSP file(s)
+jspc.implicit.uriRoot=uriRoot implicitly set to "{0}"
+jspc.usage=Usage: jspc <options> [--] <jsp files>\n\
+where jsp files is\n\
+\    -webapp <dir>      A directory containing a web-app, whose JSP pages\n\
+\                       will be processed recursively\n\
+or any number of\n\
+\    <file>             A file to be parsed as a JSP page\n\
+where options include:\n\
+\    -help              Print this help message\n\
+\    -v                 Verbose mode\n\
+\    -d <dir>           Output Directory (default -Djava.io.tmpdir)\n\
+\    -l                 Outputs the name of the JSP page upon failure\n\
+\    -s                 Outputs the name of the JSP page upon success\n\
+\    -p <name>          Name of target package (default org.apache.jsp)\n\
+\    -c <name>          Name of target class name (only applies to first JSP page)\n\
+\    -mapped            Generates separate write() calls for each HTML line in the JSP\n\
+\    -die[#]            Generates an error return code (#) on fatal errors (default 1)\n\
+\    -uribase <dir>     The uri directory compilations should be relative to\n\
+\                       (default "/")\n\
+\    -uriroot <dir>     Same as -webapp\n\
+\    -compile           Compiles generated servlets\n\
+\    -webinc <file>     Creates a partial servlet mappings in the file\n\
+\    -webxml <file>     Creates a complete web.xml in the file\n\
+\    -ieplugin <clsid>  Java Plugin classid for Internet Explorer\n\
+\    -classpath <path>  Overrides java.class.path system property\n\
+\    -xpoweredBy        Add X-Powered-By response header\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.4)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Automatically created by Apache Jakarta Tomcat JspC.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Automatically created by Apache Jakarta Tomcat JspC.\n\
+Place this fragment in the web.xml before all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.webinc.insertEnd=<!-- JSPC servlet mappings end -->
+jspc.webinc.insertStart=<!-- JSPC servlet mappings start -->
+jspc.error.jasperException=error-the file ''{0}'' generated the following parse exception: {1}
+jspc.error.generalException=ERROR-the file ''{0}'' generated the following general exception:
+jspc.error.fileDoesNotExist=The file argument ''{0}'' does not exist
+jspc.error.emptyWebApp=-webapp requires a trailing file argument
+jsp.error.library.invalid=JSP page is invalid according to library {0}: {1}
+jsp.error.tlvclass.instantiation=Failed to load or instantiate TagLibraryValidator class: {0}
+jsp.error.tlv.invalid.page=Validation error messages from TagLibraryValidator for {0} in {1}
+jsp.error.tei.invalid.attributes=Validation error messages from TagExtraInfo for {0}
+jsp.parser.sax.propertynotsupported=SAX property not supported: {0}
+jsp.parser.sax.propertynotrecognized=SAX property not recognized: {0}
+jsp.parser.sax.featurenotsupported=SAX feature not supported: {0}
+jsp.parser.sax.featurenotrecognized=SAX feature not recognized: {0}
+jsp.error.no.more.content=End of content reached while more parsing required: tag nesting error?
+jsp.error.parse.xml=XML parsing error on file {0}
+jsp.error.parse.xml.line=XML parsing error on file {0}: (line {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body=Body of {0} element must not contain any XML elements
+jsp.error.internal.tldinit=Unable to initialize TldLocationsCache: {0}
+jsp.error.internal.filenotfound=Internal Error: File {0} not found
+jsp.error.internal.evaluator_not_found=Internal error: unable to load expression evaluator
+jsp.error.parse.xml.invalidPublicId=Invalid PUBLIC ID: {0}
+jsp.error.include.flush.invalid.value=Invalid value for the flush attribute: {0}
+jsp.error.unsupported.encoding=Unsupported encoding: {0}
+tld.error.variableNotAllowed=It is an error for a tag that has one or more variable subelements to have a TagExtraInfo class that returns a non-null object.
+jsp.error.tldInWebDotXmlNotFound=Could not locate TLD {1} for URI {0} specified in web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved=The absolute uri: {0} cannot be resolved in either web.xml or the jar files deployed with this application
+jsp.error.taglibDirective.missing.location=Neither \'uri\' nor \'tagdir\' attribute specified
+jsp.error.taglibDirective.both_uri_and_tagdir=Both \'uri\' and \'tagdir\' attributes specified
+jsp.error.invalid.tagdir=Tag file directory {0} does not start with \"/WEB-INF/tags\"
+jsp.error.unterminated.user.tag=Unterminated user-defined tag: ending tag {0} not found or incorrectly nested
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:text&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix=The taglib prefix {0} is reserved
+jsp.error.invalid.javaEncoding=Invalid java encodings. Tried {0} and then {1}. Both failed.
+jsp.error.needAlternateJavaEncoding=Default java encoding {0} is invalid on your java platform. An alternate can be specified via the 'javaEncoding' parameter of JspServlet.
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number=An error occurred at line: {0} in the jsp file: {1}
+jsp.error.multiple.line.number=\n\nAn error occurred between lines: {0} and {1} in the jsp file: {2}\n\n
+jsp.error.java.line.number=An error occurred at line: {0} in the generated java file
+jsp.error.corresponding.servlet=Generated servlet error:\n
+jsp.error.empty.body.not.allowed=Empty body not allowed for {0}
+jsp.error.jspbody.required=Must use jsp:body to specify tag body for {0} if jsp:attribute is used.
+jsp.error.jspbody.emptybody.only=The {0} tag can only have jsp:attribute in its body.
+jsp.error.no.scriptlets=Scripting elements ( &lt;%!, &lt;jsp:declaration, &lt;%=, &lt;jsp:expression, &lt;%, &lt;jsp:scriptlet ) are disallowed here.
+jsp.error.internal.unexpected_node_type=Internal Error: Unexpected node type encountered
+jsp.error.tld.fn.invalid.signature=Invalid syntax for function signature in TLD.  Tag Library: {0}, Function: {1}
+jsp.error.tld.fn.duplicate.name=Duplicate function name {0} in tag library {1}
+jsp.error.tld.fn.invalid.signature.commaexpected=Invalid syntax for function signature in TLD.  Comma ',' expected.  Tag Library: {0}, Function: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Invalid syntax for function signature in TLD.  Parenthesis '(' expected.  Tag Library: {0}, Function: {1}.
+jsp.error.tld.mandatory.element.missing=Mandatory TLD element missing or empty: {0}
+jsp.error.dynamic.attributes.not.implemented=The {0} tag declares that it accepts dynamic attributes but does not implement the required interface
+jsp.error.nomatching.fragment=Cannot find an attribute directive (with name={0} and fragment=true) prior to the fragment directive.
+jsp.error.attribute.noequal=equal symbol expected
+jsp.error.attribute.noquote=quote symbol expected
+jsp.error.attribute.unterminated=attribute for {0} is not properly terminated
+jsp.error.attribute.noescape=Attribute value {0} is quoted with {1} which must be escaped when used within the value
+jsp.error.missing.tagInfo=TagInfo object for {0} is missing from TLD
+jsp.error.deferredmethodsignaturewithoutdeferredmethod=Cannot specify a method signature if 'deferredMethod' is not 'true'
+jsp.error.deferredvaluetypewithoutdeferredvalue=Cannot specify a value type if 'deferredValue' is not 'true'
+jsp.error.deferredmethodandvalue='deferredValue' and 'deferredMethod' cannot be both 'true'
+jsp.error.fragmentwithtype=Cannot specify both 'fragment' and 'type' attributes.  If 'fragment' is present, 'type' is fixed as 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue=Cannot specify both 'fragment' and 'rtexprvalue' attributes.  If 'fragment' is present, 'rtexprvalue' is fixed as 'true'
+jsp.error.fragmentWithDeclareOrScope=Both 'fragment' and 'declare' or 'scope' attributes specified in variable directive
+jsp.error.var_and_varReader=Only one of \'var\' or \'varReader\' may be specified
+jsp.error.missing_var_or_varReader=Missing \'var\' or \'varReader\' attribute
+jsp.warning.bad.urlpattern.propertygroup=Bad value {0} in the url-pattern subelement in web.xml
+jsp.error.unknown_attribute_type=Unknown attribute type ({1}) for attribute {0}.
+jsp.error.coerce_to_type=Cannot coerce value ({2}) to type ({1}) for attribute {0}.
+jsp.error.jspelement.missing.name=Mandatory XML-style \'name\' attribute missing
+jsp.error.xmlns.redefinition.notimplemented=Internal error: Attempt to redefine xmlns:{0}.  Redefinition of namespaces is not implemented.
+jsp.error.could.not.add.taglibraries=Could not add one or more tag libraries.
+jsp.error.duplicate.name.jspattribute=The attribute {0} specified in the standard or custom action also appears as the value of the name attribute in the enclosed jsp:attribute
+jsp.error.not.in.template={0} not allowed in a template text body.
+jsp.error.badStandardAction=Invalid standard action
+jsp.error.xml.badStandardAction=Invalid standard action: {0}
+jsp.error.tagdirective.badbodycontent=Invalid body-content ({0}) in tag directive
+jsp.error.simpletag.badbodycontent=The TLD for the class {0} specifies an invalid body-content (JSP) for a SimpleTag.
+jsp.error.config_pagedir_encoding_mismatch=Page-encoding specified in jsp-property-group ({0}) is different from that specified in page directive ({1})
+jsp.error.prolog_pagedir_encoding_mismatch=Page-encoding specified in XML prolog ({0}) is different from that specified in page directive ({1})
+jsp.error.prolog_config_encoding_mismatch=Page-encoding specified in XML prolog ({0}) is different from that specified in jsp-property-group ({1})
+jsp.error.attribute.custom.non_rt_with_expr=According to TLD or attribute directive in tag file, attribute {0} does not accept any expressions
+jsp.error.attribute.standard.non_rt_with_expr=The {0} attribute of the {1} standard action does not accept any expressions
+jsp.error.scripting.variable.missing_name=Unable to determine scripting variable name from attribute {0}
+jasper.error.emptybodycontent.nonempty=According to TLD, tag {0} must be empty, but is not
+jsp.error.tagfile.nameNotUnique=The value of {0} and the value of {1} in line {2} are the same.
+jsp.error.tagfile.nameFrom.noAttribute=Cannot find an attribute directive with a name attribute with a value \"{0}\", the value of this name-from-attribute attribute.
+jsp.error.tagfile.nameFrom.badAttribute=The attribute directive (declared in line {1} and whose name attribute is \"{0}\", the value of this name-from-attribute attribute) must be of type java.lang.String, is \"required\" and not a \"rtexprvalue\".
+jsp.error.page.noSession=Cannot access session scope in page that does not participate in any session
+jsp.error.usebean.noSession=Illegal for useBean to use session scope when JSP page declares (via page directive) that it does not participate in sessions
+jsp.error.xml.encodingByteOrderUnsupported = Given byte order for encoding \"{0}\" is not supported.
+jsp.error.xml.encodingDeclInvalid = Invalid encoding name \"{0}\".
+jsp.error.xml.encodingDeclRequired = The encoding declaration is required in the text declaration.
+jsp.error.xml.morePseudoAttributes = more pseudo attributes is expected.
+jsp.error.xml.noMorePseudoAttributes = no more pseudo attributes is allowed.
+jsp.error.xml.versionInfoRequired = The version is required in the XML declaration.
+jsp.error.xml.xmlDeclUnterminated = The XML declaration must end with \"?>\".
+jsp.error.xml.reservedPITarget = The processing instruction target matching \"[xX][mM][lL]\" is not allowed.
+jsp.error.xml.spaceRequiredInPI = White space is required between the processing instruction target and data.
+jsp.error.xml.invalidCharInContent = An invalid XML character (Unicode: 0x{0}) was found in the element content of the document.
+jsp.error.xml.spaceRequiredBeforeStandalone = White space is required before the encoding pseudo attribute in the XML declaration.
+jsp.error.xml.sdDeclInvalid = The standalone document declaration value must be \"yes\" or \"no\", not \"{0}\".
+jsp.error.xml.invalidCharInPI = An invalid XML character (Unicode: 0x{0}) was found in the processing instruction.
+jsp.error.xml.versionNotSupported = XML version \"{0}\" is not supported, only XML 1.0 is supported.
+jsp.error.xml.pseudoAttrNameExpected = a pseudo attribute name is expected.
+jsp.error.xml.expectedByte = Expected byte {0} of {1}-byte UTF-8 sequence.
+jsp.error.xml.invalidByte = Invalid byte {0} of {1}-byte UTF-8 sequence.
+jsp.error.xml.operationNotSupported = Operation \"{0}\" not supported by {1} reader.
+jsp.error.xml.invalidHighSurrogate = High surrogate bits in UTF-8 sequence must not exceed 0x10 but found 0x{0}.
+jsp.error.xml.invalidASCII = Byte \"{0}\" not 7-bit ASCII.
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = White space is required before the encoding pseudo attribute in the XML declaration.
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = White space is required before the encoding pseudo attribute in the text declaration.
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = White space is required before the version pseudo attribute in the text declaration.
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = White space is required before the version pseudo attribute in the XML declaration.
+jsp.error.xml.eqRequiredInXMLDecl = The '' = '' character must follow \"{0}\" in the XML declaration.
+jsp.error.xml.eqRequiredInTextDecl = The '' = '' character must follow \"{0}\" in the text declaration.
+jsp.error.xml.quoteRequiredInTextDecl = The value following \"{0}\" in the text declaration must be a quoted string.
+jsp.error.xml.quoteRequiredInXMLDecl = The value following \"{0}\" in the XML declaration must be a quoted string.
+jsp.error.xml.invalidCharInTextDecl = An invalid XML character (Unicode: 0x{0}) was found in the text declaration.
+jsp.error.xml.invalidCharInXMLDecl = An invalid XML character (Unicode: 0x{0}) was found in the XML declaration.
+jsp.error.xml.closeQuoteMissingInTextDecl = closing quote in the value following \"{0}\" in the text declaration is missing.
+jsp.error.xml.closeQuoteMissingInXMLDecl = closing quote in the value following \"{0}\" in the XML declaration is missing.
+jsp.error.xml.invalidHighSurrogate = High surrogate bits in UTF-8 sequence must not exceed 0x10 but found 0x{0}.
+jsp.error.multiple.jsp = Cannot have multiple specifications of 
+jsp.error.jspoutput.conflict=&lt;jsp:output&gt;: illegal to have multiple occurrences of \"{0}\" with different values (old: {1}, new: {2})
+jsp.error.jspoutput.doctypenamesystem=&lt;jsp:output&gt;: 'doctype-root-element' and 'doctype-system' attributes must appear together
+jsp.error.jspoutput.doctypepulicsystem=&lt;jsp:output&gt;: 'doctype-system' attribute must appear if 'doctype-public' attribute appears
+jsp.error.jspoutput.nonemptybody=&lt;jsp:output&gt; must not have a body
+jsp.error.jspoutput.invalidUse=&lt;jsp:output&gt; must not be used in standard syntax
+jsp.error.attributes.not.allowed = {0} must not have any attributes
+jsp.error.tagfile.badSuffix=Missing \".tag\" suffix in tag file path {0}
+jsp.error.tagfile.illegalPath=Illegal tag file path: {0}, must start with \"/WEB-INF/tags\" or \"/META-INF/tags\"
+jsp.error.plugin.wrongRootElement=Name of root element in {0} different from {1}
+jsp.error.attribute.invalidPrefix=The attribute prefix {0} does not correspond to any imported tag library
+jsp.error.nested.jspattribute=A jsp:attribute standard action cannot be nested within another jsp:attribute standard action
+jsp.error.nested.jspbody=A jsp:body standard action cannot be nested within another jsp:body or jsp:attribute standard action
+jsp.error.variable.either.name=Either name-given or name-from-attribute attribute must be specified in a variable directive
+jsp.error.variable.both.name=Cannot specify both name-given or name-from-attribute attributes in a variable directive
+jsp.error.variable.alias=Both or none of the name-from-attribute and alias attributes must be specified in a variable directive
+jsp.error.attribute.null_name=Null attribute name
+jsp.error.jsptext.badcontent=\'&lt;\', when appears in the body of &lt;jsp:text&gt;, must be encapsulated within a CDATA
+jsp.error.jsproot.version.invalid=Invalid version number: \"{0}\", must be \"1.2\", \"2.0\", or  \"2.1\"
+jsp.error.noFunctionPrefix=The function {0} must be used with a prefix when a default namespace is not specified
+jsp.error.noFunction=The function {0} cannot be located with the specified prefix
+jsp.error.noFunctionMethod=Method \"{0}\" for function \"{1}\" not found in class \"{2}\"
+jsp.error.function.classnotfound=The class {0} specified in TLD for the function {1} cannot be found: {2}
+jsp.error.signature.classnotfound=The class {0} specified in the method signature in TLD for the function {1} cannot be found. {2}
+jsp.error.text.has_subelement=&lt;jsp:text&gt; must not have any subelements
+jsp.error.data.file.read=Error reading file \"{0}\"
+jsp.error.prefix.refined=Attempt to redefine the prefix {0} to {1}, when it was already defined as {2} in the current scope.
+jsp.error.nested_jsproot=Nested &lt;jsp:root&gt;
+jsp.error.unbalanced.endtag=The end tag \"&lt;/{0}\" is unbalanced
+jsp.error.invalid.bean=The value for the useBean class attribute {0} is invalid.
+jsp.error.prefix.use_before_dcl=The prefix {0} specified in this tag directive has been previously used by an action in file {1} line {2}.
+
+jsp.exception=An exception occurred processing JSP page {0} at line {1}
+
+# JSP 2.1
+jsp.error.el.template.deferred=#{..} is not allowed in template text
+jsp.error.el.parse={0} : {1}
+jsp.error.page.invalid.deferredsyntaxallowedasliteral=Page directive: invalid value for deferredSyntaxAllowedAsLiteral
+jsp.error.tag.invalid.deferredsyntaxallowedasliteral=Tag directive: invalid value for deferredSyntaxAllowedAsLiteral
+jsp.error.page.conflict.deferredsyntaxallowedasliteral=Page directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral=Tag directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
+
+jsp.error.page.invalid.trimdirectivewhitespaces=Page directive: invalid value for trimDirectiveWhitespaces
+jsp.error.tag.invalid.trimdirectivewhitespaces=Tag directive: invalid value for trimDirectiveWhitespaces
+jsp.error.page.conflict.trimdirectivewhitespaces=Page directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces=Tag directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties
new file mode 100644
index 0000000..0cf9618
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties
@@ -0,0 +1,456 @@
+jsp.error.compiler = No hay compilador Java disponible
+# 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.
+# $Id$
+#
+# Default localized string information
+# Localized para Locale es_ES
+jsp.error.bad.servlet.engine = \u00A1Versi\u00F3n incorrecta del motor servlet\!
+jsp.error.no.scratch.dir = El motor JSP no tiene configurado un directorio de trabajo.\n\
+	\ A\u00F1ada "jsp.initparams\=scratchdir\=<dir-name>" \n\
+	\ en el fichero servlets.properties para este contexto.
+jsp.error.bad.scratch.dir = El directorio de trabajo especificado\: {0} no es utilizable.
+jsp.message.scratch.dir.is = El directorio de trabajo para el motor JSP es\: {0}
+jsp.message.parent_class_loader_is = El cargador de clases es\: {0}
+jsp.message.dont.modify.servlets = IMPORTANTE\: No modifique los servlets generados
+jsp.error.not.impl.comments = Error Interno\: Comments no implementado
+jsp.error.not.impl.directives = Error Interno\: Directives no implementado
+jsp.error.not.impl.declarations = Error Interno\: Declarations no implementado
+jsp.error.not.impl.expressions = Error Interno\: Expressions no implementado
+jsp.error.not.impl.scriptlets = Error Interno\: Scriptlets no implementado
+jsp.error.not.impl.usebean = Error Interno\: useBean no implementado
+jsp.error.not.impl.getp = Error Interno\: getProperty no implementado
+jsp.error.not.impl.setp = Error Interno\: setProperty no implementado
+jsp.error.not.impl.plugin = Error Interno\: plugin no implementado
+jsp.error.not.impl.forward = Error Interno\: forward no implementado
+jsp.error.not.impl.include = Error Interno\: include no implementado
+jsp.error.unavailable = JSP ha sido marcado como no disponible
+jsp.error.usebean.missing.attribute = useBean\: falta atributo id o est\u00E1 mal digitado
+jsp.error.usebean.missing.type = useBean ({0})\: Se debe de especificar atributo class o type\:
+jsp.error.usebean.duplicate = useBean\: Nombre de bean duplicado\: {0}
+jsp.error.usebean.prohibited.as.session = No puedo usar como bean de sesi\u00F3n {0} ya que est\u00E1 prohibido por directiva jsp definida previamente\: 
+jsp.error.usebean.not.both = useBean\: No puede especificar ambos atributos class y beanName\: 
+jsp.error.usebean.bad.type.cast = useBean ({0})\: Tipo ({1}) no es asignable desde clase ({2}) 
+jsp.error.invalid.scope = Valor ilegal de atributo 'scope'\: {0} (debe de ser uno de "page", "request", "session", o "application")
+jsp.error.classname = No pude determinar el nombre de clase desde el fichero .class
+jsp.error.outputfolder = no hay carpeta de salida
+jsp.warning.bad.type = Aviso\: tipo incorrecto en archivo .class
+jsp.error.data.file.write = Error mientras escrib\u00EDa el archivo de datos
+jsp.error.page.invalid.buffer = Directiva Page\: valor incorrecto para buffer
+jsp.error.page.conflict.contenttype = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'contentType' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.contenttype = Directiva Page\: valor incorrecto para contentType
+jsp.error.page.conflict.session = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'session' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.session = Directiva Page\: valor incorrecto para session
+jsp.error.page.conflict.buffer = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'buffer'con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.buffer = Directiva Page\: valor incorrecto para b\u00FAfer
+jsp.error.page.conflict.autoflush = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'autoFlush' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.autoflush = \=Directiva Page\: valor incorrecto para autoFlush
+jsp.error.page.conflict.isthreadsafe = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isThreadSafe' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.isthreadsafe = \=Directiva Page\: valor incorrecto para isThreadSafe
+jsp.error.page.conflict.info = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'info' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.info = \=Directiva Page\: valor incorrecto para info
+jsp.error.page.conflict.iserrorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isErrorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.iserrorpage = \=Directiva Page\: valor incorrecto para isErrorPage
+jsp.error.page.conflict.errorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'errorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.language = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.language = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.language.nonjava = Directiva Page\: atributo language incorrecto
+jsp.error.tag.language.nonjava = Directiva Tag\: atributo language incorrecto
+jsp.error.page.defafteruse.language = Directiva Page\: No puedo definir lenguage tras un scriptlet
+jsp.error.page.nomapping.language = Directiva Page\: No hay mapeado para language\: 
+jsp.error.page.conflict.extends = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'extends' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.iselignored = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.iselignored = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.iselignored = Directiva Page\: valor inv\u00E1lido para isELIgnored
+jsp.error.tag.invalid.iselignored = Directiva Tag\: valor incorrecto para isELIgnored
+jsp.error.page.multi.pageencoding = La directiva Page no debe de tener m\u00FAltiples ocurrencias de pageencoding
+jsp.error.tag.conflict.attr = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias del atributo "{0}" con valores distintos (viejo\: {1}, nuevo\: {2})
+jsp.error.tag.multi.pageencoding = La directiva Tag no debe de tener m\u00FAltiples ocurrencias de pageencoding
+jsp.error.page.bad_b_and_a_combo = Directiva Page\: Combinaci\u00F3n ilegal de buffer\="none" y autoFlush\="false"
+jsp.error.not.impl.taglib = Error Interno\: Tag extensions no implementado
+jsp.error.include.missing.file = No tiene argumento de nombre de fichero 
+jsp.error.include.bad.file = Argumento de nombre de fichero no v\u00E1lido
+jsp.error.include.exception = No se puede incluir {0}
+jsp.error.stream.closed = Stream cerrado
+jsp.error.invalid.forward = Tag forward no v\u00E1lido
+jsp.error.unknownException = \u00A1Error no caturado\!. Deber\u00EDas de considerar el poner una p\u00E1gina de error para avisar de los errores m\u00E1s elegantemente
+jsp.error.invalid.directive = Directiva no v\u00E1lida
+jsp.error.invalid.implicit = TLD impl\u00EDcito inv\u00E1lido para fichero de marca en {0}
+jsp.error.invalid.implicit.version = Versi\u00F3n inv\u00E1lida de JSP definida en TLD impl\u00EDcito para fichero de marca en {0}
+jsp.error.invalid.version = Versi\u00F3n inv\u00E1lida de JSP definida para fichero de marca en {0}
+jsp.error.directive.istagfile = La Directiva {0} no puede usarse en archivo de tag
+jsp.error.directive.isnottagfile = La Directiva {0} s\u00F3lo se puede usar en un archivo de tag
+jsp.error.tagfile.tld.name = El atributo "name" de la directiva tag tiene un valor {0} mientras que el tag "name" del elemento "tag-file" en el TLD es {1}
+jsp.error.action.istagfile = La acci\u00F3n {0} no se puede usar en un archivo tag
+jsp.error.action.isnottagfile = La acci\u00F3n {0} s\u00F3lo se puede usar en archivos tag
+jsp.error.unterminated = Tag {0} no terminado
+jsp.error.usebean.notinsamefile = El Tag useBean debe de empezar y terminar en el mismo archivo f\u00EDsico
+jsp.error.loadclass.taghandler = No se puede cargar la clase {0}
+jsp.error.unable.compile = No se puede compilar la clase para JSP
+jsp.error.unable.load = No se puede cargar la clase para JSP
+jsp.error.unable.rename = No se puede renombrar el archivo de clase {0} a {1}
+jsp.error.mandatory.attribute = {0}\: Falta atributo obligatorio {1}
+jsp.error.flush = Excepci\u00F3n sucedida al vaciar los datos
+jsp.engine.info = Motor Jasper JSP 2.1
+jsp.error.invalid.expression = "{0}" contiene expresiones incorrectas\: {1}
+jsp.error.invalid.attribute = {0}\: Atributo incorrecto, {1}
+jsp.error.usebean.class.notfound = Clase\: {0} no hallada
+jsp.error.file.cannot.read = No se puede leer el archivo\: {0}
+jsp.error.file.already.registered = El archivo {0} ya se ha visto, \u00BFpodr\u00EDa ser un include recursivo?
+jsp.error.file.not.registered = Archivo {0} not visto en include
+jsp.error.quotes.unterminated = Comillas no terminadas
+jsp.error.attr.quoted = El valor del atributo deber\u00EDa ir entre comillas
+jsp.error.attr.novalue = Atributo {0} no tiene valor
+jsp.error.tag.attr.unterminated = Lista de atributos del tag no terminada
+jsp.error.param.noname = No hay nombre en el tag PARAM
+jsp.error.param.novalue = No hay valor en el tag PARAM
+jsp.error.beans.nullbean = Se ha intentado una operaci\u00F3n de bean en un objeto nulo
+jsp.error.beans.nobeaninfo = No se puede encontrar BeanInfo para el bean  ''{0}'' seguramente la clase no existe
+jsp.error.beans.introspection = Una excepci\u00F3n ha tenido lugar mientras se le\u00EDa el m\u00E9todo de lectura de la propiedad ''{0}'' en un bean del tipo ''{1}''\:\n\
+	{2}
+jsp.error.beans.nomethod = No puedo encontrar un m\u00E9todo para leer la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.nomethod.setproperty = No puedo encontrar un m\u00E9todo para escribir la propiedad ''{0}'' en un bean del tipo ''{2}''
+jsp.error.beans.noproperty = No puedo encontrar informaci\u00F3n de la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.property.conversion = No puedo convertir cadena "{0}" a clase "{1}" para atributo "{2}"\: {3}
+jsp.error.beans.propertyeditor.notregistered = Editor de Propiedades no registrado con el PropertyEditorManager
+jsp.error.beans.setproperty.noindexset = No puedo poner la propiedad indexada
+jsp.error.include.tag = Tag jsp\:include no v\u00E1lido
+jsp.error.include.noflush = jsp\:include necesita tener "flush\=true"
+jsp.error.include.badflush = jsp\:include page\="..." flush\="true" es la \u00FAnica combinaci\u00F3n v\u00E1lida en JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer = Error\: Se ha intentado limpiar un buffer que ya hab\u00EDa sido escrito
+jsp.error.overflow = Error\:Buffer de JSP desbordado
+jsp.error.paramexpected = El tag "param" era esperado con los atributos "name" y "value" despu\u00E9s del tag "params".
+jsp.error.param.invalidUse = La acci\u00F3n jsp\:param no debe de ser usada fuera de los elementos jsp\:include, jsp\:forward o jsp\:params
+jsp.error.params.invalidUse = jsp\:params debe de ser un hijo directo de jsp\:plugin
+jsp.error.fallback.invalidUse = jsp\:fallback debe de ser un hijo directo de jsp\:plugin
+jsp.error.namedAttribute.invalidUse = jsp\:attribute debe de ser el subelemento de una acci\u00F3n est\u00E1ndar o de cliente
+jsp.error.jspbody.invalidUse = jsp\:body debe de ser el subelemento de una acci\u00F3n est\u00E1ndar o de cliente
+jsp.error.closeindividualparam = El tag param necesita ser cerrado con "/>"
+jsp.error.closeparams = El tag param necesita ser cerrado con /params
+jsp.error.params.emptyBody = jsp\:params debe de contener al menos un jsp\:param anidado
+jsp.error.params.illegalChild = jsp\:params no debe de contener elementos anidados que no sean jsp\:param
+jsp.error.plugin.notype = Tipo no declarado en jsp\:plugin
+jsp.error.plugin.badtype = Valor ilegal para atributo 'type' en jsp\:plugin\: debe de ser 'bean' o 'applet'
+jsp.error.plugin.nocode = C\u00F3digo no declarado en jsp\:plugin
+jsp.error.ise_on_clear = Es ilegal usar clear() cuando el tama\u00F1o del buffer es cero
+jsp.error.setproperty.beanNotFound = setProperty\: Bean {0} no encontrado
+jsp.error.getproperty.beanNotFound = getProperty\: Bean {0} no encontrado
+jsp.error.setproperty.ClassNotFound = setProperty\: clase {0} no encontrada
+jsp.error.javac = Excepci\u00F3n de Javac
+jsp.error.javac.env = Entorno
+jsp.error.compilation = Error compilando fichero\: {0} {1}
+jsp.error.setproperty.invalidSyntax = setproperty\: no puedo tener valor no nulo si la propiedad\=*
+jsp.error.setproperty.beanInfoNotFound = setproperty\: beanInfo para bean {0} no encontrado
+jsp.error.setproperty.paramOrValue = setProperty\: O param o value pueden estar presentes
+jsp.error.setproperty.arrayVal = setProperty\: No puede escribir en la propiedad de array {0} a trav\u00E9s de una valor de cadena literal
+jsp.warning.keepgen = Aviso\: valor incorrecto para el initParam keepgen. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.xpoweredBy = Aviso\: valor incorrecto para el initParam xpoweredBy. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.enablePooling = Aviso\: valor incorrecto para el initParam enablePooling. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.invalidTagPoolSize = Aviso\: valor incorrecto para el par\u00E1metro init llamado tagPoolSize. Se usar\u00E1 la medida por defecto de {0}
+jsp.warning.mappedFile = Aviso\: valor incorrecto para el initParam mappedFile. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.classDebugInfo = Aviso\: valor incorrecto para el initParam classdebuginfo. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.checkInterval = Aviso\: valor incorrecto para el initParam checkInterval. Se usar\u00E1 el valor por defecto de "300" segundos
+jsp.warning.modificationTestInterval = Aviso\: valor incorrecto para el initParam modificationTestInterval. Se usar\u00E1 el valor por defecto de "4" segundos
+jsp.warning.development = Aviso\: valor incorrecto para el initParam development. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.fork = Aviso\: valor incorrecto para el initParam fork. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.reloading = Aviso\: valor incorrecto para el initParam reloading. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.dumpSmap = Aviso\: valor incorrecto para el initParam dumpSmap. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.genchararray = Aviso\: valor incorrecto para el initParam genStrAsCharArray. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.suppressSmap = Aviso\: valor incorrecto para el initParam suppressSmap. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.displaySourceFragment = Aviso\: valor incorrecto para el initParam displaySourceFragment. Se usar\u00E1 el valor por defecto de "verdadero"
+jsp.error.badtaglib = No se puede abrir la biblioteca de tags {0}\: {1}
+jsp.error.badGetReader = No se puede crear un reader cuando el stream no tiene buffer
+jsp.warning.unknown.element.in.taglib = Elemento desconocido ({0}) en taglib
+jsp.warning.unknown.element.in.tag = Elemento desconocido ({0}) en tag
+jsp.warning.unknown.element.in.tagfile = Elemento desconocido ({0}) en tag-file
+jsp.warning.unknown.element.in.attribute = Elemento desconocido ({0}) en attribute
+jsp.warning.unknown.element.in.variable = Elemento desconocido ({0}) en variable
+jsp.warning.unknown.element.in.validator = Elemento desconocido ({0}) en validator
+jsp.warning.unknown.element.in.initParam = Elemento desconocido ({0}) en init-param de validator
+jsp.warning.unknown.element.in.function = Elemento desconocido ({0}) en function
+jsp.error.more.than.one.taglib = M\u00E1s de una biblioteca de tags en el TLD\: {0}
+jsp.error.teiclass.instantiation = No se puede cargar la clase TagExtraInfo llamada\: {0}
+jsp.error.non_null_tei_and_var_subelems = Tag {0} tiene uno o m\u00E1s subelementos variable y una clase TagExtraInfo que devuelve una o m\u00E1s VariableInfo
+jsp.error.parse.error.in.TLD = Error de an\u00E1lisis en el descriptor de biblioteca de tags\: {0}
+jsp.error.unable.to.open.TLD = No se puede abrir el descriptor de biblioteca de tags\: {0}
+jsp.buffer.size.zero = Tama\u00F1o de buffer <\= 0
+jsp.error.file.not.found = Archivo JSP "{0}" no encontrado
+jsp.message.copyinguri = Copiando {0} en {1}
+jsp.message.htmlcomment = \n\
+	Quitando comentario\: \t{0}
+jsp.message.handling_directive = \n\
+	Resolviendo directiva\: {0}\t{1}
+jsp.message.handling_plugin = \n\
+	Plugin\: {0}
+jsp.message.package_name_is = El Nombre del Package es\: {0}
+jsp.message.class_name_is = El Nombre de la clase es\: {0}
+jsp.message.java_file_name_is = El Nombre del Archivo Java es\: {0}
+jsp.message.class_file_name_is = El Nombre del Archivo de clase es\: {0}
+jsp.message.accepted = Acept\u00F3 {0} en {1}
+jsp.message.adding_jar = A\u00F1adiendo jar {0} a mi classpath
+jsp.message.compiling_with = Compilado con\: {0}
+jsp.message.template_text = texto plantilla
+jsp.error.missing_attribute = De acuerdo con el TLD el atributo {0} es obligatorio para el tag {1}
+jsp.error.bad_attribute = El atributo {0} no es v\u00E1lido seg\u00FAn el TLD especificado
+jsp.error.tld.unable_to_read = Imposible de leer TLD "{1}" desde archivo JAR "{0}"\: {2}
+jsp.error.tld.unable_to_get_jar = Imposible obtener recurso JAR "{0}" conteniendo TLD\: {1}
+jsp.error.tld.missing_jar = Falta recurso JAR "{0}" conteniendo TLD
+jsp.error.webxml_not_found = No puedo localizar web.xml
+jsp.cmd_line.usage = Uso\: jsptoservlet [-dd <ruta/a/DirectorioSalida>] [-keepgenerated] <Archivos .jsp>
+jsp.message.cp_is = Classpath {0} es\: {1}
+jsp.error.unable.to_load_taghandler_class = No se puede cargar clase manejadora {0} del tag a causa de {1}
+jsp.error.unable.to_find_method = No se puede encontrar el m\u00E9todo de escritura para el atributo\: {0}
+jsp.error.unable.to_convert_string = No pude convertir un String a {0} para atributo {1}
+jsp.error.unable.to_introspect = No se puede hacer introspecci\u00F3n en manejador de tag clase\: {0} a causa de {1}
+jsp.error.bad_tag = No existe el tag {0} en la biblioteca importada con prefijo {1}
+jsp.error.xml.bad_tag = No se ha definido el tag "{0}" en la biblioteca tag asociada con uri "{1}"
+jsp.error.bad_string_Character = No puede extraer un Character desde un array de tama\u00F1o cero
+jsp.error.bad_string_char = No puede extraer un char desde un array de tama\u00F1o cero
+jsp.warning.compiler.class.cantcreate = No puedo crear una instancia de la clase especificada {0} de plugin del compilador debido a {1}. Se usar\u00E1 el compilador Java de Sun.
+jsp.warning.compiler.class.notfound = No puedo encontrar una instancia de la clase {0} de plugin de compilador. Se usar\u00E1 el compilador del Java de Sun.
+jsp.warning.compiler.path.notfound = Trayectoria del compilador especificado {0} no encontrada. Se usar\u00E1 el PATH del sistema.
+jsp.error.jspc.uriroot_not_dir = La opci\u00F3n -uriroot debe de especificar un directorio ya existente
+jsp.error.jspc.missingTarget = Falta target\: Debe de especificar -webapp o -uriroot o una o m\u00E1s p\u00E1ginas JSP
+jsp.error.jspc.no_uriroot = No se ha especificado uriroot y no puede ser localizado en los archivos JSP especificados
+jspc.implicit.uriRoot = uriRoot implicitamente puesto a "{0}"
+jspc.usage = Uso\: jspc <opciones> [--] <Archivos JSP>\n\
+	donde <Archivos JSP> son\:\n\
+	\    -webapp <dir>      Un directorio conteniendo una web-app. Todas las\n\
+	\                       p\u00E1ginas jsp ser\u00E1n compiladas recursivamente\n\
+	o cualquier n\u00FAmero de\n\
+	\    <Archivo>          Un Archivo para ser interpretado como una p\u00E1gina jsp\n\
+	y donde <opciones> incluyen\:\n\
+	\    -help              Muestra este mensaje de ayuda\n\
+	\    -v                 Modo detallado\n\
+	\    -d <dir>           Directorio de salida\n\
+	\    -l                 Muestra el nombre de la p\u00E1gina JSP al ocurrir un fallo\n\
+	\    -s                 Muestra el nombre de la p\u00E1gina JSP al tener \u00E9xito\n\
+	\    -p <name>          Nombre del package objetivo\n\
+	\                       (por defecto org.apache.jsp)\n\
+	\    -c <name>          Nombre de la clase objetivo\n\
+	\                       (s\u00F3lo se aplica a la primera p\u00E1gina JSP)\n\
+	\    -mapped            Genera llamadas separadas a write() para cada l\u00EDnea de\n\
+	\                       HTML en el JSP\n\
+	\    -die[\#]            Genera un c\u00F3digo de retorno de error (\#) en errores\n\
+	\                       fatales. (por defecto 1).\n\
+	\    -uribase <dir>     El directorio uri de donde deben de partir las\n\
+	\                       compilaciones. (por defecto "/")\n\
+	\    -uriroot <dir>     Igual que -webapp\n\
+	\    -compile           Compila los servlets generados\n\
+	\    -webinc <file>     Crea unos mapeos parciales de servlet en el archivo\n\
+	\    -webxml <file>     Crea un web.xml completo en el archivo.\n\
+	\    -ieplugin <clsid>  Java Plugin classid para Internet Explorer\n\
+	\    -classpath <path>  Pasa por alto la propiedad de sistema java.class.path\n\
+	\    -xpoweredBy        A\u00F1ade cabecera de respuesta  X-Powered-By\n\
+	\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+	\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+	\    -source <version>   Set the -source argument to the compiler (default 1.4)\n\
+	\    -target <version>   Set the -target argument to the compiler (default 1.4)\n
+jspc.webxml.header = <?xml version\="1.0" encoding\="ISO-8859-1"?>\n\
+	\n\
+	<\!DOCTYPE web-app\n\
+	\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+	\    "http\://java.sun.com/dtd/web-app_2_3.dtd">\n\
+	<\!--\n\
+	Creado automaticamente mediante Apache Jakarta Tomcat JspC.\n\
+	-->\n\
+	<web-app>\n\
+	\n
+jspc.webxml.footer = \n\
+	</web-app>\n\
+	\n
+jspc.webinc.header = \n\
+	<\!--\n\
+	Creado automaticamente mediante Apache Jakarta Tomcat JspC.\n\
+	Coloque este fragmento en el fichero web.xml antes de \n\
+	todos los elementos\: icon, display-name, description, \n\
+	distributable y context-param .\n\
+	-->\n
+jspc.webinc.footer = \n\
+	<\!--\n\
+	Los Elementos\: session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+	resource-ref, security-constraint, login-config, security-role,\n\
+	env-entry y ejb-ref deber\u00E1n de ir despu\u00E9s de este fragmento .\n\
+	-->\n
+jspc.webinc.insertEnd = <\!-- Fin de mapeos de servlet JSPC -->
+jspc.webinc.insertStart = <\!-- Inicio de mapeos de servlet JSPC -->
+jspc.error.jasperException = error-el archivo ''{0}'' ha generado la excepci\u00F3n de sint\u00E1xis siguiente\: {1}
+jspc.error.generalException = ERROR-el archivo ''{0}'' ha generado la excepci\u00F3n general siguiente\:
+jspc.error.fileDoesNotExist = El archivo ''{0}'' utilizado como argumento no existe.
+jspc.error.emptyWebApp = -webapp necesita un argumento de archivo
+jsp.error.library.invalid = La p\u00E1gina JSP es incorrecta de acuerdo a la biblioteca {0}\: {1}
+jsp.error.tlvclass.instantiation = No pude cargar o instanciar clase TagLibraryValidator\: {0}
+jsp.error.tlv.invalid.page = Mensajes de error de validaci\u00F3n desde TagLibraryValidator para {0} in {1}
+jsp.error.tei.invalid.attributes = Mensajes de error de validaci\u00F3n desde TagExtraInfo para {0}
+jsp.parser.sax.propertynotsupported = Propiedad SAX no soportada\: {0}
+jsp.parser.sax.propertynotrecognized = Propiedad SAX no reconocida\: {0}
+jsp.parser.sax.featurenotsupported = Caracter\u00EDstica SAX no soportada\: {0}
+jsp.parser.sax.featurenotrecognized = Caracter\u00EDstica SAX no reconocida\: {0}
+jsp.error.no.more.content = Alcanzado fin de contenido mietras se requer\u00EDa m\u00E1s an\u00E1lisis\: \u00BFerror de anidamiento de tag?
+jsp.error.parse.xml = Error de an\u00E1lisis XML en archivo {0}
+jsp.error.parse.xml.line = Error de an\u00E1lisis XML en archivo {0}\: (l\u00EDnea {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body = El cuerpo de elemento {0} no debe de contener elementos XML
+jsp.error.internal.tldinit = No pude inicializar TldLocationsCache\: {0}
+jsp.error.internal.filenotfound = Error Interno\: Archivo {0} no hallado
+jsp.error.internal.evaluator_not_found = Error interno\: no pude cargar evaluador de expresiones
+jsp.error.parse.xml.invalidPublicId = PUBLIC ID incorrecta\: {0}
+jsp.error.include.flush.invalid.value = Valor incorrecto para atributo flush\: {0}
+jsp.error.unsupported.encoding = Codificaci\u00F3n no soportada\: {0}
+tld.error.variableNotAllowed = Es un error para un tag, que tiene uno o m\u00E1s subelementos variables, el tener una clase TagExtraInfo que devuelve un objeto no nulo.
+jsp.error.tldInWebDotXmlNotFound = No pude localizar TLD {1} para URI {0} especificado en web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved = La uri absoluta\: {0} no puede resolverse o en web.xml o el los archivos jar desplegados con esta aplicaci\u00F3n
+jsp.error.taglibDirective.missing.location = No se ha especificado ni el atributo 'uri' ni el 'tagdir'
+jsp.error.taglibDirective.both_uri_and_tagdir = Se han especificado ambos atributos 'uri' y 'tagdir'
+jsp.error.invalid.tagdir = El directorio de archivo Tag {0} no comienza con "/WEB-INF/tags"
+jsp.error.unterminated.user.tag = Tag definido por usuario no terminado\: tag final {0} no hallado o anidado incorrectamente
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata = Error de Validaci\u00F3n\: El Elemento &lt;{0}&gt; no puede tener datos plantilla. Los datos plantilla deben de estar encapsulados dentro de un elemento &lt;jsp\:text&gt;. [JSP1.2 PFD secci\u00F3n 5.1.9]\n\
+	Datos de Plantilla en error\: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix = El prefijo taglib {0} est\u00E1 reservado
+jsp.error.invalid.javaEncoding = Codificaciones java incorrectas. Intent\u00E9 {0} y luego {1}. Ambas fallaron.
+jsp.error.needAlternateJavaEncoding = La codificaci\u00F3n java por defecto {0} es incorrecta en tu plataforma java. Se puede especificar una alternativa v\u00EDa par\u00E1metro 'javaEncoding' de JspServlet.
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number = Ha tenido lugar un error en la l\u00EDnea\: {0} en el archivo jsp\: {1}
+jsp.error.multiple.line.number = \n\
+	\n\
+	Ha tenido lugar un error entre las l\u00EDneas\: {0} y {1} en el archivo jsp\: {2}\n\
+	\n
+jsp.error.java.line.number = Ha tenido lugar un error en la l\u00EDnea\: {0} en el fichero java generado
+jsp.error.corresponding.servlet = Error de servlet generado\:\n
+jsp.error.empty.body.not.allowed = Cuerpo vac\u00EDo no permitido para {0}
+jsp.error.jspbody.required = Se debe de usar jsp\:body para especificar cuerpo tag para {0} si se usa jsp\:attribute.
+jsp.error.jspbody.emptybody.only = El tag {0} s\u00F3lo puede tener jsp\:attribute en su cuerpo.
+jsp.error.no.scriptlets = Los elementos de Scripting (&lt;%\!, &lt;jsp\:declaration, &lt;%\=, &lt;jsp\:expression, &lt;%, &lt;jsp\:scriptlet ) no est\u00E1n permitidos aqu\u00ED.
+jsp.error.internal.unexpected_node_type = Error Interno\: Encontrado tipo de nodo inesperado
+jsp.error.tld.fn.invalid.signature = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}
+jsp.error.tld.fn.duplicate.name = Nombre duplicado de funci\u00F3n {0} en biblioteca de tag {1}
+jsp.error.tld.fn.invalid.signature.commaexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Coma ','. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Par\u00E9ntesis '('. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
+jsp.error.tld.mandatory.element.missing = Falta o est\u00E1 vac\u00EDo elemento TLD obligatorio\: {0}
+jsp.error.dynamic.attributes.not.implemented = El tag {0} declara que acepta atributos din\u00E1micos pero no implementa la interfaz requerida
+jsp.error.nomatching.fragment = No puedo hallar una directiva de atributo (con name\={0} y fragment\=true) antes de la directiva de fragment.
+jsp.error.attribute.noequal = se esperaba s\u00EDmbolo igual
+jsp.error.attribute.noquote = se esperaba s\u00EDmbolo comillas
+jsp.error.attribute.unterminated = el atributo para {0} no est\u00E1 terminado correctamente
+jsp.error.attribute.noescape = El valor de atributo {0} est\u00E1 entrecomillado con {1} que debe de usar escape al usarse dentro del valor
+jsp.error.missing.tagInfo = El objeto TagInfo para {0} falta del TLD
+jsp.error.deferredmethodsignaturewithoutdeferredmethod = No puedo especificar firma de m\u00E9todo si 'deferredMethod' no es 'verdadero'
+jsp.error.deferredvaluetypewithoutdeferredvalue = No puedo especificar un tipo de valor si 'deferredValue' no es 'verdadero'
+jsp.error.deferredmethodandvalue = 'deferredValue' y 'deferredMethod' no pueden ser ambos 'verdadero'
+jsp.error.fragmentwithtype = No puede especificar ambos atributos 'fragment' y 'type'. Si est\u00E1 presente 'fragment', 'type' se pone como 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue = No puede especificar ambos atributos 'fragment' y 'rtexprvalue'.  Si est\u00E1 presente 'fragment', 'rtexprvalue' se pone como 'true'
+jsp.error.fragmentWithDeclareOrScope = Ambos atributos 'fragment' y 'declare' o 'scope' se han especificado en la directiva variable
+jsp.error.var_and_varReader = S\u00F3lo se puede especificar uno de 'var' o 'varReader'
+jsp.error.missing_var_or_varReader = Falta atributo 'var' o 'varReader'
+jsp.warning.bad.urlpattern.propertygroup = Valor malo {0} en el subelemento url-pattern en web.xml
+jsp.error.unknown_attribute_type = Tipo de atributo desconocido ({1}) para atributo {0}.
+jsp.error.coerce_to_type = No puedo coaccionar el valor ({2}) a tipo ({1}) para atributo {0}.
+jsp.error.jspelement.missing.name = Falta atributo obligatorio XML-style 'name'
+jsp.error.xmlns.redefinition.notimplemented = Error interno\: Intento de redefinir xmlns\:{0}. La redefinici\u00F3n de espacios de nombre no est\u00E1 implementada.
+jsp.error.could.not.add.taglibraries = No pude a\u00F1adir una o m\u00E1s bibliotecas.
+jsp.error.duplicate.name.jspattribute = El atributo {0} especificado en la acci\u00F3n standard o custom tambi\u00E9n aparece como el valor del atributo name en jsp\:attribute
+jsp.error.not.in.template = {0} no permitido en una plantilla cuerpo de texto.
+jsp.error.badStandardAction = Acci\u00F3n est\u00E1ndar incorrecta
+jsp.error.xml.badStandardAction = Acci\u00F3n est\u00E1ndar incorrecta\: {0}
+jsp.error.tagdirective.badbodycontent = body-content incorrecto ({0}) en directiva tag
+jsp.error.simpletag.badbodycontent = El TLD para la clase {0} especifica un body-content es incorrecto (JSP) para un SimpleTag.
+jsp.error.config_pagedir_encoding_mismatch = El Page-encoding especificado en jsp-property-group ({0}) es diferente del especificado en la diectiva page ({1})
+jsp.error.prolog_pagedir_encoding_mismatch = El Page-encoding especificado en XML prolog ({0}) difiere del especificado en la directiva page ({1})
+jsp.error.prolog_config_encoding_mismatch = El Page-encoding especificado en XML prolog ({0}) difiere del especificado en jsp-property-group ({1})
+jsp.error.attribute.custom.non_rt_with_expr = Seg\u00FAn el TLD o la directiva attribute del archivo tag, el atributo {0} no acepta expresiones
+jsp.error.attribute.standard.non_rt_with_expr = El atributo {0} de la acci\u00F3n est\u00E1ndar {1} no acepta expresiones
+jsp.error.scripting.variable.missing_name = Imposible determinar nombre de variable de scripting desde atributo {0}
+jasper.error.emptybodycontent.nonempty = Seg\u00FAn el TLD, el tag {0} debe de estar vac\u00EDo, pero no lo est\u00E1
+jsp.error.tagfile.nameNotUnique = El valor de {0} y el valor de {1} en la l\u00EDnea {2} son el mismo.
+jsp.error.tagfile.nameFrom.noAttribute = No puedo hallar una directiva attribute con un atributo name con un valor "{0}", el valor de este atributo name-from-attribute.
+jsp.error.tagfile.nameFrom.badAttribute = La directiva attribute (declarada en la l\u00EDnea {1} y cuyo atributo name es "{0}", el valor de este atributo name-from-attribute attribute) debe de ser del tipo java.lang.String, es "required" y no un "rtexprvalue".
+jsp.error.page.noSession = No puedo acceder al \u00E1mbito de sesi\u00F3n en una p\u00E1gina que no participa en una sesi\u00F3n
+jsp.error.usebean.noSession = Es ilegal para useBean el usar \u00E1mbito de sesi\u00F3n cuando la p\u00E1gina JSP declara (v\u00EDa directiva de p\u00E1gina) que no participa en sesiones
+jsp.error.xml.encodingByteOrderUnsupported = El orden de byte dado para encoding "{0}" no est\u00E1 soportado
+jsp.error.xml.encodingDeclInvalid = Nombre de codificaci\u00F3n "{0}" incorrecto.
+jsp.error.xml.encodingDeclRequired = Se necesita la declaraci\u00F3n encoding en la declaraci\u00F3n de texto
+jsp.error.xml.morePseudoAttributes = se esperan m\u00E1s pseudo-atributos
+jsp.error.xml.noMorePseudoAttributes = no se permiten m\u00E1s pseudo-atributos.
+jsp.error.xml.versionInfoRequired = Se requiere la versi\u00F3n en la declaraci\u00F3n XML.
+jsp.error.xml.xmlDeclUnterminated = La declaraci\u00F3n XML debe de terminar con "?>".
+jsp.error.xml.reservedPITarget = La instrucci\u00F3n de procesamiento que coincide con "[xX][mM][lL]" no est\u00E1 permitida.
+jsp.error.xml.spaceRequiredInPI = Se necesita un espacio en blanco entre la instrucci\u00F3n de procesamiento y los datos.
+jsp.error.xml.invalidCharInContent = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en el contenido del elemento del documento.
+jsp.error.xml.spaceRequiredBeforeStandalone = Se necesita un espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n XML.
+jsp.error.xml.sdDeclInvalid = El valor de declaraci\u00F3n de documento standalone debe de ser "yes" o "no", no "{0}".
+jsp.error.xml.invalidCharInPI = Se hall\u00F3 un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) en la instrucci\u00F3n de procesamiento
+jsp.error.xml.versionNotSupported = No se soporta la versi\u00F3n XML "{0}", s\u00F3lo se soporta XML 1.0
+jsp.error.xml.pseudoAttrNameExpected = se esperaba un pseudo-atributo name.
+jsp.error.xml.expectedByte = Se esperaba byte {0} de {1}-byte de secuencia UTF-8.
+jsp.error.xml.invalidByte = Incorrecto byte {0} de {1}-byte de secuencia UTF-8.
+jsp.error.xml.operationNotSupported = La operaci\u00F3n "{0}" no est\u00E1 soportada por lector {1}.
+jsp.error.xml.invalidHighSurrogate = Surrogaci\u00F3n Alta de bits en secuencia UTF-8 no debe de exceder 0x10, pero se hall\u00F3 0x{0}.
+jsp.error.xml.invalidASCII = El Byte "{0}" no es ASCII de 7-bit.
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = Se necesita espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n XML.
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = Se necesita espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n text.
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = Se necesita espacio en blanco antes del pseudo-atributo version en la declaraci\u00F3n text.
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = Se necesita espacio en blanco antes del pseudo-atributo version en la declaraci\u00F3n XML.
+jsp.error.xml.eqRequiredInXMLDecl = El car\u00E1cter '' \= '' debe de serguir a "{0}" en la declaraci\u00F3n XML.
+jsp.error.xml.eqRequiredInTextDecl = El car\u00E1cter '' \= '' debe de serguir a "{0}" en la declaraci\u00F3n text.
+jsp.error.xml.quoteRequiredInTextDecl = El valor que sigue a "{0}" en la declaraci\u00F3n text debe de ser una cadena entre comillas.
+jsp.error.xml.quoteRequiredInXMLDecl = El valor que sigue a "{0}" en la declaraci\u00F3n XML debe de ser un cadena entre comillas.
+jsp.error.xml.invalidCharInTextDecl = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en la declaraci\u00F3n text
+jsp.error.xml.invalidCharInXMLDecl = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en la declaraci\u00F3n XML
+jsp.error.xml.closeQuoteMissingInTextDecl = Faltan las comillas de cierre en el valor que sigue a "{0}" en la declaraci\u00F3n text.
+jsp.error.xml.closeQuoteMissingInXMLDecl = Faltan las comillas de cierre en el valor que sigue a  "{0}" en la declaraci\u00F3n XML.
+jsp.error.xml.invalidHighSurrogate = Los bits de surrogaci\u00F3n alta en secuencai UTF-8 no deben de exceder 0x10 pero se hall\u00F3 0x{0}.
+jsp.error.multiple.jsp = No puedo tener m\u00FAltiples especificaciones de
+jsp.error.jspoutput.conflict = &lt;jsp\:output&gt;\: ilegal tener ocurrencias m\u00FAltiples de "{0}" con diferentes valores (viejo\: {1}, nuevo\: {2})
+jsp.error.jspoutput.doctypenamesystem = &lt;jsp\:output&gt;\: atributos 'doctype-root-element' y 'doctype-system' deben de aparecer juntos
+jsp.error.jspoutput.doctypepulicsystem = &lt;jsp\:output&gt;\: atributo 'doctype-system' debe de aparecer si aparece atributo 'doctype-public'
+jsp.error.jspoutput.nonemptybody = &lt;jsp\:output&gt; no debe de tener un cuerpo
+jsp.error.jspoutput.invalidUse = &lt;jsp\:output&gt; no se debe de usar en sint\u00E1xis est\u00E1ndar
+jsp.error.attributes.not.allowed = {0} no debe de tener atributos
+jsp.error.tagfile.badSuffix = Falta sufijo ".tag" en trayectoria de archivo de tag {0}
+jsp.error.tagfile.illegalPath = Trayectoria de archivo de tag\: {0}, debe de comenzar con "/WEB-INF/tags" o "/META-INF/tags"
+jsp.error.plugin.wrongRootElement = El nombre del elemento ra\u00EDz en {0} difiere de {1}
+jsp.error.attribute.invalidPrefix = El prefijo de atributo {0} no se correponde con ninguna biblioteca importada
+jsp.error.nested.jspattribute = Una acci\u00F3n est\u00E1ndar jsp\:attribute no puede estar anidada dentro de otra acci\u00F3n est\u00E1ndar jsp\:attribute
+jsp.error.nested.jspbody = Una acci\u00F3n est\u00E1ndar jsp\:body no puede estar anidada dentro de otra acci\u00F3n est\u00E1ndar jsp\:body o jsp\:attribute
+jsp.error.variable.either.name = O el atributo name-given o name-from-attribute deben de ser especificados en una directiva variable
+jsp.error.variable.both.name = No se puede especificar ambos atributos name-given o name-from-attribute en una directiva variable
+jsp.error.variable.alias = Ambos atributos o ninguno de name-from-attribute y alias pueden ser especificados en una directiva variable
+jsp.error.attribute.null_name = Nombre de atributo nulo
+jsp.error.jsptext.badcontent = '&lt;', cuando aparece en el cuerpo de &lt;jsp\:text&gt;, debe de estar encapsulado dentro de un CDATA
+jsp.error.jsproot.version.invalid = N\u00FAmero incorrecto de versi\u00F3n\: "{0}", debe de ser "1.2" o "2.0" o "2.1"
+jsp.error.noFunctionPrefix = La funci\u00F3n {0} debe de usarse con un prefijo cuando no se especifica un espacio de nombres por defecto
+jsp.error.noFunction = La funci\u00F3n {0} no puede ser localizada mediante el prefijo especificado
+jsp.error.noFunctionMethod = El m\u00E9todo "{0}" para la funci\u00F3n "{1}" no se pudo hallar en la clase "{2}"
+jsp.error.function.classnotfound = La clase {0} especificada en el TLD para la funci\u00F3n {1} no se puede hallar\: {2}
+jsp.error.signature.classnotfound = La clase {0} especificada en la firma del m\u00E9todo en el TLD para la funci\u00F3n {1} no se puede hallar. {2}
+jsp.error.text.has_subelement = &lt;jsp\:text&gt; no debe de tener subelementos
+jsp.error.data.file.read = Error leyendo archivo "{0}"
+jsp.error.prefix.refined = Intento de redefinir el prefijo {0} por {1}, cuando ya estaba definido como {2} en el \u00E1mbito en curso.
+jsp.error.nested_jsproot = &lt;jsp\:root&gt; anidado
+jsp.error.unbalanced.endtag = El tgag final "&lt;/{0}" est\u00E1 desequilibrado
+jsp.error.invalid.bean = El valor el atributo de clsae useBean {0} es inv\u00E1lido.
+jsp.error.prefix.use_before_dcl = El prefijo {0} especificado en esta directiva de marca ha sido usado previamente mediante un fichero de acci\u00F3n {1} l\u00EDnea {2}.
+jsp.exception = Ha sucedido una excepci\u00F3n al procesar la p\u00E1gina JSP {0} en l\u00EDnea {1}
+jsp.error.el.template.deferred = \#{..} no est\u00E1 permitido en texto de plantilla
+jsp.error.el.parse = {0} \: {1}
+jsp.error.page.invalid.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
+jsp.error.tag.invalid.deferredsyntaxallowedasliteral = Directiva de marca\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
+jsp.error.page.conflict.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.trimdirectivewhitespaces = Directiva de p\u00E1gina\: valor inv\u00E1lido para trimDirectiveWhitespaces
+jsp.error.tag.invalid.trimdirectivewhitespaces = Directiva de marca\: valor inv\u00E1lido para trimDirectiveWhitespaces
+jsp.error.page.conflict.trimdirectivewhitespaces = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties
new file mode 100644
index 0000000..28c68e8
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties
@@ -0,0 +1,319 @@
+# 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.
+
+# $Id$
+#
+# Default localized string information
+# Localized this the Default Locale as is fr_FR
+
+jsp.error.bad.servlet.engine=Version de moteur de servlet incorrecte!
+jsp.error.no.scratch.dir=Le moteur de JSP engine n''est pas configuré avec un répertoire de travail.\
+\n Merci d''ajouter \"jsp.initparams=scratchdir=<dir-name>\" \
+\n dans le fichier "servlets.properties" de ce contexte.
+jsp.error.bad.scratch.dir=Le paramêtre "scratchDir" que vous avez spécifié: {0} est inutilisable.
+jsp.message.scratch.dir.is=Le répertoire de travail (scratch dir) pour le moteur de JSP est: {0}
+jsp.message.parent_class_loader_is=Le chargeur de classe parent (class loader) est: {0}
+jsp.message.dont.modify.servlets=IMPORTANT: Ne pas modifier les servlets générées
+jsp.error.not.impl.comments=Erreur interne: Commentaires non implémentés
+jsp.error.not.impl.directives=Erreur interne: Directives non implémentées
+jsp.error.not.impl.declarations=Erreur interne: Declarations non implémentées
+jsp.error.not.impl.expressions=Erreur interne: Expressions non implémentées
+jsp.error.not.impl.scriptlets=Erreur interne: Scriptlets non implémentés
+jsp.error.not.impl.usebean=Erreur interne: useBean non implémenté
+jsp.error.not.impl.getp=Erreur interne: getProperty non implémenté
+jsp.error.not.impl.setp=Erreur interne: setProperty non implémenté
+jsp.error.not.impl.plugin=Erreur interne: plugin non implémenté
+jsp.error.not.impl.forward=Erreur interne: forward non implémenté
+jsp.error.not.impl.include=Erreur interne: include non implémenté
+jsp.error.unavailable=La JSP a été marquée comme non disponible
+jsp.error.usebean.missing.attribute=useBean: l''identificateur d''attribut (id attribute) est manquant ou mal orthographié
+jsp.error.usebean.missing.type=useBean ({0}): La classe ou le type d''attribut doit être\
+spécifié: 
+jsp.error.usebean.duplicate=useBean: Nom de bean dupliqué: {0}
+jsp.error.usebean.prohibited.as.session=Impossible d''utiliser comme bean de session {0} car c''est interdit\
+par la directive jsp définie précédemment: 
+jsp.error.usebean.not.both=useBean: Impossible de spécifier à la fois la classe et l''attribut beanName: 
+jsp.error.usebean.bad.type.cast=useBean ({0}): Le type ({1}) n''est pas assignable depuis la classe ({2}) 
+jsp.error.classname=Impossible de déterminer le nom de classe d''après le fichier .class
+jsp.warning.bad.type=Attention: mauvais type dans le fichier .class
+jsp.error.data.file.write=Erreur lors de l''écriture du fichier de données
+#Directive de Page: valeur incorrecte pour pageEncoding
+jsp.error.page.invalid.contenttype=Directive de Page: valeur incorrecte pour contentType
+jsp.error.page.invalid.session=Directive de Page: valeur incorrecte pour session
+jsp.error.page.invalid.buffer=Directive de Page: valeur incorrecte pour "buffer"
+jsp.error.page.invalid.autoflush=Directive de Page: valeur incorrecte pour autoFlush
+jsp.error.page.invalid.isthreadsafe=Directive de Page: valeur incorrecte pour isThreadSafe
+jsp.error.page.invalid.info=Directive de Page: valeur incorrecte pour info
+jsp.error.page.invalid.iserrorpage=Directive de Page: valeur incorrecte pour isErrorPage
+jsp.error.page.defafteruse.language=Directive de Page: on ne peut définir language après un scriptlet 
+jsp.error.page.nomapping.language=Directive de Page: Pas de correspondance pour language: 
+jsp.error.page.bad_b_and_a_combo=Directive de Page: combinaison illégale de buffer=\"none\" && autoFlush=\"false\"
+jsp.error.not.impl.taglib=Internal error: Tag extensions non implémentés
+jsp.error.include.missing.file=l''argument fichier (file) pour l''inclusion (include) est absent
+jsp.error.include.bad.file=Mauvais argument fichier (file) pour l''inclusion (include)
+jsp.error.include.exception=Impossible d''inclure (include) {0}
+jsp.error.stream.closed=Flux fermé
+jsp.error.invalid.forward=Tag forward incorrect
+jsp.error.unknownException=Erreur non traitée! Vous devriez penser à utiliser une page d''erreur \
+pour rapporter ce type d''erreur plus élégamment
+jsp.error.invalid.directive=Directive incorrecte
+jsp.error.directive.istagfile=La directive {0} ne peut être utilisée dans un fichier tag
+jsp.error.directive.isnottagfile=La directive {0} ne peut être utilisée que dans un fichier tag
+jsp.error.tagfile.tld.name=L''attribut \"name\" de la directive tag contient la valeur {0} alors que le tag \"name\" de l''élément \"tag-file\" dans le TLD est {1}
+jsp.error.action.istagfile=L''action {0} ne peut être utilisée dans un fichier tag
+jsp.error.action.isnottagfile=L''action {0} ne peut être utilisée que dans un fichier tag
+jsp.error.unterminated=Tag {0} non terminé
+jsp.error.usebean.notinsamefile=le tag useBean doit commencé et finir dans le même fichier physique
+jsp.error.loadclass.taghandler=Impossible de charger la classe {0}
+jsp.error.unable.compile=Impossible de compiler la classe pour la JSP
+jsp.error.unable.load=Impossible de charger la classe pour la JSP
+jsp.error.unable.rename=Impossible de renommer le fichier classe de {0} vers {1}
+jsp.error.mandatory.attribute={0}: L''attribut obligatoire {1} est manquant
+jsp.engine.info=Moteur Jasper JSP 2.0
+jsp.error.invalid.expression="{0}" contient d''incorrecte(s) expression(s): {1}
+jsp.error.invalid.attribute={0}: Attribut incorrect: {1}
+jsp.error.usebean.class.notfound=Classe: {0} non trouvée
+jsp.error.file.cannot.read=Impossible de lire le fichier: {0}
+jsp.error.file.already.registered=Inclusion récursive du fichier {0}
+jsp.error.file.not.registered=Le fichier {0} n''apparaît pas dans l''inclusion (include)
+jsp.error.quotes.unterminated=guillemets non terminés
+jsp.error.attr.quoted=La valeur de l''attribute doit être entre guillemets 
+jsp.error.attr.novalue=L''attribute {0} n''a pas de valeur
+jsp.error.tag.attr.unterminated=Liste de tag d''attribut non terminée
+jsp.error.param.noname=Pas de nom dans le tag PARAM
+jsp.error.param.novalue=Pas de valeur dans le tag PARAM
+jsp.error.beans.nullbean=Tentative d''opération bean sur un objet nul.
+jsp.error.beans.nobeaninfo=Pas d''information bean (BeanInfo) pour le bean de type ''{0}'' n''a pu être trouvée, la classe n''existe probablement pas.
+jsp.error.beans.introspection=Une exception s''est produite lors de l''introspection de la méthode read de la propriété ''{0}'' dans le bean de type ''{1}'':\n{2}
+jsp.error.beans.nomethod=Impossible de trouver une méthode pour lire la propriété ''{0}'' dans le bean de type ''{1}''
+jsp.error.beans.nomethod.setproperty=Impossible de trouver une méthode pour mettre à jour la propriété ''{0}'' de type ''{1}''dans le bean de type ''{2}''
+jsp.error.beans.noproperty==Impossible de trouver de l''information sur la propriété ''{0}'' dans le bean de type ''{1}''
+jsp.error.beans.setproperty.noindexset=Impossible de renseigner la propriété indéxée
+jsp.error.include.tag=Tag jsp:include incorrect
+jsp.error.include.noflush=jsp:include doit avoir \"flush=true\"
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" est la seule combinaison valide dans JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer=Erreur: Tentative d''effacement d''un tampon qui a déjà été vidangé (flush)
+jsp.error.overflow=Erreur: Dépassement de capacité du tampon JSP
+jsp.error.paramexpected=Le tag \"param\" est attendu avec les attributs \"name\" et \"value\" après le tag \"params\".
+jsp.error.closeindividualparam=Le tag param doit être fermé avec \"/>\"
+jsp.error.closeparams=Le tag param tag doit être fermé avec  /params
+jsp.error.plugin.notype=type non déclaré dans jsp:plugin
+jsp.error.plugin.nocode=code non déclaré dans jsp:plugin
+jsp.error.ise_on_clear=Il est interdit d''utiliser clear() quand la taille de tampon== 0
+jsp.error.setproperty.beanNotFound=setProperty: le Bean {0} est introuvable
+jsp.error.getproperty.beanNotFound=getProperty: le Bean {0} est introuvable
+jsp.error.setproperty.ClassNotFound=setProperty: la Classe {0} est introuvable
+jsp.error.setproperty.invalidSyntax=setProperty: On ne peut avoir de valeur non nulle quand property=*
+jsp.error.setproperty.beanInfoNotFound=setproperty: beanInfo pour le bean {0} est introuvable
+jsp.error.setproperty.paramOrValue=setProperty: param ou value doit être présent
+jsp.error.setproperty.arrayVal=setProperty: on ne peut renseigner les array property {0} au travers d''une valeur chaîne constante (string constant value)
+jsp.warning.keepgen=Attention: Valeur incorrecte pour le initParam keepgenerated. Utilisation de la valeur par défaut \"false\"
+jsp.warning.enablePooling=Attention: Valeur incorrecte pour le initParam enablePooling. Utilisation de la valeur par défaut \"true\"
+jsp.warning.mappedFile=Attention: Valeur incorrecte pour le initParam mappedFile. Utilisation de la valeur par défaut \"false\"
+jsp.warning.classDebugInfo=Attention: Valeur incorrecte pour le initParam classdebuginfo. Utilisation de la valeur par défaut \"false\"
+jsp.warning.checkInterval=Attention: Valeur incorrecte pour le initParam checkInterval. Utilisation de la valeur par défaut \"300\" secondes
+jsp.warning.development=Attention: Valeur incorrecte pour le initParam development. Utilisation de la valeur par défaut \"true\"
+jsp.warning.reloading=Attention: Valeur incorrecte pour le initParam reloading. Utilisation de la valeur par défaut \"true\"
+jsp.warning.reloading=
+jsp.error.badtaglib=Impossible d''ouvrir le taglibrary {0} : {1}
+jsp.error.badGetReader=Impossible de créer un lecteur (reader) quand le flux n''utilse pas des tampons (not buffered)
+jsp.warning.unknown.element.in.TLD=Attention: Elément inconnu {0} dans le TLD
+jsp.warning.unknown.element.in.tag=Attention: Elément inconnu {0} dans le tag
+jsp.warning.unknown.element.in.tagfile=Attention: El?ment inconnu {0} dans le tag-file
+jsp.warning.unknown.element.in.attribute=Attention: Elément inconnu {0} dans l''attribute
+jsp.error.more.than.one.taglib=plus d''un taglib dans le TLD: {0}
+jsp.error.teiclass.instantiation=Impossible de charger ou d''instancier la classe TagExtraInfo: {0}
+jsp.error.non_null_tei_and_var_subelems=Le tag {0} possède une ou plusieurs variables subelements et une classe TagExtraInfo qui retourne une ou plusieurs VariableInfo
+jsp.error.parse.error.in.TLD=Erreur d''évaluation (parse) dans le descripteur de librairie de tag (TLD): {0}
+jsp.error.unable.to.open.TLD=Impossible d''ouvrir le descripteur de librairie de tag (TLD): {0}
+jsp.buffer.size.zero=Taille du tampon <= 0
+jsp.error.file.not.found=Le fichier \"{0}\" n''a pas été trouvé
+jsp.message.copyinguri=Copie de {0} dans {1}
+jsp.message.htmlcomment=\nEffacement des commentaires: \t{0}
+jsp.message.handling_directive=\nDirective de gestion (handling): {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=Le nom de package est: {0}
+jsp.message.class_name_is=Le nom de classe est: {0}
+jsp.message.java_file_name_is=Le nom de fichier Java est: {0}
+jsp.message.class_file_name_is=Le nom de fichier Class est: {0}
+jsp.message.accepted=Accepté {0} à {1}
+jsp.message.adding_jar=Ajout du jar {0} à mon classpath
+jsp.message.compiling_with=Compilation avec: {0}
+jsp.message.template_text=texte template
+jsp.error.missing_attribute=D''après le TLD l''attribut {0} est obligatoire pour le tag {1}
+jsp.error.bad_attribute=L''attribut {0} est incorrect pour le tag {1} d''après la TLD indiquée
+jsp.error.webxml_not_found=Impossible de localiser le fichier web.xml
+jsp.cmd_line.usage=Usage: jsptoservlet [-dd <path/to/outputDirectory>] [-keepgenerated] \
+<.jsp files>
+jsp.message.cp_is=Le Classpath {0} est: {1}
+jsp.error.unable.to_load_taghandler_class=Impossible de charger la classe gestionnaire de tag {0} car {1}
+jsp.error.unable.to_find_method=Impossible de trouver une méthode de mise à jour pour l''attribut: {0}
+jsp.error.unable.to_convert_string=Impossible de convertir une chaîne vers {0} pour l''attribut {1}
+jsp.error.unable.to_introspect=Impossible d''introspecter la classe gestionnaire de tag : {0} car {1}
+jsp.error.bad_tag=Aucun tag {0} dans la librairie de tag importée avec le préfixe {1}
+jsp.error.bad_string_Character=Impossible d''extraire un caractère depuis un tableau vide
+jsp.error.bad_string_char=Impossible d''extraire un caractère depuis un tableau vide
+jsp.warning.compiler.class.cantcreate=Impossible de créer une instance de classe plugin pour le compilateur indiqué {0} due to {1}. Utilisation par défaut du Compilateur Java Sun.
+jsp.warning.compiler.class.notfound=La classe plugin de compilateur {0} est introuvable. Utilisation par défaut du Compilateur Java Sun.
+jsp.warning.compiler.path.notfound=le chemin de compilateur indiqué {0} est introuvable. Utilisation par défaut du chemin système (system PATH).
+jsp.error.jspc.uriroot_not_dir=L''option -uriroot doit indiqué un répertoire déjà existant
+jspc.implicit.uriRoot=uriRoot réglé implicitement à "{0}"
+jspc.usage=Usage: jspc <options> [--] <fichiers jsp>\n\
+où les fichiers jsp sont n''importe quel nombre de:\n\
+\    <file>         Un fichier à évaluer (parser) comme page jsp\n\
+\    -webapp <dir>  Un répertoire contenant une application web, toutes les pages jsp\n\
+\                   seront récursivement évaluées\n\
+où les options comprennet:\n\
+\    -q          Mode silencieux (identique à -v0)\n\
+\    -v[#]       Mode bavard (Le nombre optionnel indique le niveau, 2 par défaut)\n\
+\    -d <dir>    Dossier de sortie\n\
+\    -dd <dir>   Dossier de sortie literal.  (Les dossiers de paquets ne seront pas construits)\n\
+\    -l          Sortie du nom la page JSP en cas d''échec\n\
+\    -s          Sortie du nom la page JSP en cas de succès\n\
+\    -p <name>   Nom du paquet cible\n\
+\    -c <name>   Nom d'un nom de classe cible\n\
+\                (s''applique seulement à la première page JSP)\n\
+\    -mapped     Génère des appels à write() séparés pour chaque ligne HTML dans la JSP\n\
+\    -die[#]     Génère un code d''erreur de retour (#) en cas d''erreurs fatales.\n\
+\                Si le nombre est absent ou non numérique, le défaut est 1.\n\
+\    -uribase <dir>  Le répertoire uri de compilations relatif\n\
+\                    (Par défaut "/")\n\
+\    -uriroot <dir>  The répertoire racine contre lequel les fichiers seront résolus\n\
+\                    , (Par défaut le répertoire depuis lequel jspc est appelé)\n\
+\    -webinc <file>  Création d''association partielle de servlet pour l''option -webapp.\n\
+\    -webxml <file>  Création d''un fichier web.xml complet pour l''option -webapp.\n\
+\    -ieplugin <clsid>  Le classid du Plugin Java Plugin pour Internet Explorer\n\
+\    -sax2 <driverclassname>  Le nom de classe du Driver SAX 2.0 à utiliser\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.4)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Créer automatiquement par le JspC Apache Jakarta Tomcat.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Créer automatiquement par le JspC Apache Jakarta Tomcat.\n\
+Placez ce fragment dans le fichier web.xml avant all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.error.jasperException=erreur-le fichier ''{0}'' a généré l''exception d''évaluation suivante: {1}
+jspc.error.generalException=ERREUR-le fichier ''{0}'' a généré l''exception générale suivante:
+jspc.error.fileDoesNotExist=L''argument fichier ''{0}'' n''existe pas
+jspc.error.emptyWebApp=-webapp nécessite à sa suite un argument fichier
+jsp.error.library.invalid=La page JSP page est incorrecte d''après la librairie {0}: {1}
+jsp.error.tlvclass.instantiation=Impossible de charger ou d''instancier la classe TagLibraryValidator: {0}
+jsp.error.tlv.invalid.page=Message d''erreurs de validation provenant du TagLibraryValidator pour {0} en {1}
+jsp.error.tei.invalid.attributes=Message d''erreurs de validation provenant du TagExtraInfo pour {0}
+jsp.parser.sax.propertynotsupported=Propriété SAX non supportée: {0}
+jsp.parser.sax.propertynotrecognized=Propriété SAX non reconnue: {0}
+jsp.parser.sax.featurenotsupported=Fonctionnalité SAX non supportée: {0}
+jsp.parser.sax.featurenotrecognized=Fonctionnalité SAX non reconnue: {0}
+jsp.error.no.more.content=Fin de contenu alors que l''évalution n''était pas terminée: erreur de tags imbriqués?
+jsp.error.parse.xml=Erreur d''évaluation XML sur le fichier {0}
+jsp.error.parse.xml.line=Erreur d''évaluation XML sur le fichier  {0}: (ligne {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body=Le corps de l''élément {0} ne doit contenir aucun éléments XML
+jsp.error.internal.tldinit=Exception lors de l'initialisation de TldLocationsCache: {0}
+jsp.error.internal.filenotfound=Erreur interne: Fichier {0} introuvable
+jsp.error.internal.evaluator_not_found=Erreur interne: Impossible de charger l''évaluateur d''expression
+jsp.error.parse.xml.invalidPublicId=PUBLIC ID invalide: {0}
+jsp.error.include.flush.invalid.value=Valeur incorrecte pour l''attribut flush: {0}
+jsp.error.unsupported.encoding=Encodage non supporté: {0}
+jsp.warning.unknown.element.in.variable=Attention: Element inconnu {0} dans la variable
+tld.error.variableNotAllowed=Ceci est une erreur pour le tag qui possède une ou plusieurs variables subelements pour avoir une classe TagExtraInfo qui retourne un objet non-nul.
+jsp.error.tldInWebDotXmlNotFound=Ne peut trouver le TLD {1} pour l''URI {0} indiquée dans le fichier web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved=L''uri absolue: {0} ne peut être résolu dans le fichier web.xml ou dans les fichiers jar déployés avec cette application
+jsp.error.taglibDirective.missing.location=Ni l''uri' ni l''attribut 'tagdir' n''ont été indiqués dans la directive taglib
+jsp.error.invalid.tagdir=Le répertoire du fichier Tag {0} ne commence pas par \"/WEB-INF/tags\"
+jsp.error.unterminated.user.tag=Tag user-defined non terminé: Le tag de fermeture {0} est introuvable found ou incorrectement imbriqué
+#jspx.error.templateDataNotInJspCdata=Erreur de validation: l''élément &lt;{0}&gt; ne peut avoir de données template. Les données Template doivent être encapsulées à l''intérieur d''un élément &lt;jsp:cdata&gt;. [JSP1.2 PFD section 5.1.9]\nDonnée Template en erreur: {1}
+jspx.error.templateDataNotInJspCdata=Erreur de validation: l''élément &lt;{0}&gt; ne peut avoir de données template. Les données Template doivent être encapsulées à l''intérieur d''un élément &lt;jsp:text&gt;. [JSP1.2 PFD section 5.1.9]\nDonnée Template en erreur: {1}
+#Erreur lors du traitement du fichier jar de la taglib {0}: {1}
+jsp.error.taglib.reserved.prefix=Le préfixe taglib {0} est réservé
+jsp.error.invalid.javaEncoding=Encodage java incorrect. Essai de {0} puis de {1}. Les deux ont échoué.
+jsp.error.needAlternateJavaEncoding=L''encodage java par défaut {0} est incorrect sur votre environnement java. Une alternative peut être indiquée via le paramêtre 'javaEncoding' de la JspServlet.
+#Erreur lors de la compilation, utilisé pour la ligne jsp des messages d''erreur
+jsp.error.single.line.number=Une erreur s''est produite à la ligne: {0} dans le fichier jsp: {1}
+jsp.error.multiple.line.number=\n\nUne erreur s''est produite entre les lignes: {0} et {1} dans le fichier jsp: {2}\n\n
+jsp.error.corresponding.servlet=Erreur de servlet générée:\n
+jsp.error.empty.body.not.allowed=Un corps vide n'est pas autorisé pour {0}
+jsp.error.jspbody.required=Doit utiliser jsp:body pour indiqué le corps de tag body de {0} si jsp:attribute est utilisé.
+jsp.error.jspbody.emptybody.only=Le tag {0} ne peut avoir que jsp:attribute dans son corps.
+jsp.error.no.scriptlets=Les éléments de Scripting ( <%!, <jsp:declaration, <%=, <jsp:expression, <%, <jsp:scriptlet ) ne sont pas autorisés ici.
+jsp.error.internal.unexpected_node_type=Erreur Interne: Type de node inattendu rencontré
+jsp.error.tld.fn.invalid.signature=Synthaxe invalide pour la signature de fonction dans la TLD.  Librairie de Tag : {0}, Fonction: {1}
+jsp.error.tld.fn.invalid.signature.classnotfound=Synthaxe invalide pour la signature de fonction dans la TLD.  Classe introuvable: ${0}.  Librairie de Tag: {1}, Fonction: {2}.
+jsp.error.tld.fn.invalid.signature.commaexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Virgule ',' attendue.  Librairie de Tag: {0}, Fonction: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Parenthèse '(' attendue.  Librairie de Tag: {0}, Fonction: {1}.
+jsp.error.dynamic.attributes.not.implemented=Le tag {0} indique qu''il accepte des attributs dynamics mais n''implémente pas l''interface requise
+jsp.error.nomatching.fragment=Ne peut trouver une directive attribut (avec pour nom={0} et fragment=true) avant la directive fragment.
+jsp.error.attribute.noequal=Symbole égal (equal) attendu
+jsp.error.attribute.noquote=Symbole guillemet (quote) attendu
+jsp.error.attribute.unterminated=L''attribut pour {0} n''est pas correctement terminé
+jsp.error.missing.tagInfo=L''objet TagInfo de {0} est absent de la TLD
+jsp.error.fragmentwithtype=On ne peut indiquer à la fois les attributs 'fragment' et 'type'.  Si 'fragment' est présent, 'type' est fixé comme 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue=On ne peut indiquer à la fois les attributs 'fragment' et 'rtexprvalue'.  Si 'fragment' est présent, 'rtexprvalue' est fixé à 'true'
+jsp.error.fragmentWithDeclareOrScope=Les attributs 'fragment' et 'declare' ou 'scope' sont indiqués dans la directive variable
+jsp.error.var_and_varReader=A la fois 'var' et 'varReader' sont indiqués
+jsp.warning.bad.urlpattern.propertygroup=Mauvaise valeur {0} dans le sous-élément (subelement) url-pattern du fichier web.xml
+jsp.error.unknown_attribute_type=Type d''attribut inconnu ({1}) pour l''attribut {0}.
+jsp.error.jspelement.missing.name=L''attribut obligatoire 'name' est absent de jsp:element
+jsp.error.xmlns.redefinition.notimplemented=Erreur Interne: Tentative de redéfinition de xmlns:{0}.  La redéfinition des domaines de noms (namespaces) n''est pas implémentée.
+jsp.error.could.not.add.taglibraries=Impossible d''ajouter une ou plusieurs librairies de tag.
+jsp.error.duplicate.name.jspattribute=L''attribut {0} indiqué dans l''action standard ou spécifique (custom) apparait aussi comme valeur de l''attribut de nom dans le jsp:attribute inclus
+jsp.error.not.in.template={0} n''est pas autorisé dans le corps de texte de template.
+jsp.error.badStandardAction=L''action n''est pas reconnue comme une action standard.
+jsp.error.tagdirective.badbodycontent=Contenu de corps (body-content) ({0}) invalide dans la directive tag
+jsp.error.config_pagedir_encoding_mismatch=L''encode de page (Page-encoding) indiqué dans le jsp-property-group ({0}) est différent de celui indiqué dans la directive de page ({1})
+jsp.error.prolog_pagedir_encoding_mismatch=
+jsp.error.prolog_config_encoding_mismatch=
+jsp.error.attribute.custom.non_rt_with_expr=D''après la TLD, l''attribut {0} n''accepte aucune expression
+jsp.error.scripting.variable.missing_name=Incapable de déterminer le nom de variable scripting d''après l''attribut {0}
+jasper.error.emptybodycontent.nonempty=D''après la TLD, le tag {0} doit être vide, mais ne l''est pas
+jsp.error.tagfile.nameNotUnique=
+jsp.error.tagfile.nameFrom.noAttribute=
+jsp.error.tagfile.nameFrom.badAttribute=
+jsp.error.useBean.noSession=Il est illégal pour useBean d''utiliser une portée de session (session scope) quand la page JSP indique (via la directive de page) qu''elle ne participe pas aux sessions
+jsp.error.attributes.not.allowed = {0} ne doit avoir aucun attribut
+jsp.error.nested.jspattribute=
+jsp.error.nested.jspbody=
+jsp.error.variable.either.name=
+jsp.error.variable.both.name=
+jsp.error.variable.alias=
+jsp.error.jsptext.badcontent=
+jsp.error.prefix.refined=
+jsp.error.jspoutput.conflict=
+jsp.error.jspoutput.doctypenamesystem=
+jsp.error.jspoutput.doctypepulicsystem=
+jsp.error.jspoutput.nonemptybody=
+jsp.error.jspoutput.invalidUse=
+jsp.error.invalid.bean=
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_ja.properties b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_ja.properties
new file mode 100644
index 0000000..cfe536c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_ja.properties
@@ -0,0 +1,421 @@
+# 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.
+
+# $Id$
+#
+# Default localized string information
+# Localized this the Default Locale as is ja_JP
+
+jsp.error.bad.servlet.engine=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30a8\u30f3\u30b8\u30f3\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093
+jsp.error.no.scratch.dir=JSP\u30a8\u30f3\u30b8\u30f3\u306b\u30c7\u30d5\u30a9\u30eb\u30c8\u306escratchDir\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\
+\n \u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306eservlets.properties\u30d5\u30a1\u30a4\u30eb\u306b\u3001\
+\n \"jsp.initparams=scratchdir=<dir-name>\" \u3092\u8ffd\u52a0\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+jsp.error.bad.scratch.dir=\u3042\u306a\u305f\u304c\u6307\u5b9a\u3057\u305fscratchDir: {0} \u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.message.scratch.dir.is=JSP\u30a8\u30f3\u30b8\u30f3\u306eScratchdir: {0}
+jsp.message.parent_class_loader_is=\u89aa\u30af\u30e9\u30b9\u30ed\u30fc\u30c0: {0}
+jsp.message.dont.modify.servlets=\u91cd\u8981: \u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u5909\u66f4\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.not.impl.comments=\u5185\u90e8\u30a8\u30e9\u30fc: Comments\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.directives=\u5185\u90e8\u30a8\u30e9\u30fc: Directives\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.declarations=\u5185\u90e8\u30a8\u30e9\u30fc: Declarations\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.expressions=\u5185\u90e8\u30a8\u30e9\u30fc: Expressions\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.scriptlets=\u5185\u90e8\u30a8\u30e9\u30fc: Scriptlets\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.usebean=\u5185\u90e8\u30a8\u30e9\u30fc: useBean\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.getp=\u5185\u90e8\u30a8\u30e9\u30fc: getProperty\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.setp=\u5185\u90e8\u30a8\u30e9\u30fc: setProperty\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.plugin=\u5185\u90e8\u30a8\u30e9\u30fc: plugin\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.forward=\u5185\u90e8\u30a8\u30e9\u30fc: forward\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.include=\u5185\u90e8\u30a8\u30e9\u30fc: include\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.unavailable=JSP\u306f\u5229\u7528\u4e0d\u53ef\u3068\u30de\u30fc\u30af\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.usebean.missing.attribute=useBean: id\u5c5e\u6027\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u30b9\u30da\u30eb\u30df\u30b9\u3067\u3059
+jsp.error.usebean.missing.type=useBean ({0}): class\u5c5e\u6027\u304btype\u5c5e\u6027\u3092\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044:
+jsp.error.usebean.duplicate=useBean: beanName\u5c5e\u6027\u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059: {0}
+jsp.error.usebean.prohibited.as.session=\u4ee5\u524d\u306b\u5b9a\u7fa9\u3057\u305fJSP\u6307\u793a\u5b50\u306b\u3088\u3063\u3066\u7981\u6b62\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u306b\u3001session bean {0} \u3068\u3057\u3066\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093:
+jsp.error.usebean.not.both=useBean: class\u5c5e\u6027\u3068beanName\u5c5e\u6027\u306e\u4e21\u65b9\u3092\u540c\u6642\u306b\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093:
+jsp.error.usebean.bad.type.cast=useBean ({0}): type ({1}) \u306fclass ({2}) \u304b\u3089\u5272\u308a\u5f53\u3066\u3089\u308c\u307e\u305b\u3093
+jsp.error.invalid.scope='scope'\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: {0} (\"page\"\u3001\"request\"\u3001\"session\"\u53c8\u306f\"application\"\u306e\u3069\u308c\u304b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093)
+jsp.error.classname=.class\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30af\u30e9\u30b9\u540d\u3092\u6c7a\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.warning.bad.type=\u8b66\u544a: .class\u30d5\u30a1\u30a4\u30eb\u4e2d\u306e\u578b\u304c\u9055\u3044\u307e\u3059
+jsp.error.data.file.write=\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u66f8\u304d\u8fbc\u307f\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+jsp.error.page.invalid.buffer=page\u6307\u793a\u5b50: \u7121\u52b9\u306a\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u3067\u3059
+jsp.error.page.conflict.contenttype=page\u6307\u793a\u5b50: 'contentType'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.contenttype=page\u6307\u793a\u5b50: contentType\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.session=page\u6307\u793a\u5b50: 'session'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.session=page\u6307\u793a\u5b50: session\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.buffer=page\u6307\u793a\u5b50: 'buffer'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.buffer=page\u6307\u793a\u5b50: buffer\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.autoflush=page\u6307\u793a\u5b50: 'autoFlush'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.autoflush=page\u6307\u793a\u5b50: autoFlush\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.isthreadsafe=page\u6307\u793a\u5b50: 'isThreadSafe'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.isthreadsafe=page\u6307\u793a\u5b50: isThreadSafe\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.info=page\u6307\u793a\u5b50: 'info'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.info=page\u6307\u793a\u5b50: info\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.iserrorpage=page\u6307\u793a\u5b50: 'isErrorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.iserrorpage=page\u6307\u793a\u5b50: isErrorPage\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.errorpage=page\u6307\u793a\u5b50: 'errorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.language=page\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.tag.conflict.language=tag\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.language.nonjava=page\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
+jsp.error.tag.language.nonjava=tag\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
+jsp.error.page.defafteruse.language=page\u6307\u793a\u5b50: scriptlet\u306e\u5f8c\u3067language\u5c5e\u6027\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.page.nomapping.language=page\u6307\u793a\u5b50 language\u5c5e\u6027\u306e\u30de\u30c3\u30d4\u30f3\u30b0\u304c\u5b58\u5728\u3057\u307e\u305b\u3093:
+jsp.error.page.conflict.extends=page\u6307\u793a\u5b50: 'extends'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.iselignored=page\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.tag.conflict.iselignored=tag\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.iselignored=page\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.tag.invalid.iselignored=tag\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.page.multi.pageencoding=page\u6307\u793a\u5b50\u306f\u8907\u6570\u306epageencoding\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.tag.conflict.attr=Tag\u6307\u793a\u5b50: \u5c5e\u6027\"{0}\"\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u4e0d\u6b63\u3067\u3059 (\u65e7: {1}, \u65b0: {2})
+jsp.error.tag.multi.pageencoding=tag\u6307\u793a\u5b50\u306f\u8907\u6570\u306epageencoding\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.page.bad_b_and_a_combo=page\u6307\u793a\u5b50: buffer=\"none\"\u3068autoFlush=\"false\"\u3092\u540c\u6642\u306b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.not.impl.taglib=\u5185\u90e8\u30a8\u30e9\u30fc: \u30bf\u30b0\u62e1\u5f35\u5b50\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.include.missing.file=\u53d6\u308a\u8fbc\u3080\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.include.bad.file=include\u5c5e\u6027\u306e\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059
+jsp.error.include.exception={0} \u3092\u53d6\u308a\u8fbc\u3081\u307e\u305b\u3093
+jsp.error.stream.closed=\u30b9\u30c8\u30ea\u30fc\u30e0\u304c\u30af\u30ed\u30fc\u30ba\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.forward=\u7121\u52b9\u306aforward\u30bf\u30b0\u3067\u3059
+jsp.error.unknownException=\u51e6\u7406\u4e0d\u53ef\u80fd\u306a\u30a8\u30e9\u30fc\u3067\u3059! \u3053\u306e\u3088\u3046\u306a\u30a8\u30e9\u30fc\u3092\u3088\u308a\u8a73\u7d30\u306b\u5831\u544a\u3059\u308b\u30a8\u30e9\u30fc\u30da\u30fc\u30b8\u3092\u6301\u3063\u305f\u65b9\u304c\u3088\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.invalid.directive=\u7121\u52b9\u306a\u6307\u793a\u5b50\u3067\u3059
+jsp.error.directive.istagfile={0} \u6307\u793a\u5b50\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.directive.isnottagfile={0} \u6307\u793a\u5b50\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u3057\u304b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.tagfile.tld.name=TLD\u4e2d\u306e\u30bf\u30b0\u6307\u793a\u5b50\u306e\"tag-file\"\u8981\u7d20\u306e\"name\"\u30bf\u30b0\u306f {1} \u3067\u3059\u304c\uff0c\"name\"\u5c5e\u6027\u306f\u5024 {0} \u3092\u6301\u3063\u3066\u3044\u307e\u3059
+jsp.error.action.istagfile={0} \u30a2\u30af\u30b7\u30e7\u30f3\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.action.isnottagfile={0} \u30a2\u30af\u30b7\u30e7\u30f3\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u306e\u307f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.unterminated={0} \u30bf\u30b0\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.usebean.notinsamefile=useBean\u30bf\u30b0\u306f\u3001\u540c\u4e00\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u958b\u59cb\u3057\u3001\u7d42\u4e86\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.loadclass.taghandler=\u30bf\u30b0 \"{1}\" \u306b\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 \"{0}\" \u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.compile=JSP\u306e\u30af\u30e9\u30b9\u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.load=JSP\u306e\u30af\u30e9\u30b9\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.rename=\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb {0} \u3092 {1} \u306b\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093
+jsp.error.mandatory.attribute={0}: \u5fc5\u9808\u5c5e\u6027 {1} \u304c\u3042\u308a\u307e\u305b\u3093
+jsp.engine.info=Jasper JSP 2.0\u30a8\u30f3\u30b8\u30f3
+jsp.error.invalid.expression="{0}" \u306f\u7121\u52b9\u306a\u5f0f\u3092\u542b\u3093\u3067\u3044\u307e\u3059: {1}
+jsp.error.invalid.attribute={0}\u306f\u7121\u52b9\u306a\u5c5e\u6027\u3092\u6301\u3063\u3066\u3044\u307e\u3059: {1}
+jsp.error.usebean.class.notfound=\u30af\u30e9\u30b9: {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.file.cannot.read=\u30d5\u30a1\u30a4\u30eb\u304c\u8aad\u3081\u307e\u305b\u3093: {0}
+jsp.error.file.already.registered=\u30d5\u30a1\u30a4\u30eb {0} \u306e\u518d\u5e30\u7684\u306a\u53d6\u308a\u8fbc\u307f\u3067\u3059
+jsp.error.file.not.registered=include\u5c5e\u6027\u4e2d\u306e\u30d5\u30a1\u30a4\u30eb {0} \u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+jsp.error.quotes.unterminated=\u5f15\u7528\u7b26\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.attr.quoted=\u5c5e\u6027\u5024\u306f\u5f15\u7528\u7b26\u3067\u56f2\u308f\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.attr.novalue=\u5c5e\u6027 {0} \u306b\u306f\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.tag.attr.unterminated=\u30bf\u30b0\u306e\u5c5e\u6027\u30ea\u30b9\u30c8\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.param.noname=PARAM\u30bf\u30b0\u306bname\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.param.novalue=PARAM\u30bf\u30b0\u306bvalue\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.beans.nullbean=null\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306bBean\u64cd\u4f5c\u3092\u304a\u3053\u306a\u304a\u3046\u3068\u3057\u307e\u3057\u305f
+jsp.error.beans.nobeaninfo=\u30bf\u30a4\u30d7 ''{0}'' \u306eBean\u306bBeanInfo\u304c\u306a\u3044\u306e\u3092\u691c\u51fa\u3057\u307e\u3057\u305f, \u30af\u30e9\u30b9\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.beans.introspection=\u30bf\u30a4\u30d7 ''{1}'' \u306eBean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u306eread\u30e1\u30bd\u30c3\u30c9\u3092\u5185\u7701\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f:\n{2}
+jsp.error.beans.nomethod=\u30bf\u30a4\u30d7 ''{1}'' \u306eBean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u3092\u8aad\u307f\u8fbc\u3080\u30e1\u30bd\u30c3\u30c9\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.nomethod.setproperty=\u30bf\u30a4\u30d7''{2}''\u306eBean\u306e\u30bf\u30a4\u30d7 ''{1}'' \u306e\u5c5e\u6027 ''{0}'' \u3092\u66f8\u304d\u8fbc\u3080\u30e1\u30bd\u30c3\u30c9\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.noproperty=\u30bf\u30a4\u30d7 ''{1}'' \u306ebean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u306e\u60c5\u5831\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.setproperty.noindexset=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4ed8\u304d\u306e\u5c5e\u6027\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.include.tag=\u7121\u52b9\u306ajsp:include\u30bf\u30b0\u3067\u3059
+jsp.error.include.noflush=jsp:include\u30bf\u30b0\u306b \"flush=true\" \u3092\u5b9a\u7fa9\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" \u306f\u3001JSP 1.0\u3067\u306e\u307f\u6709\u52b9\u306a\u7d44\u307f\u5408\u308f\u305b\u3067\u3059
+jsp.error.attempt_to_clear_flushed_buffer=\u30a8\u30e9\u30fc: \u65e2\u306b\u30d5\u30e9\u30c3\u30b7\u30e5\u3055\u308c\u3066\u3044\u308b\u30d0\u30c3\u30d5\u30a1\u3092\u30af\u30ea\u30a2\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f
+jsp.error.overflow=\u30a8\u30e9\u30fc: JSP\u30d0\u30c3\u30d5\u30a1\u304c\u30aa\u30fc\u30d0\u30fc\u30d5\u30ed\u30fc\u3057\u307e\u3057\u305f
+jsp.error.paramexpected=\"name\"\u5c5e\u6027 \u3068 \"value\" \u5c5e\u6027\u3092\u6301\u3064 \"jsp:param\" \u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.param.invalidUse=jsp:include\u3001jsp:forward\u3001\u53c8\u306fjsp:params\u8981\u7d20\u306e\u5916\u3067jsp:param\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u4f7f\u7528\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.invalidUse=jsp:params\u306fjsp:plugin\u306e\u76f4\u63a5\u306e\u5b50\u4f9b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.fallback.invalidUse=jsp:fallback\u306fjsp:plugin\u306e\u76f4\u63a5\u306e\u5b50\u4f9b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.namedAttribute.invalidUse=jsp:attribute\u306f\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u526f\u8981\u7d20\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspbody.invalidUse=jsp:body\u306f\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u526f\u8981\u7d20\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.closeindividualparam=param\u30bf\u30b0\u306f \"/>\" \u3067\u9589\u3058\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.closeparams=param\u30bf\u30b0\u306f/params\u3067\u9589\u3058\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.emptyBody=jsp:params\u306f\u5c11\u306a\u304f\u3068\u3082\u4e00\u3064\u306e\u30cd\u30b9\u30c8\u3057\u305fjsp:param\u3092\u542b\u307e\u306d\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.illegalChild=jsp:params\u306fjsp:param\u4ee5\u5916\u306e\u30cd\u30b9\u30c8\u3057\u305f\u8981\u7d20\u3092\u542b\u3093\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.notype=jsp:plugin\u3067type\u5c5e\u6027\u304c\u5ba3\u8a00\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.plugin.badtype=jsp:plugin\u306e 'type'\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: 'bean'\u53c8\u306f'applet'\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.nocode=jsp:plugin\u3067code\u5c5e\u6027\u304c\u5ba3\u8a00\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.ise_on_clear=\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u304c0\u306e\u6642\u306bclear()\u3092\u5b9f\u884c\u3057\u3066\u3082\u7121\u52b9\u3067\u3059
+jsp.error.setproperty.beanNotFound=setProperty: Bean {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.getproperty.beanNotFound=getProperty: Bean {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.setproperty.ClassNotFound=setProperty: \u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+# typo ?
+#jsp.error.setproperty.invalidSayntax=setProperty: property=*\u306e\u5834\u5408\u306fnull\u3067\u306a\u3044\u5024\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.setproperty.invalidSyntax=setProperty: property=*\u306e\u5834\u5408\u306fnull\u3067\u306a\u3044\u5024\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.setproperty.beanInfoNotFound=setproperty: Bean {0} \u306ebeanInfo\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.setproperty.paramOrValue=setProperty: param\u5c5e\u6027\u304bvalue\u5c5e\u6027\u306e\u3069\u3061\u3089\u304b\u4e00\u3064\u3060\u3051\u3092\u6307\u5b9a\u3067\u304d\u307e\u3059
+jsp.error.setproperty.arrayVal=setProperty: \u5c5e\u6027\u914d\u5217 {0} \u3092\u6587\u5b57\u5217\u5b9a\u6570\u5024\u3067\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.warning.keepgen=\u8b66\u544a: initParam keepgenerated\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002 \u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.xpoweredBy=\u8b66\u544a: Invalid value for the initParam xpoweredBy\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.enablePooling=\u8b66\u544a: initParam enablePooling\u304c\u7121\u52b9\u306a\u5024\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.invalidTagPoolSize=\u8b66\u544a: tagPoolSize\u306e\u521d\u671f\u30d1\u30e9\u30e1\u30fc\u30bf\u304c\u7121\u52b9\u306a\u5024\u3067\u3059\u3002{0}\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u30b5\u30a4\u30ba\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.mappedFile=\u8b66\u544a: initParam mappedFile\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.classDebugInfo=\u8b66\u544a: initParam classDebugInfo\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\"\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.checkInterval=\u8b66\u544a: initParam checkInterval\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"300\"\u79d2\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.development=\u8b66\u544a: initParam development\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.fork=\u8b66\u544a: initParam fork\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.reloading=\u8b66\u544a: initParam reloading\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.dumpSmap=\u8b66\u544a: initParam dumpSmap\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"false\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.genchararray=\u8b66\u544a: initParam genStrAsCharArray\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"false\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.suppressSmap=\u8b66\u544a: initParam suppressSmap\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.error.badtaglib=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea {0} \u3092\u30aa\u30fc\u30d7\u30f3\u3067\u304d\u307e\u305b\u3093: {1}
+jsp.error.badGetReader=\u30b9\u30c8\u30ea\u30fc\u30e0\u304c\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Reader\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093
+jsp.warning.unknown.element.in.taglib=taglib\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.tag=tag\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.tagfile=tag-file\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.attribute=attribute\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.variable=variable\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.validator=validator\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.initParam=validator\u306einit-param\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.function=function\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.error.more.than.one.taglib=TLD\u306e\u4e2d\u306b\u8907\u6570\u306etaglib\u304c\u5b58\u5728\u3057\u307e\u3059: {0}
+jsp.error.teiclass.instantiation=TagExtraInfo class\u306e\u30ed\u30fc\u30c9\u53c8\u306f\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+jsp.error.non_null_tei_and_var_subelems=\u30bf\u30b0 {0} \u306f\u4e00\u3064\u4ee5\u4e0a\u306evariable\u526f\u8981\u7d20\u3068\u4e00\u3064\u4ee5\u4e0a\u306eVariableInfo\u3092\u8fd4\u3059TagExtraInfo\u30af\u30e9\u30b9\u3092\u6301\u3063\u3066\u3044\u307e\u3059
+jsp.error.parse.error.in.TLD=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u8a18\u8ff0\u5b50 {0} \u4e2d\u306e\u89e3\u6790\u30a8\u30e9\u30fc\u3067\u3059
+jsp.error.unable.to.open.TLD=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u8a18\u8ff0\u5b50 {0} \u3092\u30aa\u30fc\u30d7\u30f3\u3067\u304d\u307e\u305b\u3093
+jsp.buffer.size.zero=\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u304c0\u4ee5\u4e0b\u3067\u3059
+jsp.error.file.not.found=JSP \u30d5\u30a1\u30a4\u30eb \"{0}\" \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.message.copyinguri={0} \u3092 {1} \u306b\u30b3\u30d4\u30fc\u3057\u307e\u3059
+jsp.message.htmlcomment=\n\u524a\u9664\u3059\u308b\u30b3\u30e1\u30f3\u30c8: \t{0}
+jsp.message.handling_directive=\n\u51e6\u7406\u3059\u308b\u6307\u793a\u5b50: {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=\u30d1\u30c3\u30b1\u30fc\u30b8\u540d: {0}
+jsp.message.class_name_is=\u30af\u30e9\u30b9\u540d: {0}
+jsp.message.java_file_name_is=Java\u30d5\u30a1\u30a4\u30eb\u540d: {0}
+jsp.message.class_file_name_is=\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u540d: {0}
+jsp.message.accepted={1} \u3067 {0} \u3092\u53d7\u3051\u5165\u308c\u307e\u3059
+jsp.message.adding_jar=jar {0} \u3092\u30af\u30e9\u30b9\u30d1\u30b9\u306b\u8ffd\u52a0\u3057\u307e\u3059
+jsp.message.compiling_with={0} \u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u4e2d\u3067\u3059
+jsp.message.template_text=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8
+jsp.error.missing_attribute=TLD\u53c8\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u306b\u3088\u308b\u3068\u3001\u5c5e\u6027 {0} \u306f\u30bf\u30b0 {1} \u306b\u306f\u5fc5\u9808\u3067\u3059
+jsp.error.bad_attribute=TLD\u306b\u3088\u308b\u3068\u3001\u30bf\u30b0 {1} \u306e\u5c5e\u6027 {0} \u306f\u7121\u52b9\u3067\u3059
+jsp.error.tld.unable_to_read=JAR\u30d5\u30a1\u30a4\u30eb \"{0}\" \u304b\u3089TLD \"{1}\" \u3092\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093: {2}
+jsp.error.tld.unable_to_get_jar=TLD\u3092\u542b\u3080JAR\u30ea\u30bd\u30fc\u30b9 \"{0}\" \u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093 : {1}
+jsp.error.tld.missing_jar=TLD\u3092\u542b\u3080JAR\u30ea\u30bd\u30fc\u30b9 \"{0}\" \u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.webxml_not_found=web.xml\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.cmd_line.usage=\u4f7f\u7528\u6cd5:  [-dd <\u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u30d1\u30b9>] [-keepgenerated] \
+<.jsp\u30d5\u30a1\u30a4\u30eb\u7fa4>
+jsp.message.cp_is=\u30af\u30e9\u30b9\u30d1\u30b9 {0} \u306f {1} \u3067\u3059
+jsp.error.unable.to_load_taghandler_class=\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 {0} \u3092 {1} \u306e\u305f\u3081\u306b\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.to_find_method=\u5c5e\u6027 {0} \u306esetter\u30e1\u30bd\u30c3\u30c9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.unable.to_convert_string=\u5c5e\u6027 {1}\u306e\u6587\u5b57\u5217\u3092 {0}\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.to_introspect=\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 {0} \u3092 {1} \u306e\u305f\u3081\u306b\u5185\u7701\u3067\u304d\u307e\u305b\u3093
+jsp.error.bad_tag=\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9 {1}\u3067\u30a4\u30f3\u30dd\u30fc\u30c8\u3055\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306b\u306f\u3001\u30bf\u30b0 {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+jsp.error.xml.bad_tag=URI \"{1}\" \u306b\u95a2\u9023\u3065\u3051\u3089\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306e\u4e2d\u306b\u306f\u30bf\u30b0 \"{0}\" \u306f\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.bad_string_Character=\u9577\u30550\u306e\u914d\u5217\u304b\u3089\u306f\u6587\u5b57\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093
+jsp.error.bad_string_char=\u9577\u30550\u306e\u914d\u5217\u304b\u3089\u306f\u6587\u5b57\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093
+jsp.warning.compiler.class.cantcreate=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d7\u30e9\u30b0\u30a4\u30f3\u30af\u30e9\u30b9 {0} \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092 {1} \u306e\u305f\u3081\u306b\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u3092Sun\u306eJava\u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u3057\u307e\u3059\u3002
+jsp.warning.compiler.class.notfound=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d7\u30e9\u30b0\u30a4\u30f3\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002not found. \u30c7\u30d5\u30a9\u30eb\u30c8\u3092Sun\u306eJava\u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u3057\u307e\u3059\u3002
+jsp.warning.compiler.path.notfound=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d1\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30b7\u30b9\u30c6\u30e0\u306ePATH\u3092\u30c7\u30d5\u30a9\u30eb\u30c8\u306b\u3057\u307e\u3059\u3002
+jsp.error.jspc.uriroot_not_dir=-uriroot \u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u3001\u65e2\u306b\u5b58\u5728\u3059\u308b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspc.missingTarget=\u30bf\u30fc\u30b2\u30c3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093: -webapp\u53c8\u306f-uriroot\uff0c\u53c8\u306f\u4e00\u3064\u4ee5\u4e0a\u306eJSP\u30da\u30fc\u30b8\u3092\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspc.no_uriroot=uriroot\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u306e\u3067\u3001\u6307\u5b9a\u3055\u308c\u305fJSP\u30d5\u30a1\u30a4\u30eb(\u7fa4)\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jspc.implicit.uriRoot=uriRoot\u306f\u30c7\u30d5\u30a9\u30eb\u30c8"{0}"\u306b\u8a2d\u5b9a\u3055\u308c\u307e\u3059
+jspc.usage=\u4f7f\u7528\u6cd5: jspc <options> [--] <jsp files>\n\
+JSP\u30d5\u30a1\u30a4\u30eb\u306e\u5834\u6240\u306f\u6b21\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6307\u5b9a\u3059\u308b\u304b\u3001\n\
+\    -webapp <dir>   web-app\u3092\u542b\u3080\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3002\u3059\u3079\u3066\u306eJSP\u30d5\u30a1\u30a4\u30eb\u306f\n\
+\                    \u518d\u5e30\u7684\u306b\u89e3\u6790\u3055\u308c\u308b\n\
+\u53c8\u306f\u6b21\u306e\u4efb\u610f\u306e\u6570\u306e\u30d5\u30a1\u30a4\u30eb\u3067\u6307\u5b9a\u3057\u307e\u3059\u3002\n\
+\    <file>          JSP\u3068\u3057\u3066\u89e3\u6790\u3055\u308c\u308b\u30d5\u30a1\u30a4\u30eb\n\
+\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u4ee5\u4e0b\u306e\u901a\u308a\u3067\u3059\n\
+\    -help           \u3053\u306e\u30d8\u30eb\u30d7\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8868\u793a\n\
+\    -v              Verbose\u30e2\u30fc\u30c9\n\
+\    -d <dir>        \u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\n\
+\    -l              \u5931\u6557\u3057\u305fJSP\u30da\u30fc\u30b8\u306e\u540d\u524d\u306e\u51fa\u529b\n\
+\    -s              \u6210\u529f\u3057\u305fJSP\u30da\u30fc\u30b8\u306e\u540d\u524d\u306e\u51fa\u529b\n\
+\    -p <name>       \u30bf\u30fc\u30b2\u30c3\u30c8\u30d1\u30c3\u30b1\u30fc\u30b8\u306e\u540d\u524d  (\u30c7\u30d5\u30a9\u30eb\u30c8\u306forg.apache.jsp)\n\
+\    -c <name>       \u30bf\u30fc\u30b2\u30c3\u30c8\u30af\u30e9\u30b9\u306e\u540d\u524d (\u6700\u521d\u306eJSP\u30da\u30fc\u30b8\u3060\u3051\u306b\u9069\u7528\u3055\u308c\u308b)\n\
+\    -mapped         JSP\u306e\u5404HTML\u884c\u3054\u3068\u306bwrite()\u30b3\u30fc\u30eb\u3092\u751f\u6210\n\
+\    -die[#]         \u81f4\u547d\u7684\u30a8\u30e9\u30fc\u306b\u30a8\u30e9\u30fc\u30ea\u30bf\u30fc\u30f3\u30b3\u30fc\u30c9(#)\u3092\u751f\u6210 (\u30c7\u30d5\u30a9\u30eb\u30c8\u306f1)\n\
+\    -uribase <dir>  \u30b3\u30f3\u30d1\u30a4\u30eb\u304c\u76f8\u5bfe\u7684\u306b\u304a\u3053\u306a\u308f\u308c\u308buri\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\n\
+\                    (\u30c7\u30d5\u30a9\u30eb\u30c8\u306f"/")\n\
+\    -uriroot <dir>  -webapp\u3068\u540c\u3058\n\
+\    -compile        \u751f\u6210\u3057\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30b3\u30f3\u30d1\u30a4\u30eb\n\
+\    -webinc <file>  \u30d5\u30a1\u30a4\u30eb\u306b\u90e8\u5206\u7684\u306a\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30de\u30c3\u30d4\u30f3\u30b0\u3092\u4f5c\u6210\n\
+\    -webxml <file>  \u30d5\u30a1\u30a4\u30eb\u306b\u5b8c\u5168\u306aweb.xml\u3092\u4f5c\u6210\n\
+\    -ieplugin <clsid>  Internet Explorer\u306eJava Plugin\u306eclassid\n\
+\    -classpath <path>  java.class.path\u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u4e0a\u66f8\u304d\n\
+\    -xpoweredBy        X-Powered-By\u30ec\u30b9\u30dd\u30f3\u30b9\u30d8\u30c3\u30c0\u306e\u8ffd\u52a0\n\
+\    -trimSpaces        \u30a2\u30af\u30b7\u30e7\u30f3\u3084\u6307\u793a\u5b50\u306e\u9593\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8\u4e2d\u306e\u30b9\u30da\u30fc\u30b9\u3092\u524a\u9664\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.4)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Automatically created by Apache Jakarta Tomcat JspC.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Automatically created by Apache Jakarta Tomcat JspC.\n\
+Place this fragment in the web.xml before all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.webinc.insertEnd=<!-- JSPC servlet mappings end -->
+jspc.webinc.insertStart=<!-- JSPC servlet mappings start -->
+jspc.error.jasperException=\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb ''{0}'' \u306f\u6b21\u306e\u4f8b\u5916\u3092\u767a\u751f\u3057\u307e\u3057\u305f: {1}
+jspc.error.generalException=\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb ''{0}'' \u306f\u6b21\u306e\u4f8b\u5916\u3092\u767a\u751f\u3057\u307e\u3057\u305f:
+jspc.error.fileDoesNotExist=\u30d5\u30a1\u30a4\u30eb\u5f15\u6570 ''{0}'' \u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002
+jspc.error.emptyWebApp=-webapp\u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u3001\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.library.invalid=\u30e9\u30a4\u30d6\u30e9\u30ea{0}\u306b\u5f93\u3046\u3068JSP\u30da\u30fc\u30b8\u306f\u7121\u52b9\u3067\u3059: {1}
+jsp.error.tlvclass.instantiation=TagLibraryValidator\u30af\u30e9\u30b9\u306e\u30ed\u30fc\u30c9\u53c8\u306f\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+jsp.error.tlv.invalid.page={0} \u306b\u5bfe\u3059\u308bTagLibraryValidator\u306e\u691c\u8a3c\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3067\u3059 ({1})
+jsp.error.tei.invalid.attributes={0} \u306b\u5bfe\u3059\u308bTagExtraInfo\u304b\u3089\u306e\u691c\u8a3c\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3067\u3059
+jsp.parser.sax.propertynotsupported=SAX\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.propertynotrecognized=SAX\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.featurenotsupported=SAX\u30d5\u30a3\u30fc\u30c1\u30e3\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.featurenotrecognized=SAX\u30d5\u30a3\u30fc\u30c1\u30e3\u304c\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.error.no.more.content=\u5fc5\u8981\u306a\u89e3\u6790\u4e2d\u306b\u5185\u5bb9\u306e\u6700\u5f8c\u307e\u3067\u9054\u3057\u307e\u3057\u305f: \u30bf\u30b0\u306e\u30cd\u30b9\u30c8\u306e\u30a8\u30e9\u30fc\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.parse.xml=\u30d5\u30a1\u30a4\u30eb{0}\u306eXML\u89e3\u6790\u30a8\u30e9\u30fc
+jsp.error.parse.xml.line=\u30d5\u30a1\u30a4\u30eb{0}\u306eXML\u89e3\u6790\u30a8\u30e9\u30fc: (\u884c {1}, \u5217 {2})
+jsp.error.parse.xml.scripting.invalid.body={0} \u8981\u7d20\u306e\u30dc\u30c7\u30a3\u306fXML\u8981\u7d20\u3092\u542b\u3093\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.internal.tldinit=TldLocationsCache\u3092\u521d\u671f\u5316\u4e2d\u306e\u4f8b\u5916\u3067\u3059: {0}
+jsp.error.internal.filenotfound=\u5185\u90e8\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.internal.evaluator_not_found=\u5185\u90e8\u30a8\u30e9\u30fc: \u5f0f\u691c\u8a3c\u5668\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.parse.xml.invalidPublicId=\u7121\u52b9\u306aPUBLIC ID: {0}
+jsp.error.include.flush.invalid.value=flush\u5c5e\u6027\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059: {0}
+jsp.error.unsupported.encoding=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u306a\u3044\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u3067\u3059: {0}
+tld.error.variableNotAllowed=null\u3067\u306a\u3044\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3092\u8fd4\u3059TagExtraInfo\u3092\u6301\u3064\u4e00\u3064\u4ee5\u4e0a\u306e\u5909\u6570\u526f\u8981\u7d20\u3092\u6301\u3064\u30bf\u30b0\u306f\u30a8\u30e9\u30fc\u3067\u3059\u3002
+jsp.error.tldInWebDotXmlNotFound=web.xml\u3067\u6307\u5b9a\u3055\u308c\u305fURI {0} \u306bTLD\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jsp.error.taglibDirective.absUriCannotBeResolved=\u7d76\u5bfeURI:  {0} \u306fweb.xml\u3068\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u305fJAR\u30d5\u30a1\u30a4\u30eb\u306e\u3069\u3061\u3089\u304b\u3067\u3082\u89e3\u6c7a\u3067\u304d\u307e\u305b\u3093
+jsp.error.taglibDirective.missing.location=taglib\u6307\u793a\u5b50\u306e\u4e2d\u306b'uri'\u5c5e\u6027\u3068'tagdir'\u5c5e\u6027\u306e\u3069\u3061\u3089\u3082\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.taglibDirective.both_uri_and_tagdir=\'uri\'\u5c5e\u6027 \u3068 \'tagdir\'\u5c5e\u6027\u306e\u4e21\u65b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.tagdir=\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u304c\"/WEB-INF/tags\"\u3067\u59cb\u307e\u308a\u307e\u305b\u3093
+jsp.error.unterminated.user.tag=\u672a\u7d42\u4e86\u306e\u30e6\u30fc\u30b6\u5b9a\u7fa9\u30bf\u30b0: \u7d42\u4e86\u30bf\u30b0 {0} \u304c\u898b\u3064\u304b\u3089\u306a\u3044\u304b\u3001\u30cd\u30b9\u30c8\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=\u8a3c\u660e\u30a8\u30e9\u30fc: \u8981\u7d20&lt;{0}&gt;\u306f\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u306f\u3001&lt;jsp:text&gt;\u8981\u7d20\u306e\u4e2d\u3067\u96a0\u853d\u3055\u308c\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u3002[JSP1.2 PFD 5.1.9]\n\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u306e\u30a8\u30e9\u30fc\u3067\u3059: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix=taglib\u30d7\u30ea\u30d5\u30a3\u30af\u30b9 {0} \u306f\u4e88\u7d04\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.javaEncoding=\u7121\u52b9\u306aJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u3067\u3059\u3002{0}\u3092\u8a66\u3057\u3066\u3001\u305d\u308c\u304b\u3089{1}\u3092\u8a66\u3057\u307e\u3057\u305f\u304c\u3001\u4e21\u65b9\u304c\u5931\u6557\u3057\u307e\u3057\u305f
+jsp.error.needAlternateJavaEncoding=\u30c7\u30d5\u30a9\u30eb\u30c8\u306eJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 {0} \u306f\u3042\u306a\u305f\u306e\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3067\u306f\u7121\u52b9\u3067\u3059\u3002JspServlet\u306e 'javaEncoding' \u30d1\u30e9\u30e1\u30bf\u3067\u3001\u5225\u306e\u5024\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number=JSP\u30d5\u30a1\u30a4\u30eb: {1} \u306e\u4e2d\u306e{0}\u884c\u76ee\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jsp.error.multiple.line.number=\n\nJPS\u30d5\u30a1\u30a4\u30eb: {2}\u306e\u4e2d\u306e{0}\u884c\u76ee\u3068{1}\u884c\u76ee\u306e\u9593\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\n\n
+jsp.error.corresponding.servlet=\u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30a8\u30e9\u30fc\u3067\u3059:\n
+jsp.error.empty.body.not.allowed={0} \u306b\u5bfe\u3057\u3066\u7a7a\u306e\u30dc\u30c7\u30a3\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.jspbody.required=jsp:attribute\u304c\u4f7f\u7528\u3055\u308c\u305f\u5834\u5408\u306b\u306f\u3001{0}\u306b\u30bf\u30b0\u30dc\u30c7\u30a3\u3092\u6307\u5b9a\u3059\u308b\u305f\u3081\u306bjsp:body\u3092\u4f7f\u7528\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspbody.emptybody.only={0} \u30bf\u30b0\u306f\u3001\u305d\u306e\u30dc\u30c7\u30a3\u4e2d\u306bjsp:attribute\u3060\u3051\u3092\u6301\u3064\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.no.scriptlets=\u30b9\u30af\u30ea\u30d7\u30c6\u30a3\u30f3\u30b0\u8981\u7d20 ( &lt;%!\u3001&lt;jsp:declaration\u3001&lt;%=\u3001&lt;jsp:expression\u3001&lt;%\u3001&lt;jsp:scriptlet ) \u306f\u3053\u3053\u3067\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.internal.unexpected_node_type=\u5185\u90e8\u30a8\u30e9\u30fc: \u672a\u77e5\u306e\u30ce\u30fc\u30c9\u30bf\u30a4\u30d7\u304c\u8868\u308c\u307e\u3057\u305f
+jsp.error.tld.fn.invalid.signature=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}
+jsp.error.tld.fn.duplicate.name=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea {1} \u306e\u4e2d\u306e\u95a2\u6570\u540d {0} \u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059
+jsp.error.tld.fn.invalid.signature.commaexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u30b3\u30f3\u30de ',' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
+jsp.error.tld.fn.invalid.signature.parenexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u62ec\u5f27 '(' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
+jsp.error.tld.mandatory.element.missing=\u5fc5\u9808TLD\u8981\u7d20\u304c\u306a\u3044\u3001\u53c8\u306f\u7a7a\u3067\u3059: {0}
+jsp.error.dynamic.attributes.not.implemented={0} \u30bf\u30b0\u306f\u305d\u308c\u304cdynamic\u5c5e\u6027\u3092\u53d7\u3051\u4ed8\u3051\u308b\u3068\u5ba3\u8a00\u3057\u3066\u3044\u307e\u3059\u304c\u3001\u305d\u308c\u306b\u5fc5\u8981\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.nomatching.fragment=attribute\u6307\u793a\u5b50 (name={0}\u304a\u3088\u3073fragment=true\u3092\u6301\u3064) \u304cfragment\u6307\u793a\u5b50\u3088\u308a\u524d\u306b\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.attribute.noequal=\u7b49\u53f7\u8a18\u53f7\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.attribute.noquote=\u5f15\u7528\u7b26\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.attribute.unterminated={0} \u306e\u5c5e\u6027\u304c\u6b63\u3057\u304f\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.missing.tagInfo={0} \u306b\u5bfe\u3059\u308bTagInfo\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u304cTLD\u304b\u3089\u5931\u308f\u308c\u307e\u3057\u305f
+jsp.error.fragmentwithtype='fragment'\u5c5e\u6027\u3068'type'\u5c5e\u6027\u3092\u4e21\u65b9\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002'fragment'\u304c\u5b58\u5728\u3059\u308b\u5834\u5408\u306b\u306f'type'\u306f'javax.servlet.jsp.tagext.JspFragment'\u306b\u56fa\u5b9a\u3055\u308c\u307e\u3059
+jsp.error.fragmentwithrtexprvalue='fragment'\u5c5e\u6027\u3068'rtexprvalue'\u5c5e\u6027\u3092\u4e21\u65b9\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002'fragment'\u304c\u5b58\u5728\u3059\u308b\u5834\u5408\u306b\u306f'rtexprvalue'\u306f'true'\u306b\u56fa\u5b9a\u3055\u308c\u307e\u3059
+jsp.error.fragmentWithDeclareOrScope='fragment'\u5c5e\u6027\u3068'declare'\u5c5e\u6027\u306e\u4e21\u65b9\u53c8\u306f'scope'\u5c5e\u6027\u304cvariable\u6307\u793a\u5b50\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.var_and_varReader=\'var\'\u53c8\u306f\'varReader\'\u306e\u3069\u3061\u3089\u304b\u4e00\u3064\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.missing_var_or_varReader=\'var\'\u53c8\u306f\'varReader\'\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.warning.bad.urlpattern.propertygroup=web.xml\u4e2d\u306eurl-pattern\u526f\u8981\u7d20\u4e2d\u306b\u8aa4\u3063\u305f\u5024 {0} \u304c\u3042\u308a\u307e\u3059
+jsp.error.unknown_attribute_type=\u5c5e\u6027 {0} \u306b\u5bfe\u3059\u308b\u672a\u77e5\u306e\u5c5e\u6027\u30bf\u30a4\u30d7\u3067\u3059
+jsp.error.jspelement.missing.name=\u5fc5\u9808\u306eXML\u30b9\u30bf\u30a4\u30eb\u306e'name'\u5c5e\u6027\u304cjsp:element\u4e2d\u306b\u3042\u308a\u307e\u305b\u3093
+jsp.error.xmlns.redefinition.notimplemented=\u5185\u90e8\u30a8\u30e9\u30fc: xmlns:{0}\u3092\u518d\u5b9a\u7fa9\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u3002\u540d\u524d\u7a7a\u9593\u306e\u518d\u5b9a\u7fa9\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+jsp.error.could.not.add.taglibraries=1\u3064\u4ee5\u4e0a\u306e\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093
+jsp.error.duplicate.name.jspattribute=\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u5c5e\u6027 {0} \u306f\u305d\u308c\u306b\u56f2\u307e\u308c\u305fjsp:attribute\u4e2d\u306ename\u5c5e\u6027\u306e\u5024\u3068\u3057\u3066\u3082\u8868\u308c\u307e\u3059
+jsp.error.not.in.template=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8\u30dc\u30c7\u30a3\u4e2d\u3067\u306f {0} \u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.badStandardAction=\u7121\u52b9\u306a\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u3059
+jsp.error.xml.badStandardAction=\u7121\u52b9\u306a\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u3059: {0}
+jsp.error.tagdirective.badbodycontent=tag\u6307\u793a\u5b50\u4e2d\u306e\u7121\u52b9\u306abody-content ({0})\u3067\u3059
+jsp.error.simpletag.badbodycontent=\u30af\u30e9\u30b9 {0} \u306eTLD\u306fSimpleTag\u306b\u7121\u52b9\u306abody-content (JSP)\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059
+jsp.error.config_pagedir_encoding_mismatch=jsp-property-group\u4e2d\u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308bPage-encoding ({0}) \u304cpage\u6307\u793a\u5b50\u4e2d\u306e\u6307\u5b9a ({1}) \u3068\u9055\u3044\u307e\u3059
+jsp.error.prolog_pagedir_encoding_mismatch=XML\u5c0e\u5165\u90e8\u3067\u6307\u5b9a\u3055\u308c\u305fpage-encoding ({0}) \u304cpage\u6307\u793a\u5b50\u4e2d\u306e\u6307\u5b9a ({1}) \u3068\u9055\u3044\u307e\u3059
+jsp.error.prolog_config_encoding_mismatch=XML\u5c0e\u5165\u90e8\u3067\u6307\u5b9a\u3055\u308c\u305fpage-encoding ({0}) \u304cjsp-property-group\u4e2d\u306e\u6307\u5b9a\u3068\u9055\u3044\u307e\u3059 ({1})
+jsp.error.attribute.custom.non_rt_with_expr=TLD\u53c8\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u306eattribute\u6307\u793a\u5b50\u306b\u5f93\u3063\u3066\u5c5e\u6027{0}\u306f\u3069\u3093\u306a\u5f0f\u3082\u53d7\u3051\u4ed8\u3051\u307e\u305b\u3093
+jsp.error.attribute.standard.non_rt_with_expr={1} \u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e {0} \u5c5e\u6027\u306f\u3069\u3093\u306a\u5f0f\u3082\u53d7\u3051\u4ed8\u3051\u307e\u305b\u3093
+jsp.error.scripting.variable.missing_name=\u5c5e\u6027 {0} \u304b\u3089\u30b9\u30af\u30ea\u30d7\u30c8\u5909\u6570\u540d\u3092\u6c7a\u5b9a\u3067\u304d\u307e\u305b\u3093
+jasper.error.emptybodycontent.nonempty=TLD\u306b\u5f93\u3063\u3066\u30bf\u30b0 {0} \u306f\u7a7a\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u304c\u3001\u305d\u3046\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.tagfile.nameNotUnique={2}\u884c\u76ee\u306e {0} \u306e\u5024\u3068 {1} \u306e\u5024\u306f\u540c\u3058\u3067\u3059
+jsp.error.tagfile.nameFrom.noAttribute=\u3053\u306ename-from-attribute\u5c5e\u6027\u306e\u5024\u3067\u3042\u308b\u5024 \"{0}\" \u306ename\u5c5e\u6027\u3092\u6301\u3064attribute\u6307\u793a\u5b50\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.tagfile.nameFrom.badAttribute=attribute\u6307\u793a\u5b50 ({1}\u884c\u76ee\u3067\u5ba3\u8a00\u3055\u308c\u3001\u305d\u306ename\u5c5e\u6027\u304c\"{0}\"\u3001\u3053\u306ename-from-attribute\u5c5e\u6027\u306e\u5024) \u306fjava.lang.String\u578b\u306e\"required\" \u3067 \"rtexprvalue\".\u3067\u3042\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.page.noSession=\u30bb\u30c3\u30b7\u30e7\u30f3\u306b\u52a0\u308f\u3063\u3066\u3044\u306a\u3044\u30da\u30fc\u30b8\u306e\u4e2d\u3067\u306f\u30bb\u30c3\u30b7\u30e7\u30f3\u30b9\u30b3\u30fc\u30d7\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093
+jsp.error.useBean.noSession=JSP\u30da\u30fc\u30b8\u304c(page\u6307\u793a\u5b50\u306b\u3088\u308a)\u30bb\u30c3\u30b7\u30e7\u30f3\u4e2d\u3067\u5354\u8abf\u3057\u306a\u3044\u3053\u3068\u3092\u5ba3\u8a00\u3057\u3066\u3044\u308b\u6642\u3001\u30bb\u30c3\u30b7\u30e7\u30f3\u30b9\u30b3\u30fc\u30d7\u3092\u4f7f\u7528\u3059\u308b\u305f\u3081\u306euseBean\u304c\u4e0d\u6b63\u3067\u3059
+jsp.error.xml.encodingByteOrderUnsupported = \u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 \"{0}\" \u306b\u6307\u5b9a\u3055\u308c\u305f\u30d0\u30a4\u30c8\u30aa\u30fc\u30c0\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.encodingDeclInvalid = \u7121\u52b9\u306a\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u540d \"{0}\" \u3067\u3059
+jsp.error.xml.encodingDeclRequired = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306b\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u5ba3\u8a00\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.morePseudoAttributes = \u3088\u308a\u591a\u304f\u306e\u7591\u4f3c\u5c5e\u6027\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.noMorePseudoAttributes = \u3053\u308c\u4ee5\u4e0a\u306e\u7591\u4f3c\u5c5e\u6027\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.xml.versionInfoRequired = XML\u5ba3\u8a00\u306e\u4e2d\u306b\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.xmlDeclUnterminated = XML\u5ba3\u8a00\u306f\"?>\"\u3067\u7d42\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.reservedPITarget = \"[xX][mM][lL]\"\u306b\u4e00\u81f4\u3059\u308b\u51e6\u7406\u547d\u4ee4\u30bf\u30fc\u30b2\u30c3\u30c8\u306f\u8a31\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.spaceRequiredInPI = \u7a7a\u767d\u304c\u51e6\u7406\u547d\u4ee4\u30bf\u30fc\u30b2\u30c3\u30c8\u3068\u30c7\u30fc\u30bf\u306e\u9593\u306b\u5fc5\u8981\u3067\u3059
+jsp.error.xml.invalidCharInContent = \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306e\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306e\u8981\u7d20\u5185\u5bb9\u306e\u4e2d\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.spaceRequiredBeforeStandalone = XML\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.sdDeclInvalid = \u30b9\u30bf\u30f3\u30c9\u30a2\u30ed\u30f3\u6587\u66f8\u5ba3\u8a00\u5024\u306f\"yes\"\u53c8\u306f\"no\"\u306e\u3069\u3061\u3089\u304b\u3067\u3042\u308a\u3001\"{0}\"\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.invalidCharInPI = \u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u547d\u4ee4\u51e6\u7406\u4e2d\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.versionNotSupported = XML\u30d0\u30fc\u30b8\u30e7\u30f3 \"{0}\" \u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3001XML 1.0\u3060\u3051\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059
+jsp.error.xml.pseudoAttrNameExpected = \u7591\u4f3c\u5c5e\u6027\u540d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.expectedByte ={1}\u30d0\u30a4\u30c8UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u30d0\u30a4\u30c8 {0} \u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.invalidByte = {1}\u30d0\u30a4\u30c8UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u7121\u52b9\u306a\u30d0\u30a4\u30c8 {0} \u3067\u3059
+jsp.error.xml.operationNotSupported = {1} reader\u306f\u64cd\u4f5c \"{0}\" \u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.invalidHighSurrogate = UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u30cf\u30a4\u30b5\u30ed\u30b2\u30fc\u30c8\u30d3\u30c3\u30c8\u306f0x10\u3092\u8d8a\u3048\u3066\u306f\u3044\u3051\u307e\u305b\u3093\u304c\u30010x{0}\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.invalidASCII = \u30d0\u30a4\u30c8 \"{0}\" \u306f7\u30d3\u30c3\u30c8ASCII\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = XML\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306eversion\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = XML\u5ba3\u8a00\u306eversion\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.eqRequiredInXMLDecl = XML\u5ba3\u8a00\u4e2d\u3067\"{0}\"\u306e\u6b21\u306b'' = '' \u6587\u5b57\u304c\u7d9a\u304b\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.eqRequiredInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u3067\"{0}\"\u306e\u6b21\u306b'' = ''\u6587\u5b57\u304c\u7d9a\u304b\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.quoteRequiredInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306f\u30af\u30aa\u30fc\u30c8\u3067\u56f2\u307e\u308c\u305f\u6587\u5b57\u5217\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.quoteRequiredInXMLDecl = XML\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306f\u30af\u30aa\u30fc\u30c8\u3067\u56f2\u307e\u308c\u305f\u6587\u5b57\u5217\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.invalidCharInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306e\u4e2d\u306b\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.invalidCharInXMLDecl = XML\u5ba3\u8a00\u306e\u4e2d\u306b\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.closeQuoteMissingInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306e\u4e2d\u306e\u6700\u5f8c\u306e\u30af\u30aa\u30fc\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.xml.closeQuoteMissingInXMLDecl = XML\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306e\u4e2d\u306e\u6700\u5f8c\u306e\u30af\u30aa\u30fc\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.xml.invalidHighSurrogate = UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u30cf\u30a4\u30b5\u30ed\u30b2\u30fc\u30c8\u30d3\u30c3\u30c8\u306f0x10\u3092\u8d8a\u3048\u3066\u306f\u3044\u3051\u307e\u305b\u3093\u304c\u30010x{0}\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.multiple.jsp = \u8907\u6570\u306e\u4ed5\u69d8\u3092\u6e80\u305f\u3059\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.jspoutput.conflict=&lt;jsp:output&gt;: \"{0}\"\u306b\u7570\u306a\u308b\u5024\u3092\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {1}, \u65b0: {2})
+jsp.error.jspoutput.doctypenamesystem=&lt;jsp:output&gt;: 'doctype-root-element' \u53ca\u3073 'doctype-system' \u5c5e\u6027\u306f\u540c\u6642\u306b\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.doctypepulicsystem=&lt;jsp:output&gt;: 'doctype-public'\u5c5e\u6027\u3092\u6307\u5b9a\u3059\u308b\u5834\u5408\u306f\u3001'doctype-system' \u5c5e\u6027\u3082\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.nonemptybody=&lt;jsp:output&gt; \u30dc\u30c7\u30a3\u3092\u6301\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.invalidUse=&lt;jsp:output&gt; \u6a19\u6e96\u69cb\u6587\u306e\u4e2d\u3067\u4f7f\u7528\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.attributes.not.allowed = {0} \u306f\u5c5e\u6027\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.tagfile.badSuffix=\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9 {0} \u306e\u4e2d\u306b\".tag\" \u62e1\u5f35\u5b50\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.tagfile.illegalPath=\u4e0d\u6b63\u306a\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9\u3067\u3059: {0}\u3001\u3053\u308c\u306f\"/WEB-INF/tags\"\u53c8\u306f\"/META-INF/tags\"\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.wrongRootElement={0} \u306e\u4e2d\u306e\u30eb\u30fc\u30c8\u8981\u7d20\u306e\u540d\u524d\u306f {1} \u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.attribute.invalidPrefix=\u5c5e\u6027\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u306f\u3069\u306e\u53d6\u308a\u8fbc\u307e\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306b\u3082\u5bfe\u5fdc\u3057\u307e\u305b\u3093
+jsp.error.nested.jspattribute=jsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u5225\u306ejsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u7bc4\u56f2\u5185\u3067\u30cd\u30b9\u30c8\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.nested.jspbody=jsp:body\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u5225\u306ejsp:body\u53c8\u306fjsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u7bc4\u56f2\u5185\u3067\u30cd\u30b9\u30c8\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.variable.either.name=name-given\u53c8\u306fname-from-attribute\u5c5e\u6027\u306e\u3069\u3061\u3089\u304b\u3092variable\u6307\u793a\u5b50\u306e\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.variable.both.name=variable\u6307\u793a\u5b50\u4e2d\u3067name-given\u3068name-from-attribute\u5c5e\u6027\u306e\u4e21\u65b9\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.variable.alias=name-from-attribute\u304a\u3088\u3073alias\u5c5e\u6027\u306e\u4e21\u65b9\u3092variable\u6307\u793a\u5b50\u4e2d\u306b\u6307\u5b9a\u3059\u308b\u3001\u53c8\u306f\u3069\u3061\u3089\u3082\u6307\u5b9a\u3057\u306a\u3044\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.attribute.null_name=\u7a7a\u306e\u5c5e\u6027\u540d\u3067\u3059
+jsp.error.jsptext.badcontent=\'&lt;\'\u304c&lt;jsp:text&gt;\u306e\u30dc\u30c7\u30a3\u306e\u4e2d\u306b\u73fe\u308c\u308b\u6642\u306f\u3001CDATA\u306e\u4e2d\u306b\u96a0\u853d\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jsproot.version.invalid=\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u756a\u53f7\u3067\u3059: \"{0}\"\u3001\"1.2\" \u53c8\u306f \"2.0\"\u3000\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.noFunctionPrefix=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u540d\u524d\u7a7a\u9593\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u6642\u306b\u306f\u3001\u95a2\u6570 {0} \u306f\u30d7\u30ea\u30d5\u30a3\u30af\u30b9\u4ed8\u304d\u3067\u4f7f\u7528\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.noFunction=\u95a2\u6570 {0} \u3092\u6307\u5b9a\u3055\u308c\u305f\u30d7\u30ea\u30d5\u30a3\u30af\u30b9\u3067\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jsp.error.noFunctionMethod=\u95a2\u6570 \"{1}\" \u306e\u30e1\u30bd\u30c3\u30c9 \"{0}\" \u304c \"{2}\" \u4e2d\u3067\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.function.classnotfound=TLD\u306e\u4e2d\u3067\u95a2\u6570 {1} \u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {2}
+jsp.error.signature.classnotfound=TLD\u306e\u4e2d\u306e\u30e1\u30bd\u30c3\u30c9\u30b7\u30b0\u30cd\u30c1\u30e3\u3067\u95a2\u6570 {1} \u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 {2}
+jsp.error.text.has_subelement=&lt;jsp:text&gt; \u306f\u526f\u8981\u7d20\u3092\u6301\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.data.file.read=\u30d5\u30a1\u30a4\u30eb \"{0}\" \u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jsp.error.prefix.refined=\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u304c\u73fe\u5728\u306e\u30b9\u30b3\u30fc\u30d7\u4e2d\u3067\u65e2\u306b {2} \u3068\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u308b\u306e\u3067 {1} \u306b\u518d\u5b9a\u7fa9\u3057\u307e\u3057\u305f
+jsp.error.nested_jsproot=\u5165\u308c\u5b50\u306b\u306a\u3063\u305f &lt;jsp:root&gt; \u3067\u3059
+jsp.error.unbalanced.endtag=\u7d42\u4e86\u30bf\u30b0 \"&lt;/{0}\" \u306e\u5bfe\u5fdc\u304c\u53d6\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.invalid.bean=useBean\u306e\u30af\u30e9\u30b9\u5c5e\u6027 {0} \u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.prefix.use_before_dcl=\u3053\u306e\u30bf\u30b0\u6307\u793a\u5b50\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u306f\u3001\u3059\u3067\u306b\u30d5\u30a1\u30a4\u30eb {1} \u306e {2} \u884c\u76ee\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java
new file mode 100644
index 0000000..428f3d2
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/AnnotationHelper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jasper.runtime;
+
+import java.lang.reflect.InvocationTargetException;
+
+import javax.naming.NamingException;
+
+import org.apache.AnnotationProcessor;
+
+
+/**
+ * Verify the annotation and Process it.
+ *
+ * @author Fabien Carrion
+ * @author Remy Maucherat
+ * @version $Revision$, $Date$
+ */
+public class AnnotationHelper {
+
+    
+    /**
+     * Call postConstruct method on the specified instance. Note: In Jasper, this
+     * calls naming resources injection as well.
+     */
+    public static void postConstruct(AnnotationProcessor processor, Object instance)
+        throws IllegalAccessException, InvocationTargetException, NamingException {
+        if (processor != null) {
+            processor.processAnnotations(instance);
+            processor.postConstruct(instance);
+        }
+    }
+    
+    
+    /**
+     * Call preDestroy method on the specified instance.
+     */
+    public static void preDestroy(AnnotationProcessor processor, Object instance)
+        throws IllegalAccessException, InvocationTargetException {
+        if (processor != null) {
+            processor.preDestroy(instance);
+        }
+    }
+    
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java
new file mode 100644
index 0000000..1c2a8e7
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java
@@ -0,0 +1,604 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Write text to a character-output stream, buffering characters so as
+ * to provide for the efficient writing of single characters, arrays,
+ * and strings. 
+ *
+ * Provide support for discarding for the output that has been buffered. 
+ *
+ * @author Rajiv Mordani
+ * @author Jan Luehe
+ */
+public class BodyContentImpl extends BodyContent {
+    
+    private static final String LINE_SEPARATOR = 
+        System.getProperty("line.separator");
+    private static final boolean LIMIT_BUFFER = 
+        Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "false")).booleanValue();
+    
+    private char[] cb;
+    private int nextChar;
+    private boolean closed;
+    
+    // Enclosed writer to which any output is written
+    private Writer writer;
+    
+    /**
+     * Constructor.
+     */
+    public BodyContentImpl(JspWriter enclosingWriter) {
+        super(enclosingWriter);
+        bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
+        cb = new char[bufferSize];
+        nextChar = 0;
+        closed = false;
+    }
+    
+    /**
+     * Write a single character.
+     */
+    public void write(int c) throws IOException {
+        if (writer != null) {
+            writer.write(c);
+        } else {
+            ensureOpen();
+            if (nextChar >= bufferSize) {
+                reAllocBuff (1);
+            }
+            cb[nextChar++] = (char) c;
+        }
+    }
+    
+    /**
+     * Write a portion of an array of characters.
+     *
+     * <p> Ordinarily this method stores characters from the given array into
+     * this stream's buffer, flushing the buffer to the underlying stream as
+     * needed.  If the requested length is at least as large as the buffer,
+     * however, then this method will flush the buffer and write the characters
+     * directly to the underlying stream.  Thus redundant
+     * <code>DiscardableBufferedWriter</code>s will not copy data
+     * unnecessarily.
+     *
+     * @param cbuf A character array
+     * @param off Offset from which to start reading characters
+     * @param len Number of characters to write
+     */
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        if (writer != null) {
+            writer.write(cbuf, off, len);
+        } else {
+            ensureOpen();
+            
+            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+                    ((off + len) > cbuf.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return;
+            } 
+            
+            if (len >= bufferSize - nextChar)
+                reAllocBuff (len);
+            
+            System.arraycopy(cbuf, off, cb, nextChar, len);
+            nextChar+=len;
+        }
+    }
+    
+    /**
+     * Write an array of characters.  This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     */
+    public void write(char[] buf) throws IOException {
+        if (writer != null) {
+            writer.write(buf);
+        } else {
+            write(buf, 0, buf.length);
+        }
+    }
+    
+    /**
+     * Write a portion of a String.
+     *
+     * @param s String to be written
+     * @param off Offset from which to start reading characters
+     * @param len Number of characters to be written
+     */
+    public void write(String s, int off, int len) throws IOException {
+        if (writer != null) {
+            writer.write(s, off, len);
+        } else {
+            ensureOpen();
+            if (len >= bufferSize - nextChar)
+                reAllocBuff(len);
+            
+            s.getChars(off, off + len, cb, nextChar);
+            nextChar += len;
+        }
+    }
+    
+    /**
+     * Write a string.  This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     */
+    public void write(String s) throws IOException {
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            write(s, 0, s.length());
+        }
+    }
+    
+    /**
+     * Write a line separator.  The line separator string is defined by the
+     * system property <tt>line.separator</tt>, and is not necessarily a single
+     * newline ('\n') character.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    public void newLine() throws IOException {
+        if (writer != null) {
+            writer.write(LINE_SEPARATOR);
+        } else {
+            write(LINE_SEPARATOR);
+        }
+    }
+    
+    /**
+     * Print a boolean value.  The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param b The <code>boolean</code> to be printed
+     * @throws IOException
+     */
+    public void print(boolean b) throws IOException {
+        if (writer != null) {
+            writer.write(b ? "true" : "false");
+        } else {
+            write(b ? "true" : "false");
+        }
+    }
+    
+    /**
+     * Print a character.  The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param c The <code>char</code> to be printed
+     * @throws IOException
+     */
+    public void print(char c) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(c));
+        } else {
+            write(String.valueOf(c));
+        }
+    }
+    
+    /**
+     * Print an integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are
+     * written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param i The <code>int</code> to be printed
+     * @throws IOException
+     */
+    public void print(int i) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(i));
+        } else {
+            write(String.valueOf(i));
+        }
+    }
+    
+    /**
+     * Print a long integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param l The <code>long</code> to be printed
+     * @throws IOException
+     */
+    public void print(long l) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(l));
+        } else {
+            write(String.valueOf(l));
+        }
+    }
+    
+    /**
+     * Print a floating-point number.  The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param f The <code>float</code> to be printed
+     * @throws IOException
+     */
+    public void print(float f) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(f));
+        } else {
+            write(String.valueOf(f));
+        }
+    }
+    
+    /**
+     * Print a double-precision floating-point number.  The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param d The <code>double</code> to be printed
+     * @throws IOException
+     */
+    public void print(double d) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(d));
+        } else {
+            write(String.valueOf(d));
+        }
+    }
+    
+    /**
+     * Print an array of characters.  The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param s The array of chars to be printed
+     *
+     * @throws NullPointerException If <code>s</code> is <code>null</code>
+     * @throws IOException
+     */
+    public void print(char[] s) throws IOException {
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            write(s);
+        }
+    }
+    
+    /**
+     * Print a string.  If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed.  Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param s The <code>String</code> to be printed
+     * @throws IOException
+     */
+    public void print(String s) throws IOException {
+        if (s == null) s = "null";
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            write(s);
+        }
+    }
+    
+    /**
+     * Print an object.  The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param obj The <code>Object</code> to be printed
+     * @throws IOException
+     */
+    public void print(Object obj) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(obj));
+        } else {
+            write(String.valueOf(obj));
+        }
+    }
+    
+    /**
+     * Terminate the current line by writing the line separator string.  The
+     * line separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     *
+     * @throws IOException
+     */
+    public void println() throws IOException {
+        newLine();
+    }
+    
+    /**
+     * Print a boolean value and then terminate the line.  This method behaves
+     * as though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(boolean x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a character and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(char x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an integer and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(int x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a long integer and then terminate the line.  This method behaves
+     * as though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(long x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a floating-point number and then terminate the line.  This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(float x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a double-precision floating-point number and then terminate the
+     * line.  This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(double x) throws IOException{
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an array of characters and then terminate the line.  This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and
+     * then <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(char x[]) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a String and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(String x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an Object and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(Object)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @throws IOException
+     */
+    public void println(Object x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Clear the contents of the buffer. If the buffer has been already
+     * been flushed then the clear operation shall throw an IOException
+     * to signal the fact that some data has already been irrevocably 
+     * written to the client response stream.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    public void clear() throws IOException {
+        if (writer != null) {
+            throw new IOException();
+        } else {
+            nextChar = 0;
+            if (LIMIT_BUFFER && (cb.length > Constants.DEFAULT_TAG_BUFFER_SIZE)) {
+                bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
+                cb = new char[bufferSize];
+            }
+        }
+    }
+    
+    /**
+     * Clears the current contents of the buffer. Unlike clear(), this
+     * mehtod will not throw an IOException if the buffer has already been
+     * flushed. It merely clears the current content of the buffer and
+     * returns.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    public void clearBuffer() throws IOException {
+        if (writer == null) {
+            this.clear();
+        }
+    }
+    
+    /**
+     * Close the stream, flushing it first.  Once a stream has been closed,
+     * further write() or flush() invocations will cause an IOException to be
+     * thrown.  Closing a previously-closed stream, however, has no effect.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    public void close() throws IOException {
+        if (writer != null) {
+            writer.close();
+        } else {
+            closed = true;
+        }
+    }
+    
+    /**
+     * This method returns the size of the buffer used by the JspWriter.
+     *
+     * @return the size of the buffer in bytes, or 0 is unbuffered.
+     */
+    public int getBufferSize() {
+        // According to the spec, the JspWriter returned by 
+        // JspContext.pushBody(java.io.Writer writer) must behave as
+        // though it were unbuffered. This means that its getBufferSize()
+        // must always return 0.
+        return (writer == null) ? bufferSize : 0;
+    }
+    
+    /**
+     * @return the number of bytes unused in the buffer
+     */
+    public int getRemaining() {
+        return (writer == null) ? bufferSize-nextChar : 0;
+    }
+    
+    /**
+     * Return the value of this BodyJspWriter as a Reader.
+     * Note: this is after evaluation!!  There are no scriptlets,
+     * etc in this stream.
+     *
+     * @return the value of this BodyJspWriter as a Reader
+     */
+    public Reader getReader() {
+        return (writer == null) ? new CharArrayReader (cb, 0, nextChar) : null;
+    }
+    
+    /**
+     * Return the value of the BodyJspWriter as a String.
+     * Note: this is after evaluation!!  There are no scriptlets,
+     * etc in this stream.
+     *
+     * @return the value of the BodyJspWriter as a String
+     */
+    public String getString() {
+        return (writer == null) ? new String(cb, 0, nextChar) : null;
+    }
+    
+    /**
+     * Write the contents of this BodyJspWriter into a Writer.
+     * Subclasses are likely to do interesting things with the
+     * implementation so some things are extra efficient.
+     *
+     * @param out The writer into which to place the contents of this body
+     * evaluation
+     */
+    public void writeOut(Writer out) throws IOException {
+        if (writer == null) {
+            out.write(cb, 0, nextChar);
+            // Flush not called as the writer passed could be a BodyContent and
+            // it doesn't allow to flush.
+        }
+    }
+    
+    /**
+     * Sets the writer to which all output is written.
+     */
+    void setWriter(Writer writer) {
+        this.writer = writer;
+        closed = false;
+        if (writer == null) {
+            clearBody();
+        }
+    }
+    
+    private void ensureOpen() throws IOException {
+        if (closed) throw new IOException("Stream closed");
+    }
+    
+    /**
+     * Reallocates buffer since the spec requires it to be unbounded.
+     */
+    private void reAllocBuff(int len) {
+        
+        if (bufferSize + len <= cb.length) {
+            bufferSize = cb.length;
+            return;
+        }
+        
+        if (len < cb.length) {
+            len = cb.length;
+        }
+        
+        bufferSize = cb.length + len;
+        char[] tmp = new char[bufferSize];
+        
+        System.arraycopy(cb, 0, tmp, 0, cb.length);
+        cb = tmp;
+        tmp = null;
+        
+    }
+    
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java
new file mode 100644
index 0000000..a1b6413
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java
@@ -0,0 +1,88 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.HttpJspPage;
+import javax.servlet.jsp.JspFactory;
+
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * This is the super class of all JSP-generated servlets.
+ *
+ * @author Anil K. Vijendran
+ */
+public abstract class HttpJspBase 
+    extends HttpServlet 
+    implements HttpJspPage 
+        
+    
+{
+    
+    protected HttpJspBase() {
+    }
+
+    public final void init(ServletConfig config) 
+	throws ServletException 
+    {
+        super.init(config);
+	jspInit();
+        _jspInit();
+    }
+    
+    public String getServletInfo() {
+	return Localizer.getMessage("jsp.engine.info");
+    }
+
+    public final void destroy() {
+	jspDestroy();
+	_jspDestroy();
+    }
+
+    /**
+     * Entry point into service.
+     */
+    public final void service(HttpServletRequest request, HttpServletResponse response) 
+	throws ServletException, IOException 
+    {
+        _jspService(request, response);
+    }
+    
+    public void jspInit() {
+    }
+
+    public void _jspInit() {
+    }
+
+    public void jspDestroy() {
+    }
+
+    protected void _jspDestroy() {
+    }
+
+    public abstract void _jspService(HttpServletRequest request, 
+				     HttpServletResponse response) 
+	throws ServletException, IOException;
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java
new file mode 100644
index 0000000..1ea649a
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspApplicationContextImpl.java
@@ -0,0 +1,138 @@
+/*
+ * 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.jasper.runtime;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.el.ArrayELResolver;
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContextEvent;
+import javax.el.ELContextListener;
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.ResourceBundleELResolver;
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.el.ImplicitObjectELResolver;
+import javax.servlet.jsp.el.ScopedAttributeELResolver;
+
+import org.apache.el.ExpressionFactoryImpl;
+import org.apache.jasper.el.ELContextImpl;
+
+/**
+ * Implementation of JspApplicationContext
+ * 
+ * @author Jacob Hookom
+ */
+public class JspApplicationContextImpl implements JspApplicationContext {
+
+	private final static String KEY = JspApplicationContextImpl.class.getName();
+
+	private final static ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
+
+	private final List<ELContextListener> contextListeners = new ArrayList<ELContextListener>();
+
+	private final List<ELResolver> resolvers = new ArrayList<ELResolver>();
+
+	private boolean instantiated = false;
+
+	private ELResolver resolver;
+
+	public JspApplicationContextImpl() {
+
+	}
+
+	public void addELContextListener(ELContextListener listener) {
+		if (listener == null) {
+			throw new IllegalArgumentException("ELConextListener was null");
+		}
+		this.contextListeners.add(listener);
+	}
+
+	public static JspApplicationContextImpl getInstance(ServletContext context) {
+		if (context == null) {
+			throw new IllegalArgumentException("ServletContext was null");
+		}
+		JspApplicationContextImpl impl = (JspApplicationContextImpl) context
+				.getAttribute(KEY);
+		if (impl == null) {
+			impl = new JspApplicationContextImpl();
+			context.setAttribute(KEY, impl);
+		}
+		return impl;
+	}
+
+	public ELContextImpl createELContext(JspContext context) {
+		if (context == null) {
+			throw new IllegalArgumentException("JspContext was null");
+		}
+
+		// create ELContext for JspContext
+		ELResolver r = this.createELResolver();
+		ELContextImpl ctx = new ELContextImpl(r);
+		ctx.putContext(JspContext.class, context);
+
+		// alert all ELContextListeners
+		ELContextEvent event = new ELContextEvent(ctx);
+		for (int i = 0; i < this.contextListeners.size(); i++) {
+			this.contextListeners.get(i).contextCreated(event);
+		}
+
+		return ctx;
+	}
+
+	private ELResolver createELResolver() {
+		this.instantiated = true;
+		if (this.resolver == null) {
+			CompositeELResolver r = new CompositeELResolver();
+			r.add(new ImplicitObjectELResolver());
+			for (Iterator itr = this.resolvers.iterator(); itr.hasNext();) {
+				r.add((ELResolver) itr.next());
+			}
+			r.add(new MapELResolver());
+			r.add(new ResourceBundleELResolver());
+			r.add(new ListELResolver());
+			r.add(new ArrayELResolver());	
+			r.add(new BeanELResolver());
+			r.add(new ScopedAttributeELResolver());
+			this.resolver = r;
+		}
+		return this.resolver;
+	}
+
+	public void addELResolver(ELResolver resolver) throws IllegalStateException {
+		if (resolver == null) {
+			throw new IllegalArgumentException("ELResolver was null");
+		}
+		if (this.instantiated) {
+			throw new IllegalStateException(
+					"cannot call addELResolver after the first request has been made");
+		}
+		this.resolvers.add(resolver);
+	}
+
+	public ExpressionFactory getExpressionFactory() {
+		return expressionFactory;
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java
new file mode 100644
index 0000000..05f6638
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java
@@ -0,0 +1,462 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
+import javax.servlet.jsp.tagext.BodyContent;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.util.Enumerator;
+
+/**
+ * Implementation of a JSP Context Wrapper.
+ * 
+ * The JSP Context Wrapper is a JspContext created and maintained by a tag
+ * handler implementation. It wraps the Invoking JSP Context, that is, the
+ * JspContext instance passed to the tag handler by the invoking page via
+ * setJspContext().
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Jacob Hookom
+ */
+public class JspContextWrapper extends PageContext implements VariableResolver {
+
+	// Invoking JSP context
+	private PageContext invokingJspCtxt;
+
+	private transient HashMap<String, Object> pageAttributes;
+
+	// ArrayList of NESTED scripting variables
+	private ArrayList nestedVars;
+
+	// ArrayList of AT_BEGIN scripting variables
+	private ArrayList atBeginVars;
+
+	// ArrayList of AT_END scripting variables
+	private ArrayList atEndVars;
+
+	private Map aliases;
+
+	private HashMap<String, Object> originalNestedVars;
+
+	public JspContextWrapper(JspContext jspContext, ArrayList nestedVars,
+			ArrayList atBeginVars, ArrayList atEndVars, Map aliases) {
+		this.invokingJspCtxt = (PageContext) jspContext;
+		this.nestedVars = nestedVars;
+		this.atBeginVars = atBeginVars;
+		this.atEndVars = atEndVars;
+		this.pageAttributes = new HashMap<String, Object>(16);
+		this.aliases = aliases;
+
+		if (nestedVars != null) {
+			this.originalNestedVars = new HashMap<String, Object>(nestedVars.size());
+		}
+		syncBeginTagFile();
+	}
+
+	public void initialize(Servlet servlet, ServletRequest request,
+			ServletResponse response, String errorPageURL,
+			boolean needsSession, int bufferSize, boolean autoFlush)
+			throws IOException, IllegalStateException, IllegalArgumentException {
+	}
+
+	public Object getAttribute(String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		return pageAttributes.get(name);
+	}
+
+	public Object getAttribute(String name, int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (scope == PAGE_SCOPE) {
+			return pageAttributes.get(name);
+		}
+
+		return invokingJspCtxt.getAttribute(name, scope);
+	}
+
+	public void setAttribute(String name, Object value) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (value != null) {
+			pageAttributes.put(name, value);
+		} else {
+			removeAttribute(name, PAGE_SCOPE);
+		}
+	}
+
+	public void setAttribute(String name, Object value, int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (scope == PAGE_SCOPE) {
+			if (value != null) {
+				pageAttributes.put(name, value);
+			} else {
+				removeAttribute(name, PAGE_SCOPE);
+			}
+		} else {
+			invokingJspCtxt.setAttribute(name, value, scope);
+		}
+	}
+
+	public Object findAttribute(String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		Object o = pageAttributes.get(name);
+		if (o == null) {
+			o = invokingJspCtxt.getAttribute(name, REQUEST_SCOPE);
+			if (o == null) {
+				if (getSession() != null) {
+					o = invokingJspCtxt.getAttribute(name, SESSION_SCOPE);
+				}
+				if (o == null) {
+					o = invokingJspCtxt.getAttribute(name, APPLICATION_SCOPE);
+				}
+			}
+		}
+
+		return o;
+	}
+
+	public void removeAttribute(String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		pageAttributes.remove(name);
+		invokingJspCtxt.removeAttribute(name, REQUEST_SCOPE);
+		if (getSession() != null) {
+			invokingJspCtxt.removeAttribute(name, SESSION_SCOPE);
+		}
+		invokingJspCtxt.removeAttribute(name, APPLICATION_SCOPE);
+	}
+
+	public void removeAttribute(String name, int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (scope == PAGE_SCOPE) {
+			pageAttributes.remove(name);
+		} else {
+			invokingJspCtxt.removeAttribute(name, scope);
+		}
+	}
+
+	public int getAttributesScope(String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (pageAttributes.get(name) != null) {
+			return PAGE_SCOPE;
+		} else {
+			return invokingJspCtxt.getAttributesScope(name);
+		}
+	}
+
+	public Enumeration<String> getAttributeNamesInScope(int scope) {
+		if (scope == PAGE_SCOPE) {
+			return new Enumerator(pageAttributes.keySet().iterator());
+		}
+
+		return invokingJspCtxt.getAttributeNamesInScope(scope);
+	}
+
+	public void release() {
+		invokingJspCtxt.release();
+	}
+
+	public JspWriter getOut() {
+		return invokingJspCtxt.getOut();
+	}
+
+	public HttpSession getSession() {
+		return invokingJspCtxt.getSession();
+	}
+
+	public Object getPage() {
+		return invokingJspCtxt.getPage();
+	}
+
+	public ServletRequest getRequest() {
+		return invokingJspCtxt.getRequest();
+	}
+
+	public ServletResponse getResponse() {
+		return invokingJspCtxt.getResponse();
+	}
+
+	public Exception getException() {
+		return invokingJspCtxt.getException();
+	}
+
+	public ServletConfig getServletConfig() {
+		return invokingJspCtxt.getServletConfig();
+	}
+
+	public ServletContext getServletContext() {
+		return invokingJspCtxt.getServletContext();
+	}
+
+	public void forward(String relativeUrlPath) throws ServletException,
+			IOException {
+		invokingJspCtxt.forward(relativeUrlPath);
+	}
+
+	public void include(String relativeUrlPath) throws ServletException,
+			IOException {
+		invokingJspCtxt.include(relativeUrlPath);
+	}
+
+	public void include(String relativeUrlPath, boolean flush)
+			throws ServletException, IOException {
+		invokingJspCtxt.include(relativeUrlPath, false);
+	}
+
+	public VariableResolver getVariableResolver() {
+		return this;
+	}
+
+	public BodyContent pushBody() {
+		return invokingJspCtxt.pushBody();
+	}
+
+	public JspWriter pushBody(Writer writer) {
+		return invokingJspCtxt.pushBody(writer);
+	}
+
+	public JspWriter popBody() {
+		return invokingJspCtxt.popBody();
+	}
+
+	public ExpressionEvaluator getExpressionEvaluator() {
+		return invokingJspCtxt.getExpressionEvaluator();
+	}
+
+	public void handlePageException(Exception ex) throws IOException,
+			ServletException {
+		// Should never be called since handleException() called with a
+		// Throwable in the generated servlet.
+		handlePageException((Throwable) ex);
+	}
+
+	public void handlePageException(Throwable t) throws IOException,
+			ServletException {
+		invokingJspCtxt.handlePageException(t);
+	}
+
+	/**
+	 * VariableResolver interface
+	 */
+	public Object resolveVariable(String pName) throws ELException {
+		ELContext ctx = this.getELContext();
+		return ctx.getELResolver().getValue(ctx, null, pName);
+	}
+
+	/**
+	 * Synchronize variables at begin of tag file
+	 */
+	public void syncBeginTagFile() {
+		saveNestedVariables();
+	}
+
+	/**
+	 * Synchronize variables before fragment invokation
+	 */
+	public void syncBeforeInvoke() {
+		copyTagToPageScope(VariableInfo.NESTED);
+		copyTagToPageScope(VariableInfo.AT_BEGIN);
+	}
+
+	/**
+	 * Synchronize variables at end of tag file
+	 */
+	public void syncEndTagFile() {
+		copyTagToPageScope(VariableInfo.AT_BEGIN);
+		copyTagToPageScope(VariableInfo.AT_END);
+		restoreNestedVariables();
+	}
+
+	/**
+	 * Copies the variables of the given scope from the virtual page scope of
+	 * this JSP context wrapper to the page scope of the invoking JSP context.
+	 * 
+	 * @param scope
+	 *            variable scope (one of NESTED, AT_BEGIN, or AT_END)
+	 */
+	private void copyTagToPageScope(int scope) {
+		Iterator iter = null;
+
+		switch (scope) {
+		case VariableInfo.NESTED:
+			if (nestedVars != null) {
+				iter = nestedVars.iterator();
+			}
+			break;
+		case VariableInfo.AT_BEGIN:
+			if (atBeginVars != null) {
+				iter = atBeginVars.iterator();
+			}
+			break;
+		case VariableInfo.AT_END:
+			if (atEndVars != null) {
+				iter = atEndVars.iterator();
+			}
+			break;
+		}
+
+		while ((iter != null) && iter.hasNext()) {
+			String varName = (String) iter.next();
+			Object obj = getAttribute(varName);
+			varName = findAlias(varName);
+			if (obj != null) {
+				invokingJspCtxt.setAttribute(varName, obj);
+			} else {
+				invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
+			}
+		}
+	}
+
+	/**
+	 * Saves the values of any NESTED variables that are present in the invoking
+	 * JSP context, so they can later be restored.
+	 */
+	private void saveNestedVariables() {
+		if (nestedVars != null) {
+			Iterator iter = nestedVars.iterator();
+			while (iter.hasNext()) {
+				String varName = (String) iter.next();
+				varName = findAlias(varName);
+				Object obj = invokingJspCtxt.getAttribute(varName);
+				if (obj != null) {
+					originalNestedVars.put(varName, obj);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Restores the values of any NESTED variables in the invoking JSP context.
+	 */
+	private void restoreNestedVariables() {
+		if (nestedVars != null) {
+			Iterator iter = nestedVars.iterator();
+			while (iter.hasNext()) {
+				String varName = (String) iter.next();
+				varName = findAlias(varName);
+				Object obj = originalNestedVars.get(varName);
+				if (obj != null) {
+					invokingJspCtxt.setAttribute(varName, obj);
+				} else {
+					invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Checks to see if the given variable name is used as an alias, and if so,
+	 * returns the variable name for which it is used as an alias.
+	 * 
+	 * @param varName
+	 *            The variable name to check
+	 * @return The variable name for which varName is used as an alias, or
+	 *         varName if it is not being used as an alias
+	 */
+	private String findAlias(String varName) {
+
+		if (aliases == null)
+			return varName;
+
+		String alias = (String) aliases.get(varName);
+		if (alias == null) {
+			return varName;
+		}
+		return alias;
+	}
+
+	//private ELContextImpl elContext;
+
+	public ELContext getELContext() {
+        // instead decorate!!!
+        
+        return this.invokingJspCtxt.getELContext();
+        
+        /*
+		if (this.elContext != null) {
+			JspFactory jspFact = JspFactory.getDefaultFactory();
+			ServletContext servletContext = this.getServletContext();
+			JspApplicationContextImpl jspCtx = (JspApplicationContextImpl) jspFact
+					.getJspApplicationContext(servletContext);
+			this.elContext = jspCtx.createELContext(this);
+		}
+		return this.elContext;
+        */
+	}
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java
new file mode 100644
index 0000000..f06a49d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java
@@ -0,0 +1,202 @@
+/*
+ * 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.jasper.runtime;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.jsp.JspEngineInfo;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.jasper.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Implementation of JspFactory.
+ *
+ * @author Anil K. Vijendran
+ */
+public class JspFactoryImpl extends JspFactory {
+
+    // Logger
+    private Log log = LogFactory.getLog(JspFactoryImpl.class);
+
+    private static final String SPEC_VERSION = "2.1";
+    private static final boolean USE_POOL = 
+        Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.USE_POOL", "true")).booleanValue();
+    private static final int POOL_SIZE = 
+        Integer.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.POOL_SIZE", "8")).intValue();
+
+    private ThreadLocal<PageContextPool> localPool = new ThreadLocal<PageContextPool>();
+
+    public PageContext getPageContext(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL, boolean needsSession,
+            int bufferSize, boolean autoflush) {
+
+        if( Constants.IS_SECURITY_ENABLED ) {
+            PrivilegedGetPageContext dp = new PrivilegedGetPageContext(
+                    (JspFactoryImpl)this, servlet, request, response, errorPageURL,
+                    needsSession, bufferSize, autoflush);
+            return (PageContext)AccessController.doPrivileged(dp);
+        } else {
+            return internalGetPageContext(servlet, request, response,
+                    errorPageURL, needsSession,
+                    bufferSize, autoflush);
+        }
+    }
+
+    public void releasePageContext(PageContext pc) {
+        if( pc == null )
+            return;
+        if( Constants.IS_SECURITY_ENABLED ) {
+            PrivilegedReleasePageContext dp = new PrivilegedReleasePageContext(
+                    (JspFactoryImpl)this,pc);
+            AccessController.doPrivileged(dp);
+        } else {
+            internalReleasePageContext(pc);
+        }
+    }
+
+    public JspEngineInfo getEngineInfo() {
+        return new JspEngineInfo() {
+            public String getSpecificationVersion() {
+                return SPEC_VERSION;
+            }
+        };
+    }
+
+    private PageContext internalGetPageContext(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL, boolean needsSession,
+            int bufferSize, boolean autoflush) {
+        try {
+            PageContext pc;
+            if (USE_POOL) {
+                PageContextPool pool = localPool.get();
+                if (pool == null) {
+                    pool = new PageContextPool();
+                    localPool.set(pool);
+                }
+                pc = pool.get();
+                if (pc == null) {
+                    pc = new PageContextImpl();
+                }
+            } else {
+                pc = new PageContextImpl();
+            }
+            pc.initialize(servlet, request, response, errorPageURL, 
+                    needsSession, bufferSize, autoflush);
+            return pc;
+        } catch (Throwable ex) {
+            /* FIXME: need to do something reasonable here!! */
+            log.fatal("Exception initializing page context", ex);
+            return null;
+        }
+    }
+
+    private void internalReleasePageContext(PageContext pc) {
+        pc.release();
+        if (USE_POOL && (pc instanceof PageContextImpl)) {
+            localPool.get().put(pc);
+        }
+    }
+
+    private class PrivilegedGetPageContext implements PrivilegedAction {
+
+        private JspFactoryImpl factory;
+        private Servlet servlet;
+        private ServletRequest request;
+        private ServletResponse response;
+        private String errorPageURL;
+        private boolean needsSession;
+        private int bufferSize;
+        private boolean autoflush;
+
+        PrivilegedGetPageContext(JspFactoryImpl factory, Servlet servlet,
+                ServletRequest request, ServletResponse response, String errorPageURL,
+                boolean needsSession, int bufferSize, boolean autoflush) {
+            this.factory = factory;
+            this.servlet = servlet;
+            this.request = request;
+            this.response = response;
+            this.errorPageURL = errorPageURL;
+            this.needsSession = needsSession;
+            this.bufferSize = bufferSize;
+            this.autoflush = autoflush;
+        }
+
+        public Object run() {
+            return factory.internalGetPageContext(servlet, request, response,
+                    errorPageURL, needsSession, bufferSize, autoflush);
+        }
+    }
+
+    private class PrivilegedReleasePageContext implements PrivilegedAction {
+
+        private JspFactoryImpl factory;
+        private PageContext pageContext;
+
+        PrivilegedReleasePageContext(JspFactoryImpl factory,
+                PageContext pageContext) {
+            this.factory = factory;
+            this.pageContext = pageContext;
+        }
+
+        public Object run() {
+            factory.internalReleasePageContext(pageContext);
+            return null;
+        }
+    }
+
+    protected final class PageContextPool  {
+
+        private PageContext[] pool;
+
+        private int current = -1;
+
+        public PageContextPool() {
+            this.pool = new PageContext[POOL_SIZE];
+        }
+
+        public void put(PageContext o) {
+            if (current < (POOL_SIZE - 1)) {
+                current++;
+                pool[current] = o;
+            }
+        }
+
+        public PageContext get() {
+            PageContext item = null;
+            if (current >= 0) {
+                item = pool[current];
+                current--;
+            }
+            return item;
+        }
+
+    }
+
+    public JspApplicationContext getJspApplicationContext(ServletContext context) {
+        return JspApplicationContextImpl.getInstance(context);
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java
new file mode 100644
index 0000000..38d26d3
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.jasper.runtime;
+
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.JspFragment;
+import javax.servlet.jsp.tagext.JspTag;
+
+/**
+ * Helper class from which all Jsp Fragment helper classes extend.
+ * This class allows for the emulation of numerous fragments within
+ * a single class, which in turn reduces the load on the class loader
+ * since there are potentially many JspFragments in a single page.
+ * <p>
+ * The class also provides various utility methods for JspFragment
+ * implementations.
+ *
+ * @author Mark Roth
+ */
+public abstract class JspFragmentHelper 
+    extends JspFragment 
+{
+    
+    protected int discriminator;
+    protected JspContext jspContext;
+    protected PageContext _jspx_page_context;
+    protected JspTag parentTag;
+
+    public JspFragmentHelper( int discriminator, JspContext jspContext, 
+        JspTag parentTag ) 
+    {
+        this.discriminator = discriminator;
+        this.jspContext = jspContext;
+        this._jspx_page_context = null;
+        if( jspContext instanceof PageContext ) {
+            _jspx_page_context = (PageContext)jspContext;
+        }
+        this.parentTag = parentTag;
+    }
+    
+    public JspContext getJspContext() {
+        return this.jspContext;
+    }
+    
+    public JspTag getParentTag() {
+        return this.parentTag;
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java
new file mode 100644
index 0000000..02d21dd
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java
@@ -0,0 +1,1047 @@
+/*
+ * 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.jasper.runtime;
+
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Enumeration;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * Bunch of util methods that are used by code generated for useBean,
+ * getProperty and setProperty.  
+ *
+ * The __begin, __end stuff is there so that the JSP engine can
+ * actually parse this file and inline them if people don't want
+ * runtime dependencies on this class. However, I'm not sure if that
+ * works so well right now. It got forgotten at some point. -akv
+ *
+ * @author Mandar Raje
+ * @author Shawn Bayern
+ */
+public class JspRuntimeLibrary {
+    
+    private static final String SERVLET_EXCEPTION
+	= "javax.servlet.error.exception";
+    private static final String JSP_EXCEPTION
+	= "javax.servlet.jsp.jspException";
+
+    protected static class PrivilegedIntrospectHelper
+	implements PrivilegedExceptionAction {
+
+	private Object bean;
+	private String prop;
+	private String value;
+	private ServletRequest request;
+	private String param;
+	private boolean ignoreMethodNF;
+
+        PrivilegedIntrospectHelper(Object bean, String prop,
+                                   String value, ServletRequest request,
+                                   String param, boolean ignoreMethodNF)
+        {
+	    this.bean = bean;
+	    this.prop = prop;
+	    this.value = value;
+            this.request = request;
+	    this.param = param;
+	    this.ignoreMethodNF = ignoreMethodNF;
+        }
+         
+        public Object run() throws JasperException {
+	    internalIntrospecthelper(
+                bean,prop,value,request,param,ignoreMethodNF);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value of the javax.servlet.error.exception request
+     * attribute value, if present, otherwise the value of the
+     * javax.servlet.jsp.jspException request attribute value.
+     *
+     * This method is called at the beginning of the generated servlet code
+     * for a JSP error page, when the "exception" implicit scripting language
+     * variable is initialized.
+     */
+    public static Throwable getThrowable(ServletRequest request) {
+	Throwable error = (Throwable) request.getAttribute(SERVLET_EXCEPTION);
+	if (error == null) {
+	    error = (Throwable) request.getAttribute(JSP_EXCEPTION);
+	    if (error != null) {
+		/*
+		 * The only place that sets JSP_EXCEPTION is
+		 * PageContextImpl.handlePageException(). It really should set
+		 * SERVLET_EXCEPTION, but that would interfere with the 
+		 * ErrorReportValve. Therefore, if JSP_EXCEPTION is set, we
+		 * need to set SERVLET_EXCEPTION.
+		 */
+		request.setAttribute(SERVLET_EXCEPTION, error);
+	    }
+	}
+
+	return error;
+    }
+
+    public static boolean coerceToBoolean(String s) {
+	if (s == null || s.length() == 0)
+	    return false;
+	else
+	    return Boolean.valueOf(s).booleanValue();
+    }
+
+    public static byte coerceToByte(String s) {
+	if (s == null || s.length() == 0)
+	    return (byte) 0;
+	else
+	    return Byte.valueOf(s).byteValue();
+    }
+
+    public static char coerceToChar(String s) {
+	if (s == null || s.length() == 0) {
+	    return (char) 0;
+	} else {
+	    // this trick avoids escaping issues
+	    return (char)(int) s.charAt(0);
+	}
+    }
+
+    public static double coerceToDouble(String s) {
+	if (s == null || s.length() == 0)
+	    return (double) 0;
+	else
+	    return Double.valueOf(s).doubleValue();
+    }
+
+    public static float coerceToFloat(String s) {
+	if (s == null || s.length() == 0)
+	    return (float) 0;
+	else
+	    return Float.valueOf(s).floatValue();
+    }
+
+    public static int coerceToInt(String s) {
+	if (s == null || s.length() == 0)
+	    return 0;
+	else
+	    return Integer.valueOf(s).intValue();
+    }
+
+    public static short coerceToShort(String s) {
+	if (s == null || s.length() == 0)
+	    return (short) 0;
+	else
+	    return Short.valueOf(s).shortValue();
+    }
+
+    public static long coerceToLong(String s) {
+	if (s == null || s.length() == 0)
+	    return (long) 0;
+	else
+	    return Long.valueOf(s).longValue();
+    }
+
+    public static Object coerce(String s, Class target) {
+
+	boolean isNullOrEmpty = (s == null || s.length() == 0);
+
+	if (target == Boolean.class) {
+	    if (isNullOrEmpty) {
+		s = "false";
+	    }
+	    return new Boolean(s);
+	} else if (target == Byte.class) {
+	    if (isNullOrEmpty)
+		return new Byte((byte) 0);
+	    else
+		return new Byte(s);
+	} else if (target == Character.class) {
+	    if (isNullOrEmpty)
+		return new Character((char) 0);
+	    else 
+		return new Character(s.charAt(0));
+	} else if (target == Double.class) {
+	    if (isNullOrEmpty)
+		return new Double(0);
+	    else
+		return new Double(s);
+	} else if (target == Float.class) {
+	    if (isNullOrEmpty)
+		return new Float(0);
+	    else
+		return new Float(s);
+	} else if (target == Integer.class) {
+	    if (isNullOrEmpty)
+		return new Integer(0);
+	    else
+		return new Integer(s);
+	} else if (target == Short.class) {
+	    if (isNullOrEmpty)
+		return new Short((short) 0);
+	    else
+		return new Short(s);
+	} else if (target == Long.class) {
+	    if (isNullOrEmpty)
+		return new Long(0);
+	    else
+		return new Long(s);
+	} else {
+	    return null;
+	}
+    }
+
+   // __begin convertMethod
+    public static Object convert(String propertyName, String s, Class t,
+				 Class propertyEditorClass) 
+       throws JasperException 
+    {
+        try {
+            if (s == null) {
+                if (t.equals(Boolean.class) || t.equals(Boolean.TYPE))
+                    s = "false";
+                else
+                    return null;
+            }
+	    if (propertyEditorClass != null) {
+		return getValueFromBeanInfoPropertyEditor(
+				    t, propertyName, s, propertyEditorClass);
+	    } else if ( t.equals(Boolean.class) || t.equals(Boolean.TYPE) ) {
+                if (s.equalsIgnoreCase("on") || s.equalsIgnoreCase("true"))
+                    s = "true";
+                else
+                    s = "false";
+                return new Boolean(s);
+            } else if ( t.equals(Byte.class) || t.equals(Byte.TYPE) ) {
+                return new Byte(s);
+            } else if (t.equals(Character.class) || t.equals(Character.TYPE)) {
+                return s.length() > 0 ? new Character(s.charAt(0)) : null;
+            } else if ( t.equals(Short.class) || t.equals(Short.TYPE) ) {
+                return new Short(s);
+            } else if ( t.equals(Integer.class) || t.equals(Integer.TYPE) ) {
+                return new Integer(s);
+            } else if ( t.equals(Float.class) || t.equals(Float.TYPE) ) {
+                return new Float(s);
+            } else if ( t.equals(Long.class) || t.equals(Long.TYPE) ) {
+                return new Long(s);
+            } else if ( t.equals(Double.class) || t.equals(Double.TYPE) ) {
+                return new Double(s);
+            } else if ( t.equals(String.class) ) {
+                return s;
+            } else if ( t.equals(java.io.File.class) ) {
+                return new java.io.File(s);
+            } else if (t.getName().equals("java.lang.Object")) {
+                return new Object[] {s};
+	    } else {
+		return getValueFromPropertyEditorManager(
+                                            t, propertyName, s);
+            }
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+    // __end convertMethod
+
+    // __begin introspectMethod
+    public static void introspect(Object bean, ServletRequest request)
+                                  throws JasperException
+    {
+	Enumeration e = request.getParameterNames();
+	while ( e.hasMoreElements() ) {
+	    String name  = (String) e.nextElement();
+	    String value = request.getParameter(name);
+	    introspecthelper(bean, name, value, request, name, true);
+	}
+    }
+    // __end introspectMethod
+    
+    // __begin introspecthelperMethod
+    public static void introspecthelper(Object bean, String prop,
+                                        String value, ServletRequest request,
+                                        String param, boolean ignoreMethodNF)
+                                        throws JasperException
+    {
+        if( Constants.IS_SECURITY_ENABLED ) {
+            try {
+                PrivilegedIntrospectHelper dp =
+		    new PrivilegedIntrospectHelper(
+			bean,prop,value,request,param,ignoreMethodNF);
+                AccessController.doPrivileged(dp);
+            } catch( PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                throw (JasperException)e;
+            }
+        } else {
+            internalIntrospecthelper(
+		bean,prop,value,request,param,ignoreMethodNF);
+        }
+    }
+
+    private static void internalIntrospecthelper(Object bean, String prop,
+					String value, ServletRequest request,
+					String param, boolean ignoreMethodNF) 
+					throws JasperException
+    {
+        Method method = null;
+        Class type = null;
+        Class propertyEditorClass = null;
+	try {
+	    java.beans.BeanInfo info
+		= java.beans.Introspector.getBeanInfo(bean.getClass());
+	    if ( info != null ) {
+		java.beans.PropertyDescriptor pd[]
+		    = info.getPropertyDescriptors();
+		for (int i = 0 ; i < pd.length ; i++) {
+		    if ( pd[i].getName().equals(prop) ) {
+			method = pd[i].getWriteMethod();
+			type   = pd[i].getPropertyType();
+			propertyEditorClass = pd[i].getPropertyEditorClass();
+			break;
+		    }
+		}
+	    }
+	    if ( method != null ) {
+		if (type.isArray()) {
+                    if (request == null) {
+			throw new JasperException(
+		            Localizer.getMessage("jsp.error.beans.setproperty.noindexset"));
+                    }
+		    Class t = type.getComponentType();
+		    String[] values = request.getParameterValues(param);
+		    //XXX Please check.
+		    if(values == null) return;
+		    if(t.equals(String.class)) {
+			method.invoke(bean, new Object[] { values });
+		    } else {
+			Object tmpval = null;
+			createTypedArray (prop, bean, method, values, t,
+					  propertyEditorClass); 
+		    }
+		} else {
+		    if(value == null || (param != null && value.equals(""))) return;
+		    Object oval = convert(prop, value, type, propertyEditorClass);
+		    if ( oval != null )
+			method.invoke(bean, new Object[] { oval });
+		}
+	    }
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}
+        if (!ignoreMethodNF && (method == null)) {
+            if (type == null) {
+		throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.noproperty",
+					 prop,
+					 bean.getClass().getName()));
+            } else {
+		throw new JasperException(
+	            Localizer.getMessage("jsp.error.beans.nomethod.setproperty",
+					 prop,
+					 type.getName(),
+					 bean.getClass().getName()));
+            }
+        }
+    }
+    // __end introspecthelperMethod
+    
+    //-------------------------------------------------------------------
+    // functions to convert builtin Java data types to string.
+    //-------------------------------------------------------------------
+    // __begin toStringMethod
+    public static String toString(Object o) {
+        return String.valueOf(o);
+    }
+
+    public static String toString(byte b) {
+        return new Byte(b).toString();
+    }
+
+    public static String toString(boolean b) {
+        return new Boolean(b).toString();
+    }
+
+    public static String toString(short s) {
+        return new Short(s).toString();
+    }
+
+    public static String toString(int i) {
+        return new Integer(i).toString();
+    }
+
+    public static String toString(float f) {
+        return new Float(f).toString();
+    }
+
+    public static String toString(long l) {
+        return new Long(l).toString();
+    }
+
+    public static String toString(double d) {
+        return new Double(d).toString();
+    }
+
+    public static String toString(char c) {
+        return new Character(c).toString();
+    }
+    // __end toStringMethod
+
+
+    /**
+     * Create a typed array.
+     * This is a special case where params are passed through
+     * the request and the property is indexed.
+     */
+    public static void createTypedArray(String propertyName,
+					Object bean,
+					Method method,
+					String[] values,
+					Class t,
+					Class propertyEditorClass)
+	        throws JasperException {
+
+	try {
+	    if (propertyEditorClass != null) {
+		Object[] tmpval = new Integer[values.length];
+		for (int i=0; i<values.length; i++) {
+		    tmpval[i] = getValueFromBeanInfoPropertyEditor(
+                            t, propertyName, values[i], propertyEditorClass);
+		}
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Integer.class)) {
+		Integer []tmpval = new Integer[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] =  new Integer (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Byte.class)) {
+		Byte[] tmpval = new Byte[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Byte (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Boolean.class)) {
+		Boolean[] tmpval = new Boolean[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Boolean (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Short.class)) {
+		Short[] tmpval = new Short[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Short (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Long.class)) {
+		Long[] tmpval = new Long[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Long (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Double.class)) {
+		Double[] tmpval = new Double[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Double (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Float.class)) {
+		Float[] tmpval = new Float[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Float (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(Character.class)) {
+		Character[] tmpval = new Character[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = new Character(values[i].charAt(0));
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(int.class)) {
+		int []tmpval = new int[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Integer.parseInt (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(byte.class)) {
+		byte[] tmpval = new byte[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Byte.parseByte (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(boolean.class)) {
+		boolean[] tmpval = new boolean[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = (Boolean.valueOf(values[i])).booleanValue();
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(short.class)) {
+		short[] tmpval = new short[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Short.parseShort (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(long.class)) {
+		long[] tmpval = new long[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Long.parseLong (values[i]);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(double.class)) {
+		double[] tmpval = new double[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Double.valueOf(values[i]).doubleValue();
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(float.class)) {
+		float[] tmpval = new float[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = Float.valueOf(values[i]).floatValue();
+		method.invoke (bean, new Object[] {tmpval});
+	    } else if (t.equals(char.class)) {
+		char[] tmpval = new char[values.length];
+		for (int i = 0 ; i < values.length; i++)
+		    tmpval[i] = values[i].charAt(0);
+		method.invoke (bean, new Object[] {tmpval});
+	    } else {
+		Object[] tmpval = new Integer[values.length];
+		for (int i=0; i<values.length; i++) {
+		    tmpval[i] =  
+			getValueFromPropertyEditorManager(
+                                            t, propertyName, values[i]);
+		}
+		method.invoke (bean, new Object[] {tmpval});
+	    }
+	} catch (Exception ex) {
+            throw new JasperException ("error in invoking method", ex);
+	}
+    }
+
+    /**
+     * Escape special shell characters.
+     * @param unescString The string to shell-escape
+     * @return The escaped shell string.
+     */
+
+    public static String escapeQueryString(String unescString) {
+    if ( unescString == null )
+        return null;
+   
+    String escString    = "";
+    String shellSpChars = "&;`'\"|*?~<>^()[]{}$\\\n";
+   
+    for(int index=0; index<unescString.length(); index++) {
+        char nextChar = unescString.charAt(index);
+
+        if( shellSpChars.indexOf(nextChar) != -1 )
+        escString += "\\";
+
+        escString += nextChar;
+    }
+    return escString;
+    }
+
+    /**
+     * Decode an URL formatted string.
+     * @param encoded The string to decode.
+     * @return The decoded string.
+     */
+
+    public static String decode(String encoded) {
+        // speedily leave if we're not needed
+    if (encoded == null) return null;
+        if (encoded.indexOf('%') == -1 && encoded.indexOf('+') == -1)
+        return encoded;
+
+    //allocate the buffer - use byte[] to avoid calls to new.
+        byte holdbuffer[] = new byte[encoded.length()];
+
+        char holdchar;
+        int bufcount = 0;
+
+        for (int count = 0; count < encoded.length(); count++) {
+        char cur = encoded.charAt(count);
+            if (cur == '%') {
+            holdbuffer[bufcount++] =
+          (byte)Integer.parseInt(encoded.substring(count+1,count+3),16);
+                if (count + 2 >= encoded.length())
+                    count = encoded.length();
+                else
+                    count += 2;
+            } else if (cur == '+') {
+        holdbuffer[bufcount++] = (byte) ' ';
+        } else {
+            holdbuffer[bufcount++] = (byte) cur;
+            }
+        }
+	// REVISIT -- remedy for Deprecated warning.
+    //return new String(holdbuffer,0,0,bufcount);
+    return new String(holdbuffer,0,bufcount);
+    }
+
+    // __begin lookupReadMethodMethod
+    public static Object handleGetProperty(Object o, String prop)
+    throws JasperException {
+        if (o == null) {
+	    throw new JasperException(
+	            Localizer.getMessage("jsp.error.beans.nullbean"));
+        }
+	Object value = null;
+        try {
+            Method method = getReadMethod(o.getClass(), prop);
+	    value = method.invoke(o, (Object[]) null);
+        } catch (Exception ex) {
+	    throw new JasperException (ex);
+        }
+        return value;
+    }
+    // __end lookupReadMethodMethod
+
+    // handles <jsp:setProperty> with EL expression for 'value' attribute
+/** Use proprietaryEvaluate
+    public static void handleSetPropertyExpression(Object bean,
+        String prop, String expression, PageContext pageContext,
+        VariableResolver variableResolver, FunctionMapper functionMapper )
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { 
+		pageContext.getExpressionEvaluator().evaluate(
+		    expression,
+		    method.getParameterTypes()[0],
+                    variableResolver,
+                    functionMapper,
+                    null )
+	    });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}
+    }
+**/
+    public static void handleSetPropertyExpression(Object bean,
+        String prop, String expression, PageContext pageContext,
+	ProtectedFunctionMapper functionMapper )
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] {
+                PageContextImpl.proprietaryEvaluate(
+                    expression,
+                    method.getParameterTypes()[0],
+		    pageContext,
+                    functionMapper,
+                    false )
+            });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+
+    public static void handleSetProperty(Object bean, String prop,
+					 Object value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { value });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 int value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Integer(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 short value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Short(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 long value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Long(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    } 
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 double value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Double(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 float value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Float(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 char value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Character(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+
+    public static void handleSetProperty(Object bean, String prop,
+					 byte value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Byte(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+					 boolean value)
+	throws JasperException
+    {
+	try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+	    method.invoke(bean, new Object[] { new Boolean(value) });
+	} catch (Exception ex) {
+	    throw new JasperException(ex);
+	}	
+    }
+    
+    public static Method getWriteMethod(Class beanClass, String prop)
+    throws JasperException {
+	Method method = null;	
+        Class type = null;
+	try {
+	    java.beans.BeanInfo info
+                = java.beans.Introspector.getBeanInfo(beanClass);
+	    if ( info != null ) {
+		java.beans.PropertyDescriptor pd[]
+		    = info.getPropertyDescriptors();
+		for (int i = 0 ; i < pd.length ; i++) {
+		    if ( pd[i].getName().equals(prop) ) {
+			method = pd[i].getWriteMethod();
+			type   = pd[i].getPropertyType();
+			break;
+		    }
+		}
+            } else {        
+                // just in case introspection silently fails.
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nobeaninfo",
+					 beanClass.getName()));
+            }
+        } catch (Exception ex) {
+            throw new JasperException (ex);
+        }
+        if (method == null) {
+            if (type == null) {
+		throw new JasperException(
+                        Localizer.getMessage("jsp.error.beans.noproperty",
+					     prop,
+					     beanClass.getName()));
+            } else {
+		throw new JasperException(
+		    Localizer.getMessage("jsp.error.beans.nomethod.setproperty",
+					 prop,
+					 type.getName(),
+					 beanClass.getName()));
+            }
+        }
+        return method;
+    }
+
+    public static Method getReadMethod(Class beanClass, String prop)
+	    throws JasperException {
+
+        Method method = null;        
+        Class type = null;
+        try {
+            java.beans.BeanInfo info
+                = java.beans.Introspector.getBeanInfo(beanClass);
+            if ( info != null ) {
+                java.beans.PropertyDescriptor pd[]
+                    = info.getPropertyDescriptors();
+                for (int i = 0 ; i < pd.length ; i++) {
+                    if ( pd[i].getName().equals(prop) ) {
+                        method = pd[i].getReadMethod();
+                        type   = pd[i].getPropertyType();
+                        break;
+                    }
+                }
+            } else {        
+                // just in case introspection silently fails.
+		throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nobeaninfo",
+					 beanClass.getName()));
+	    }
+	} catch (Exception ex) {
+	    throw new JasperException (ex);
+	}
+        if (method == null) {
+            if (type == null) {
+		throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.noproperty", prop,
+					 beanClass.getName()));
+            } else {
+		throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nomethod", prop,
+					 beanClass.getName()));
+            }
+        }
+
+	return method;
+    }
+
+    //*********************************************************************
+    // PropertyEditor Support
+
+    public static Object getValueFromBeanInfoPropertyEditor(
+		           Class attrClass, String attrName, String attrValue,
+			   Class propertyEditorClass) 
+	throws JasperException 
+    {
+	try {
+	    PropertyEditor pe = (PropertyEditor)propertyEditorClass.newInstance();
+	    pe.setAsText(attrValue);
+	    return pe.getValue();
+	} catch (Exception ex) {
+	    throw new JasperException(
+                Localizer.getMessage("jsp.error.beans.property.conversion",
+				     attrValue, attrClass.getName(), attrName,
+				     ex.getMessage()));
+	}
+    }
+
+    public static Object getValueFromPropertyEditorManager(
+	             Class attrClass, String attrName, String attrValue) 
+	throws JasperException 
+    {
+	try {
+	    PropertyEditor propEditor = 
+		PropertyEditorManager.findEditor(attrClass);
+	    if (propEditor != null) {
+		propEditor.setAsText(attrValue);
+		return propEditor.getValue();
+	    } else {
+		throw new IllegalArgumentException(
+                    Localizer.getMessage("jsp.error.beans.propertyeditor.notregistered"));
+	    }
+	} catch (IllegalArgumentException ex) {
+	    throw new JasperException(
+                Localizer.getMessage("jsp.error.beans.property.conversion",
+				     attrValue, attrClass.getName(), attrName,
+				     ex.getMessage()));
+	}
+    }
+
+
+    // ************************************************************************
+    // General Purpose Runtime Methods
+    // ************************************************************************
+
+
+    /**
+     * Convert a possibly relative resource path into a context-relative
+     * resource path that starts with a '/'.
+     *
+     * @param request The servlet request we are processing
+     * @param relativePath The possibly relative resource path
+     */
+    public static String getContextRelativePath(ServletRequest request,
+                                                String relativePath) {
+
+        if (relativePath.startsWith("/"))
+            return (relativePath);
+        if (!(request instanceof HttpServletRequest))
+            return (relativePath);
+        HttpServletRequest hrequest = (HttpServletRequest) request;
+        String uri = (String)
+            request.getAttribute("javax.servlet.include.servlet_path");
+        if (uri != null) {
+            String pathInfo = (String)
+                request.getAttribute("javax.servlet.include.path_info");
+            if (pathInfo == null) {
+                if (uri.lastIndexOf('/') >= 0) 
+                    uri = uri.substring(0, uri.lastIndexOf('/'));
+            }
+        }
+        else {
+            uri = hrequest.getServletPath();
+            if (uri.lastIndexOf('/') >= 0) 
+                uri = uri.substring(0, uri.lastIndexOf('/'));
+        }
+        return uri + '/' + relativePath;
+
+    }
+
+
+    /**
+     * Perform a RequestDispatcher.include() operation, with optional flushing
+     * of the response beforehand.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     * @param relativePath The relative path of the resource to be included
+     * @param out The Writer to whom we are currently writing
+     * @param flush Should we flush before the include is processed?
+     *
+     * @exception IOException if thrown by the included servlet
+     * @exception ServletException if thrown by the included servlet
+     */
+    public static void include(ServletRequest request,
+                               ServletResponse response,
+                               String relativePath,
+                               JspWriter out,
+                               boolean flush)
+        throws IOException, ServletException {
+
+        if (flush && !(out instanceof BodyContent))
+            out.flush();
+
+        // FIXME - It is tempting to use request.getRequestDispatcher() to
+        // resolve a relative path directly, but Catalina currently does not
+        // take into account whether the caller is inside a RequestDispatcher
+        // include or not.  Whether Catalina *should* take that into account
+        // is a spec issue currently under review.  In the mean time,
+        // replicate Jasper's previous behavior
+
+        String resourcePath = getContextRelativePath(request, relativePath);
+        RequestDispatcher rd = request.getRequestDispatcher(resourcePath);
+
+        rd.include(request,
+                   new ServletResponseWrapperInclude(response, out));
+
+    }
+
+    /**
+     * URL encodes a string, based on the supplied character encoding.
+     * This performs the same function as java.next.URLEncode.encode
+     * in J2SDK1.4, and should be removed if the only platform supported
+     * is 1.4 or higher.
+     * @param s The String to be URL encoded.
+     * @param enc The character encoding 
+     * @return The URL encoded String
+     */
+    public static String URLEncode(String s, String enc) {
+
+	if (s == null) {
+	    return "null";
+	}
+
+	if (enc == null) {
+	    enc = "ISO-8859-1";	// The default request encoding 
+	}
+
+	StringBuffer out = new StringBuffer(s.length());
+	ByteArrayOutputStream buf = new ByteArrayOutputStream();
+	OutputStreamWriter writer = null;
+	try {
+	    writer = new OutputStreamWriter(buf, enc);
+	} catch (java.io.UnsupportedEncodingException ex) {
+	    // Use the default encoding?
+	    writer = new OutputStreamWriter(buf);
+	}
+	
+	for (int i = 0; i < s.length(); i++) {
+	    int c = s.charAt(i);
+	    if (c == ' ') {
+		out.append('+');
+	    } else if (isSafeChar(c)) {
+		out.append((char)c);
+	    } else {
+		// convert to external encoding before hex conversion
+		try {
+		    writer.write(c);
+		    writer.flush();
+		} catch(IOException e) {
+		    buf.reset();
+		    continue;
+		}
+		byte[] ba = buf.toByteArray();
+		for (int j = 0; j < ba.length; j++) {
+		    out.append('%');
+		    // Converting each byte in the buffer
+		    out.append(Character.forDigit((ba[j]>>4) & 0xf, 16));
+		    out.append(Character.forDigit(ba[j] & 0xf, 16));
+		}
+		buf.reset();
+	    }
+	}
+	return out.toString();
+    }
+
+    private static boolean isSafeChar(int c) {
+	if (c >= 'a' && c <= 'z') {
+	    return true;
+	}
+	if (c >= 'A' && c <= 'Z') {
+	    return true;
+	}
+	if (c >= '0' && c <= '9') {
+	    return true;
+	}
+	if (c == '-' || c == '_' || c == '.' || c == '!' ||
+	    c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
+	    return true;
+	}
+	return false;
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java
new file mode 100644
index 0000000..2ba9364
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jasper.runtime;
+
+/**
+ * Interface for tracking the source files dependencies, for the purpose
+ * of compiling out of date pages.  This is used for
+ * 1) files that are included by page directives
+ * 2) files that are included by include-prelude and include-coda in jsp:config
+ * 3) files that are tag files and referenced
+ * 4) TLDs referenced
+ */
+
+public interface JspSourceDependent {
+
+   /**
+    * Returns a list of files names that the current page has a source
+    * dependency on.
+    */
+    // FIXME: Type used is Object due to very weird behavior 
+    // with Eclipse JDT 3.1 in Java 5 mode
+    public Object getDependants();
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java
new file mode 100644
index 0000000..cf7a443
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java
@@ -0,0 +1,590 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.security.SecurityUtil;
+
+/**
+ * Write text to a character-output stream, buffering characters so as
+ * to provide for the efficient writing of single characters, arrays,
+ * and strings. 
+ *
+ * Provide support for discarding for the output that has been 
+ * buffered. 
+ * 
+ * This needs revisiting when the buffering problems in the JSP spec
+ * are fixed -akv 
+ *
+ * @author Anil K. Vijendran
+ */
+public class JspWriterImpl extends JspWriter {
+    
+    private Writer out;
+    private ServletResponse response;    
+    private char cb[];
+    private int nextChar;
+    private boolean flushed = false;
+    private boolean closed = false;
+    
+    public JspWriterImpl() {
+        super( Constants.DEFAULT_BUFFER_SIZE, true );
+    }
+    
+    /**
+     * Create a buffered character-output stream that uses a default-sized
+     * output buffer.
+     *
+     * @param  response  A Servlet Response
+     */
+    public JspWriterImpl(ServletResponse response) {
+        this(response, Constants.DEFAULT_BUFFER_SIZE, true);
+    }
+    
+    /**
+     * Create a new buffered character-output stream that uses an output
+     * buffer of the given size.
+     *
+     * @param  response A Servlet Response
+     * @param  sz   	Output-buffer size, a positive integer
+     *
+     * @exception  IllegalArgumentException  If sz is <= 0
+     */
+    public JspWriterImpl(ServletResponse response, int sz, 
+            boolean autoFlush) {
+        super(sz, autoFlush);
+        if (sz < 0)
+            throw new IllegalArgumentException("Buffer size <= 0");
+        this.response = response;
+        cb = sz == 0 ? null : new char[sz];
+        nextChar = 0;
+    }
+    
+    void init( ServletResponse response, int sz, boolean autoFlush ) {
+        this.response= response;
+        if( sz > 0 && ( cb == null || sz > cb.length ) )
+            cb=new char[sz];
+        nextChar = 0;
+        this.autoFlush=autoFlush;
+        this.bufferSize=sz;
+    }
+    
+    /** Package-level access
+     */
+    void recycle() {
+        flushed = false;
+        closed = false;
+        out = null;
+        nextChar = 0;
+        response = null;
+    }
+    
+    /**
+     * Flush the output buffer to the underlying character stream, without
+     * flushing the stream itself.  This method is non-private only so that it
+     * may be invoked by PrintStream.
+     */
+    protected final void flushBuffer() throws IOException {
+        if (bufferSize == 0)
+            return;
+        flushed = true;
+        ensureOpen();
+        if (nextChar == 0)
+            return;
+        initOut();
+        out.write(cb, 0, nextChar);
+        nextChar = 0;
+    }
+    
+    private void initOut() throws IOException {
+        if (out == null) {
+            out = response.getWriter();
+        }
+    }
+    
+    private String getLocalizeMessage(final String message){
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            return (String)AccessController.doPrivileged(new PrivilegedAction(){
+                public Object run(){
+                    return Localizer.getMessage(message); 
+                }
+            });
+        } else {
+            return Localizer.getMessage(message);
+        }
+    }
+    
+    /**
+     * Discard the output buffer.
+     */
+    public final void clear() throws IOException {
+        if ((bufferSize == 0) && (out != null))
+            // clear() is illegal after any unbuffered output (JSP.5.5)
+            throw new IllegalStateException(
+                    getLocalizeMessage("jsp.error.ise_on_clear"));
+        if (flushed)
+            throw new IOException(
+                    getLocalizeMessage("jsp.error.attempt_to_clear_flushed_buffer"));
+        ensureOpen();
+        nextChar = 0;
+    }
+    
+    public void clearBuffer() throws IOException {
+        if (bufferSize == 0)
+            throw new IllegalStateException(
+                    getLocalizeMessage("jsp.error.ise_on_clear"));
+        ensureOpen();
+        nextChar = 0;
+    }
+    
+    private final void bufferOverflow() throws IOException {
+        throw new IOException(getLocalizeMessage("jsp.error.overflow"));
+    }
+    
+    /**
+     * Flush the stream.
+     *
+     */
+    public void flush()  throws IOException {
+        flushBuffer();
+        if (out != null) {
+            out.flush();
+        }
+    }
+    
+    /**
+     * Close the stream.
+     *
+     */
+    public void close() throws IOException {
+        if (response == null || closed)
+            // multiple calls to close is OK
+            return;
+        flush();
+        if (out != null)
+            out.close();
+        out = null;
+        closed = true;
+    }
+    
+    /**
+     * @return the number of bytes unused in the buffer
+     */
+    public int getRemaining() {
+        return bufferSize - nextChar;
+    }
+    
+    /** check to make sure that the stream has not been closed */
+    private void ensureOpen() throws IOException {
+        if (response == null || closed)
+            throw new IOException("Stream closed");
+    }
+    
+    
+    /**
+     * Write a single character.
+     */
+    public void write(int c) throws IOException {
+        ensureOpen();
+        if (bufferSize == 0) {
+            initOut();
+            out.write(c);
+        }
+        else {
+            if (nextChar >= bufferSize)
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+            cb[nextChar++] = (char) c;
+        }
+    }
+    
+    /**
+     * Our own little min method, to avoid loading java.lang.Math if we've run
+     * out of file descriptors and we're trying to print a stack trace.
+     */
+    private int min(int a, int b) {
+        if (a < b) return a;
+        return b;
+    }
+    
+    /**
+     * Write a portion of an array of characters.
+     *
+     * <p> Ordinarily this method stores characters from the given array into
+     * this stream's buffer, flushing the buffer to the underlying stream as
+     * needed.  If the requested length is at least as large as the buffer,
+     * however, then this method will flush the buffer and write the characters
+     * directly to the underlying stream.  Thus redundant
+     * <code>DiscardableBufferedWriter</code>s will not copy data unnecessarily.
+     *
+     * @param  cbuf  A character array
+     * @param  off   Offset from which to start reading characters
+     * @param  len   Number of characters to write
+     */
+    public void write(char cbuf[], int off, int len) 
+    throws IOException 
+    {
+        ensureOpen();
+        
+        if (bufferSize == 0) {
+            initOut();
+            out.write(cbuf, off, len);
+            return;
+        }
+        
+        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+                ((off + len) > cbuf.length) || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return;
+        } 
+        
+        if (len >= bufferSize) {
+            /* If the request length exceeds the size of the output buffer,
+             flush the buffer and then write the data directly.  In this
+             way buffered streams will cascade harmlessly. */
+            if (autoFlush)
+                flushBuffer();
+            else
+                bufferOverflow();
+            initOut();
+            out.write(cbuf, off, len);
+            return;
+        }
+        
+        int b = off, t = off + len;
+        while (b < t) {
+            int d = min(bufferSize - nextChar, t - b);
+            System.arraycopy(cbuf, b, cb, nextChar, d);
+            b += d;
+            nextChar += d;
+            if (nextChar >= bufferSize) 
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+        }
+        
+    }
+    
+    /**
+     * Write an array of characters.  This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     */
+    public void write(char buf[]) throws IOException {
+        write(buf, 0, buf.length);
+    }
+    
+    /**
+     * Write a portion of a String.
+     *
+     * @param  s     String to be written
+     * @param  off   Offset from which to start reading characters
+     * @param  len   Number of characters to be written
+     */
+    public void write(String s, int off, int len) throws IOException {
+        ensureOpen();
+        if (bufferSize == 0) {
+            initOut();
+            out.write(s, off, len);
+            return;
+        }
+        int b = off, t = off + len;
+        while (b < t) {
+            int d = min(bufferSize - nextChar, t - b);
+            s.getChars(b, b + d, cb, nextChar);
+            b += d;
+            nextChar += d;
+            if (nextChar >= bufferSize) 
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+        }
+    }
+    
+    /**
+     * Write a string.  This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     */
+    public void write(String s) throws IOException {
+        // Simple fix for Bugzilla 35410
+        // Calling the other write function so as to init the buffer anyways
+        if(s == null) {
+            write(s, 0, 0);
+        } else {
+            write(s, 0, s.length());
+        }
+    }
+    
+    
+    static String lineSeparator = System.getProperty("line.separator");
+    
+    /**
+     * Write a line separator.  The line separator string is defined by the
+     * system property <tt>line.separator</tt>, and is not necessarily a single
+     * newline ('\n') character.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    
+    public void newLine() throws IOException {
+        write(lineSeparator);
+    }
+    
+    
+    /* Methods that do not terminate lines */
+    
+    /**
+     * Print a boolean value.  The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      b   The <code>boolean</code> to be printed
+     */
+    public void print(boolean b) throws IOException {
+        write(b ? "true" : "false");
+    }
+    
+    /**
+     * Print a character.  The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      c   The <code>char</code> to be printed
+     */
+    public void print(char c) throws IOException {
+        write(String.valueOf(c));
+    }
+    
+    /**
+     * Print an integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are
+     * written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      i   The <code>int</code> to be printed
+     */
+    public void print(int i) throws IOException {
+        write(String.valueOf(i));
+    }
+    
+    /**
+     * Print a long integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      l   The <code>long</code> to be printed
+     */
+    public void print(long l) throws IOException {
+        write(String.valueOf(l));
+    }
+    
+    /**
+     * Print a floating-point number.  The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      f   The <code>float</code> to be printed
+     */
+    public void print(float f) throws IOException {
+        write(String.valueOf(f));
+    }
+    
+    /**
+     * Print a double-precision floating-point number.  The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      d   The <code>double</code> to be printed
+     */
+    public void print(double d) throws IOException {
+        write(String.valueOf(d));
+    }
+    
+    /**
+     * Print an array of characters.  The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      s   The array of chars to be printed
+     *
+     * @throws  NullPointerException  If <code>s</code> is <code>null</code>
+     */
+    public void print(char s[]) throws IOException {
+        write(s);
+    }
+    
+    /**
+     * Print a string.  If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed.  Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param      s   The <code>String</code> to be printed
+     */
+    public void print(String s) throws IOException {
+        if (s == null) {
+            s = "null";
+        }
+        write(s);
+    }
+    
+    /**
+     * Print an object.  The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      obj   The <code>Object</code> to be printed
+     */
+    public void print(Object obj) throws IOException {
+        write(String.valueOf(obj));
+    }
+    
+    /* Methods that do terminate lines */
+    
+    /**
+     * Terminate the current line by writing the line separator string.  The
+     * line separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     *
+     * Need to change this from PrintWriter because the default
+     * println() writes  to the sink directly instead of through the
+     * write method...  
+     */
+    public void println() throws IOException {
+        newLine();
+    }
+    
+    /**
+     * Print a boolean value and then terminate the line.  This method behaves
+     * as though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(boolean x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a character and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+     * #println()}</code>.
+     */
+    public void println(char x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an integer and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+     * #println()}</code>.
+     */
+    public void println(int x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a long integer and then terminate the line.  This method behaves
+     * as though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(long x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a floating-point number and then terminate the line.  This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(float x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a double-precision floating-point number and then terminate the
+     * line.  This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     */
+    public void println(double x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an array of characters and then terminate the line.  This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(char x[]) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print a String and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(String x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Print an Object and then terminate the line.  This method behaves as
+     * though it invokes <code>{@link #print(Object)}</code> and then
+     * <code>{@link #println()}</code>.
+     */
+    public void println(Object x) throws IOException {
+        print(x);
+        println();
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java
new file mode 100644
index 0000000..4b71e8a
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java
@@ -0,0 +1,951 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.el.ELContextImpl;
+import org.apache.jasper.el.ExpressionEvaluatorImpl;
+import org.apache.jasper.el.FunctionMapperImpl;
+import org.apache.jasper.el.VariableResolverImpl;
+import org.apache.jasper.security.SecurityUtil;
+import org.apache.jasper.util.Enumerator;
+
+/**
+ * Implementation of the PageContext class from the JSP spec. Also doubles as a
+ * VariableResolver for the EL.
+ * 
+ * @author Anil K. Vijendran
+ * @author Larry Cable
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ * @author Mark Roth
+ * @author Jan Luehe
+ * @author Jacob Hookom
+ */
+public class PageContextImpl extends PageContext {
+
+	private static final JspFactory jspf = JspFactory.getDefaultFactory(); 
+
+	private BodyContentImpl[] outs;
+
+	private int depth;
+
+	// per-servlet state
+	private Servlet servlet;
+
+	private ServletConfig config;
+
+	private ServletContext context;
+
+	private JspApplicationContextImpl applicationContext;
+
+	private String errorPageURL;
+
+	// page-scope attributes
+	private transient HashMap<String, Object> attributes;
+
+	// per-request state
+	private transient ServletRequest request;
+
+	private transient ServletResponse response;
+
+	private transient HttpSession session;
+	
+	private transient ELContextImpl elContext;
+
+	private boolean isIncluded;
+	
+	
+	// initial output stream
+	private transient JspWriter out;
+
+	private transient JspWriterImpl baseOut;
+
+	/*
+	 * Constructor.
+	 */
+	PageContextImpl() {
+		this.outs = new BodyContentImpl[0];
+		this.attributes = new HashMap<String, Object>(16);
+		this.depth = -1;
+	}
+
+	public void initialize(Servlet servlet, ServletRequest request,
+			ServletResponse response, String errorPageURL,
+			boolean needsSession, int bufferSize, boolean autoFlush)
+			throws IOException {
+
+		_initialize(servlet, request, response, errorPageURL, needsSession,
+				bufferSize, autoFlush);
+	}
+
+	private void _initialize(Servlet servlet, ServletRequest request,
+			ServletResponse response, String errorPageURL,
+			boolean needsSession, int bufferSize, boolean autoFlush)
+			throws IOException {
+
+		// initialize state
+		this.servlet = servlet;
+		this.config = servlet.getServletConfig();
+		this.context = config.getServletContext();
+		this.errorPageURL = errorPageURL;
+		this.request = request;
+		this.response = response;
+		
+		// initialize application context
+		this.applicationContext = JspApplicationContextImpl.getInstance(context);
+
+		// Setup session (if required)
+		if (request instanceof HttpServletRequest && needsSession)
+			this.session = ((HttpServletRequest) request).getSession();
+		if (needsSession && session == null)
+			throw new IllegalStateException(
+					"Page needs a session and none is available");
+
+		// initialize the initial out ...
+		depth = -1;
+		if (this.baseOut == null) {
+			this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush);
+		} else {
+			this.baseOut.init(response, bufferSize, autoFlush);
+		}
+		this.out = baseOut;
+
+		// register names/values as per spec
+		setAttribute(OUT, this.out);
+		setAttribute(REQUEST, request);
+		setAttribute(RESPONSE, response);
+
+		if (session != null)
+			setAttribute(SESSION, session);
+
+		setAttribute(PAGE, servlet);
+		setAttribute(CONFIG, config);
+		setAttribute(PAGECONTEXT, this);
+		setAttribute(APPLICATION, context);
+
+		isIncluded = request.getAttribute("javax.servlet.include.servlet_path") != null;
+	}
+
+	public void release() {
+		out = baseOut;
+		try {
+			if (isIncluded) {
+				((JspWriterImpl) out).flushBuffer();
+				// push it into the including jspWriter
+			} else {
+				// Old code:
+				// out.flush();
+				// Do not flush the buffer even if we're not included (i.e.
+				// we are the main page. The servlet will flush it and close
+				// the stream.
+				((JspWriterImpl) out).flushBuffer();
+			}
+		} catch (IOException ex) {
+            IllegalStateException ise = new IllegalStateException(Localizer.getMessage("jsp.error.flush"), ex);
+            throw ise;
+		} finally {
+		    servlet = null;
+		    config = null;
+		    context = null;
+		    applicationContext = null;
+		    elContext = null;
+		    errorPageURL = null;
+		    request = null;
+		    response = null;
+		    depth = -1;
+		    baseOut.recycle();
+		    session = null;
+		    attributes.clear();
+        }
+	}
+
+	public Object getAttribute(final String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			return AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					return doGetAttribute(name);
+				}
+			});
+		} else {
+			return doGetAttribute(name);
+		}
+
+	}
+
+	private Object doGetAttribute(String name) {
+		return attributes.get(name);
+	}
+
+	public Object getAttribute(final String name, final int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			return AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					return doGetAttribute(name, scope);
+				}
+			});
+		} else {
+			return doGetAttribute(name, scope);
+		}
+
+	}
+
+	private Object doGetAttribute(String name, int scope) {
+		switch (scope) {
+		case PAGE_SCOPE:
+			return attributes.get(name);
+
+		case REQUEST_SCOPE:
+			return request.getAttribute(name);
+
+		case SESSION_SCOPE:
+			if (session == null) {
+				throw new IllegalStateException(Localizer
+						.getMessage("jsp.error.page.noSession"));
+			}
+			return session.getAttribute(name);
+
+		case APPLICATION_SCOPE:
+			return context.getAttribute(name);
+
+		default:
+			throw new IllegalArgumentException("Invalid scope");
+		}
+	}
+
+	public void setAttribute(final String name, final Object attribute) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					doSetAttribute(name, attribute);
+					return null;
+				}
+			});
+		} else {
+			doSetAttribute(name, attribute);
+		}
+	}
+
+	private void doSetAttribute(String name, Object attribute) {
+		if (attribute != null) {
+			attributes.put(name, attribute);
+		} else {
+			removeAttribute(name, PAGE_SCOPE);
+		}
+	}
+
+	public void setAttribute(final String name, final Object o, final int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					doSetAttribute(name, o, scope);
+					return null;
+				}
+			});
+		} else {
+			doSetAttribute(name, o, scope);
+		}
+
+	}
+
+	private void doSetAttribute(String name, Object o, int scope) {
+		if (o != null) {
+			switch (scope) {
+			case PAGE_SCOPE:
+				attributes.put(name, o);
+				break;
+
+			case REQUEST_SCOPE:
+				request.setAttribute(name, o);
+				break;
+
+			case SESSION_SCOPE:
+				if (session == null) {
+					throw new IllegalStateException(Localizer
+							.getMessage("jsp.error.page.noSession"));
+				}
+				session.setAttribute(name, o);
+				break;
+
+			case APPLICATION_SCOPE:
+				context.setAttribute(name, o);
+				break;
+
+			default:
+				throw new IllegalArgumentException("Invalid scope");
+			}
+		} else {
+			removeAttribute(name, scope);
+		}
+	}
+
+	public void removeAttribute(final String name, final int scope) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					doRemoveAttribute(name, scope);
+					return null;
+				}
+			});
+		} else {
+			doRemoveAttribute(name, scope);
+		}
+	}
+
+	private void doRemoveAttribute(String name, int scope) {
+		switch (scope) {
+		case PAGE_SCOPE:
+			attributes.remove(name);
+			break;
+
+		case REQUEST_SCOPE:
+			request.removeAttribute(name);
+			break;
+
+		case SESSION_SCOPE:
+			if (session == null) {
+				throw new IllegalStateException(Localizer
+						.getMessage("jsp.error.page.noSession"));
+			}
+			session.removeAttribute(name);
+			break;
+
+		case APPLICATION_SCOPE:
+			context.removeAttribute(name);
+			break;
+
+		default:
+			throw new IllegalArgumentException("Invalid scope");
+		}
+	}
+
+	public int getAttributesScope(final String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			return ((Integer) AccessController
+					.doPrivileged(new PrivilegedAction() {
+						public Object run() {
+							return new Integer(doGetAttributeScope(name));
+						}
+					})).intValue();
+		} else {
+			return doGetAttributeScope(name);
+		}
+	}
+
+	private int doGetAttributeScope(String name) {
+		if (attributes.get(name) != null)
+			return PAGE_SCOPE;
+
+		if (request.getAttribute(name) != null)
+			return REQUEST_SCOPE;
+
+		if (session != null) {
+		    try {
+		        if (session.getAttribute(name) != null)
+		            return SESSION_SCOPE;
+	        } catch(IllegalStateException ise) {
+	            // Session has been invalidated.
+		        // Ignore and fall through to application scope.
+		    }
+		}
+
+		if (context.getAttribute(name) != null)
+			return APPLICATION_SCOPE;
+
+		return 0;
+	}
+
+	public Object findAttribute(final String name) {
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			return AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					if (name == null) {
+						throw new NullPointerException(Localizer
+								.getMessage("jsp.error.attribute.null_name"));
+					}
+
+					return doFindAttribute(name);
+				}
+			});
+		} else {
+			if (name == null) {
+				throw new NullPointerException(Localizer
+						.getMessage("jsp.error.attribute.null_name"));
+			}
+
+			return doFindAttribute(name);
+		}
+	}
+
+	private Object doFindAttribute(String name) {
+
+		Object o = attributes.get(name);
+		if (o != null)
+			return o;
+
+		o = request.getAttribute(name);
+		if (o != null)
+			return o;
+
+		if (session != null) {
+		    try {
+		        o = session.getAttribute(name);
+		    } catch(IllegalStateException ise) {
+		        // Session has been invalidated.
+		        // Ignore and fall through to application scope.
+	        }
+			if (o != null)
+				return o;
+		}
+
+		return context.getAttribute(name);
+	}
+
+	public Enumeration<String> getAttributeNamesInScope(final int scope) {
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			return (Enumeration) AccessController
+					.doPrivileged(new PrivilegedAction() {
+						public Object run() {
+							return doGetAttributeNamesInScope(scope);
+						}
+					});
+		} else {
+			return doGetAttributeNamesInScope(scope);
+		}
+	}
+
+	private Enumeration doGetAttributeNamesInScope(int scope) {
+		switch (scope) {
+		case PAGE_SCOPE:
+			return new Enumerator(attributes.keySet().iterator());
+
+		case REQUEST_SCOPE:
+			return request.getAttributeNames();
+
+		case SESSION_SCOPE:
+			if (session == null) {
+				throw new IllegalStateException(Localizer
+						.getMessage("jsp.error.page.noSession"));
+			}
+			return session.getAttributeNames();
+
+		case APPLICATION_SCOPE:
+			return context.getAttributeNames();
+
+		default:
+			throw new IllegalArgumentException("Invalid scope");
+		}
+	}
+
+	public void removeAttribute(final String name) {
+
+		if (name == null) {
+			throw new NullPointerException(Localizer
+					.getMessage("jsp.error.attribute.null_name"));
+		}
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+					doRemoveAttribute(name);
+					return null;
+				}
+			});
+		} else {
+			doRemoveAttribute(name);
+		}
+	}
+
+	private void doRemoveAttribute(String name) {
+	    removeAttribute(name, PAGE_SCOPE);
+	    removeAttribute(name, REQUEST_SCOPE);
+	    if( session != null ) {
+	        try {
+	            removeAttribute(name, SESSION_SCOPE);
+	        } catch(IllegalStateException ise) {
+	            // Session has been invalidated.
+	            // Ignore and fall throw to application scope.
+	        }
+	    }
+	    removeAttribute(name, APPLICATION_SCOPE);
+	}
+
+	public JspWriter getOut() {
+		return out;
+	}
+
+	public HttpSession getSession() {
+		return session;
+	}
+
+	public Servlet getServlet() {
+		return servlet;
+	}
+
+	public ServletConfig getServletConfig() {
+		return config;
+	}
+
+	public ServletContext getServletContext() {
+		return config.getServletContext();
+	}
+
+	public ServletRequest getRequest() {
+		return request;
+	}
+
+	public ServletResponse getResponse() {
+		return response;
+	}
+
+	/**
+	 * Returns the exception associated with this page context, if any. <p/>
+	 * Added wrapping for Throwables to avoid ClassCastException: see Bugzilla
+	 * 31171 for details.
+	 * 
+	 * @return The Exception associated with this page context, if any.
+	 */
+	public Exception getException() {
+		Throwable t = JspRuntimeLibrary.getThrowable(request);
+
+		// Only wrap if needed
+		if ((t != null) && (!(t instanceof Exception))) {
+			t = new JspException(t);
+		}
+
+		return (Exception) t;
+	}
+
+	public Object getPage() {
+		return servlet;
+	}
+
+	private final String getAbsolutePathRelativeToContext(String relativeUrlPath) {
+		String path = relativeUrlPath;
+
+		if (!path.startsWith("/")) {
+			String uri = (String) request
+					.getAttribute("javax.servlet.include.servlet_path");
+			if (uri == null)
+				uri = ((HttpServletRequest) request).getServletPath();
+			String baseURI = uri.substring(0, uri.lastIndexOf('/'));
+			path = baseURI + '/' + path;
+		}
+
+		return path;
+	}
+
+	public void include(String relativeUrlPath) throws ServletException,
+			IOException {
+		JspRuntimeLibrary
+				.include(request, response, relativeUrlPath, out, true);
+	}
+
+	public void include(final String relativeUrlPath, final boolean flush)
+			throws ServletException, IOException {
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			try {
+				AccessController.doPrivileged(new PrivilegedExceptionAction() {
+					public Object run() throws Exception {
+						doInclude(relativeUrlPath, flush);
+						return null;
+					}
+				});
+			} catch (PrivilegedActionException e) {
+				Exception ex = e.getException();
+				if (ex instanceof IOException) {
+					throw (IOException) ex;
+				} else {
+					throw (ServletException) ex;
+				}
+			}
+		} else {
+			doInclude(relativeUrlPath, flush);
+		}
+	}
+
+	private void doInclude(String relativeUrlPath, boolean flush)
+			throws ServletException, IOException {
+		JspRuntimeLibrary.include(request, response, relativeUrlPath, out,
+				flush);
+	}
+
+	public VariableResolver getVariableResolver() {
+		return new VariableResolverImpl(this.getELContext());
+	}
+
+	public void forward(final String relativeUrlPath) throws ServletException,
+			IOException {
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			try {
+				AccessController.doPrivileged(new PrivilegedExceptionAction() {
+					public Object run() throws Exception {
+						doForward(relativeUrlPath);
+						return null;
+					}
+				});
+			} catch (PrivilegedActionException e) {
+				Exception ex = e.getException();
+				if (ex instanceof IOException) {
+					throw (IOException) ex;
+				} else {
+					throw (ServletException) ex;
+				}
+			}
+		} else {
+			doForward(relativeUrlPath);
+		}
+	}
+
+	private void doForward(String relativeUrlPath) throws ServletException,
+			IOException {
+
+		// JSP.4.5 If the buffer was flushed, throw IllegalStateException
+		try {
+			out.clear();
+		} catch (IOException ex) {
+			IllegalStateException ise = new IllegalStateException(Localizer
+					.getMessage("jsp.error.attempt_to_clear_flushed_buffer"));
+			ise.initCause(ex);
+			throw ise;
+		}
+
+		// Make sure that the response object is not the wrapper for include
+		while (response instanceof ServletResponseWrapperInclude) {
+			response = ((ServletResponseWrapperInclude) response).getResponse();
+		}
+
+		final String path = getAbsolutePathRelativeToContext(relativeUrlPath);
+		String includeUri = (String) request
+				.getAttribute(Constants.INC_SERVLET_PATH);
+
+		if (includeUri != null)
+			request.removeAttribute(Constants.INC_SERVLET_PATH);
+		try {
+			context.getRequestDispatcher(path).forward(request, response);
+		} finally {
+			if (includeUri != null)
+				request.setAttribute(Constants.INC_SERVLET_PATH, includeUri);
+		}
+	}
+
+	public BodyContent pushBody() {
+		return (BodyContent) pushBody(null);
+	}
+
+	public JspWriter pushBody(Writer writer) {
+		depth++;
+		if (depth >= outs.length) {
+			BodyContentImpl[] newOuts = new BodyContentImpl[depth + 1];
+			for (int i = 0; i < outs.length; i++) {
+				newOuts[i] = outs[i];
+			}
+			newOuts[depth] = new BodyContentImpl(out);
+			outs = newOuts;
+		}
+
+		outs[depth].setWriter(writer);
+		out = outs[depth];
+
+		// Update the value of the "out" attribute in the page scope
+		// attribute namespace of this PageContext
+		setAttribute(OUT, out);
+
+		return outs[depth];
+	}
+
+	public JspWriter popBody() {
+		depth--;
+		if (depth >= 0) {
+			out = outs[depth];
+		} else {
+			out = baseOut;
+		}
+
+		// Update the value of the "out" attribute in the page scope
+		// attribute namespace of this PageContext
+		setAttribute(OUT, out);
+
+		return out;
+	}
+
+	/**
+	 * Provides programmatic access to the ExpressionEvaluator. The JSP
+	 * Container must return a valid instance of an ExpressionEvaluator that can
+	 * parse EL expressions.
+	 */
+	public ExpressionEvaluator getExpressionEvaluator() {
+		return new ExpressionEvaluatorImpl(this.applicationContext.getExpressionFactory());
+	}
+
+	public void handlePageException(Exception ex) throws IOException,
+			ServletException {
+		// Should never be called since handleException() called with a
+		// Throwable in the generated servlet.
+		handlePageException((Throwable) ex);
+	}
+
+	public void handlePageException(final Throwable t) throws IOException,
+			ServletException {
+		if (t == null)
+			throw new NullPointerException("null Throwable");
+
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			try {
+				AccessController.doPrivileged(new PrivilegedExceptionAction() {
+					public Object run() throws Exception {
+						doHandlePageException(t);
+						return null;
+					}
+				});
+			} catch (PrivilegedActionException e) {
+				Exception ex = e.getException();
+				if (ex instanceof IOException) {
+					throw (IOException) ex;
+				} else {
+					throw (ServletException) ex;
+				}
+			}
+		} else {
+			doHandlePageException(t);
+		}
+
+	}
+
+	private void doHandlePageException(Throwable t) throws IOException,
+			ServletException {
+
+		if (errorPageURL != null && !errorPageURL.equals("")) {
+
+			/*
+			 * Set request attributes. Do not set the
+			 * javax.servlet.error.exception attribute here (instead, set in the
+			 * generated servlet code for the error page) in order to prevent
+			 * the ErrorReportValve, which is invoked as part of forwarding the
+			 * request to the error page, from throwing it if the response has
+			 * not been committed (the response will have been committed if the
+			 * error page is a JSP page).
+			 */
+			request.setAttribute("javax.servlet.jsp.jspException", t);
+			request.setAttribute("javax.servlet.error.status_code",
+					new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
+			request.setAttribute("javax.servlet.error.request_uri",
+					((HttpServletRequest) request).getRequestURI());
+			request.setAttribute("javax.servlet.error.servlet_name", config
+					.getServletName());
+			try {
+				forward(errorPageURL);
+			} catch (IllegalStateException ise) {
+				include(errorPageURL);
+			}
+
+			// The error page could be inside an include.
+
+			Object newException = request
+					.getAttribute("javax.servlet.error.exception");
+
+			// t==null means the attribute was not set.
+			if ((newException != null) && (newException == t)) {
+				request.removeAttribute("javax.servlet.error.exception");
+			}
+
+			// now clear the error code - to prevent double handling.
+			request.removeAttribute("javax.servlet.error.status_code");
+			request.removeAttribute("javax.servlet.error.request_uri");
+			request.removeAttribute("javax.servlet.error.status_code");
+			request.removeAttribute("javax.servlet.jsp.jspException");
+
+		} else {
+			// Otherwise throw the exception wrapped inside a ServletException.
+			// Set the exception as the root cause in the ServletException
+			// to get a stack trace for the real problem
+			if (t instanceof IOException)
+				throw (IOException) t;
+			if (t instanceof ServletException)
+				throw (ServletException) t;
+			if (t instanceof RuntimeException)
+				throw (RuntimeException) t;
+
+			Throwable rootCause = null;
+			if (t instanceof JspException) {
+				rootCause = ((JspException) t).getRootCause();
+			} else if (t instanceof ELException) {
+				rootCause = ((ELException) t).getRootCause();
+			}
+
+			if (rootCause != null) {
+				throw new ServletException(t.getClass().getName() + ": "
+						+ t.getMessage(), rootCause);
+			}
+
+			throw new ServletException(t);
+		}
+	}
+
+	private static String XmlEscape(String s) {
+		if (s == null)
+			return null;
+		StringBuffer sb = new StringBuffer();
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (c == '<') {
+				sb.append("&lt;");
+			} else if (c == '>') {
+				sb.append("&gt;");
+			} else if (c == '\'') {
+				sb.append("&#039;"); // &apos;
+			} else if (c == '&') {
+				sb.append("&amp;");
+			} else if (c == '"') {
+				sb.append("&#034;"); // &quot;
+			} else {
+				sb.append(c);
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Proprietary method to evaluate EL expressions. XXX - This method should
+	 * go away once the EL interpreter moves out of JSTL and into its own
+	 * project. For now, this is necessary because the standard machinery is too
+	 * slow.
+	 * 
+	 * @param expression
+	 *            The expression to be evaluated
+	 * @param expectedType
+	 *            The expected resulting type
+	 * @param pageContext
+	 *            The page context
+	 * @param functionMap
+	 *            Maps prefix and name to Method
+	 * @return The result of the evaluation
+	 */
+	public static Object proprietaryEvaluate(final String expression,
+			final Class expectedType, final PageContext pageContext,
+			final ProtectedFunctionMapper functionMap, final boolean escape)
+			throws ELException {
+		Object retValue;
+        final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory();
+		if (SecurityUtil.isPackageProtectionEnabled()) {
+			try {
+				retValue = AccessController
+						.doPrivileged(new PrivilegedExceptionAction() {
+
+							public Object run() throws Exception {
+                                ELContextImpl ctx = (ELContextImpl) pageContext.getELContext();
+                                ctx.setFunctionMapper(new FunctionMapperImpl(functionMap));
+								ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
+                                return ve.getValue(ctx);
+							}
+						});
+			} catch (PrivilegedActionException ex) {
+				Exception realEx = ex.getException();
+				if (realEx instanceof ELException) {
+					throw (ELException) realEx;
+				} else {
+					throw new ELException(realEx);
+				}
+			}
+		} else {
+            ELContextImpl ctx = (ELContextImpl) pageContext.getELContext();
+            ctx.setFunctionMapper(new FunctionMapperImpl(functionMap));
+            ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
+            retValue = ve.getValue(ctx);
+		}
+		if (escape && retValue != null) {
+			retValue = XmlEscape(retValue.toString());
+		}
+
+		return retValue;
+	}
+
+	public ELContext getELContext() {
+		if (this.elContext == null) {
+			this.elContext = this.applicationContext.createELContext(this);
+		}
+		return this.elContext;
+	}
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java
new file mode 100644
index 0000000..58640ef
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java
@@ -0,0 +1,134 @@
+/*
+ * 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.jasper.runtime;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Thread-local based pool of tag handlers that can be reused.
+ *
+ * @author Jan Luehe
+ * @author Costin Manolache
+ */
+public class PerThreadTagHandlerPool extends TagHandlerPool {
+
+    private int maxSize;
+
+    // For cleanup
+    private Vector perThreadDataVector;
+
+    private ThreadLocal perThread;
+
+    private static class PerThreadData {
+        Tag handlers[];
+        int current;
+    }
+
+    /**
+     * Constructs a tag handler pool with the default capacity.
+     */
+    public PerThreadTagHandlerPool() {
+        super();
+        perThreadDataVector = new Vector();
+    }
+
+    protected void init(ServletConfig config) {
+        maxSize = Constants.MAX_POOL_SIZE;
+        String maxSizeS = getOption(config, OPTION_MAXSIZE, null);
+        if (maxSizeS != null) {
+            maxSize = Integer.parseInt(maxSizeS);
+            if (maxSize < 0) {
+                maxSize = Constants.MAX_POOL_SIZE;
+            }
+        }
+
+        perThread = new ThreadLocal() {
+            protected Object initialValue() {
+                PerThreadData ptd = new PerThreadData();
+                ptd.handlers = new Tag[maxSize];
+                ptd.current = -1;
+                perThreadDataVector.addElement(ptd);
+                return ptd;
+            }
+        };
+    }
+
+    /**
+     * Gets the next available tag handler from this tag handler pool,
+     * instantiating one if this tag handler pool is empty.
+     *
+     * @param handlerClass Tag handler class
+     *
+     * @return Reused or newly instantiated tag handler
+     *
+     * @throws JspException if a tag handler cannot be instantiated
+     */
+    public Tag get(Class handlerClass) throws JspException {
+        PerThreadData ptd = (PerThreadData)perThread.get();
+        if(ptd.current >=0 ) {
+            return ptd.handlers[ptd.current--];
+        } else {
+	    try {
+		return (Tag) handlerClass.newInstance();
+	    } catch (Exception e) {
+		throw new JspException(e.getMessage(), e);
+	    }
+	}
+    }
+
+    /**
+     * Adds the given tag handler to this tag handler pool, unless this tag
+     * handler pool has already reached its capacity, in which case the tag
+     * handler's release() method is called.
+     *
+     * @param handler Tag handler to add to this tag handler pool
+     */
+    public void reuse(Tag handler) {
+        PerThreadData ptd=(PerThreadData)perThread.get();
+	if (ptd.current < (ptd.handlers.length - 1)) {
+	    ptd.handlers[++ptd.current] = handler;
+        } else {
+            handler.release();
+        }
+    }
+
+    /**
+     * Calls the release() method of all tag handlers in this tag handler pool.
+     */
+    public void release() {        
+        Enumeration enumeration = perThreadDataVector.elements();
+        while (enumeration.hasMoreElements()) {
+	    PerThreadData ptd = (PerThreadData)enumeration.nextElement();
+            if (ptd.handlers != null) {
+                for (int i=ptd.current; i>=0; i--) {
+                    if (ptd.handlers[i] != null) {
+                        ptd.handlers[i].release();
+		    }
+                }
+            }
+        }
+    }
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java
new file mode 100644
index 0000000..309e211
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java
@@ -0,0 +1,196 @@
+/*
+ * 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.jasper.runtime;
+
+import java.util.HashMap;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.lang.reflect.Method;
+import javax.servlet.jsp.el.FunctionMapper;
+
+import org.apache.jasper.security.SecurityUtil;
+
+/**
+ * Maps EL functions to their Java method counterparts. Keeps the actual Method
+ * objects protected so that JSP pages can't indirectly do reflection.
+ * 
+ * @author Mark Roth
+ * @author Kin-man Chung
+ */
+public final class ProtectedFunctionMapper extends javax.el.FunctionMapper
+        implements FunctionMapper {
+
+    /**
+     * Maps "prefix:name" to java.lang.Method objects.
+     */
+    private HashMap fnmap = null;
+
+    /**
+     * If there is only one function in the map, this is the Method for it.
+     */
+    private Method theMethod = null;
+
+    /**
+     * Constructor has protected access.
+     */
+    private ProtectedFunctionMapper() {
+    }
+
+    /**
+     * Generated Servlet and Tag Handler implementations call this method to
+     * retrieve an instance of the ProtectedFunctionMapper. This is necessary
+     * since generated code does not have access to create instances of classes
+     * in this package.
+     * 
+     * @return A new protected function mapper.
+     */
+    public static ProtectedFunctionMapper getInstance() {
+        ProtectedFunctionMapper funcMapper;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            funcMapper = (ProtectedFunctionMapper) AccessController
+                    .doPrivileged(new PrivilegedAction() {
+                        public Object run() {
+                            return new ProtectedFunctionMapper();
+                        }
+                    });
+        } else {
+            funcMapper = new ProtectedFunctionMapper();
+        }
+        funcMapper.fnmap = new java.util.HashMap();
+        return funcMapper;
+    }
+
+    /**
+     * Stores a mapping from the given EL function prefix and name to the given
+     * Java method.
+     * 
+     * @param fnQName
+     *            The EL function qualified name (including prefix)
+     * @param c
+     *            The class containing the Java method
+     * @param methodName
+     *            The name of the Java method
+     * @param args
+     *            The arguments of the Java method
+     * @throws RuntimeException
+     *             if no method with the given signature could be found.
+     */
+    public void mapFunction(String fnQName, final Class c,
+            final String methodName, final Class[] args) {
+        java.lang.reflect.Method method;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                method = (java.lang.reflect.Method) AccessController
+                        .doPrivileged(new PrivilegedExceptionAction() {
+
+                            public Object run() throws Exception {
+                                return c.getDeclaredMethod(methodName, args);
+                            }
+                        });
+            } catch (PrivilegedActionException ex) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + ex.getException().getMessage());
+            }
+        } else {
+            try {
+                method = c.getDeclaredMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + e.getMessage());
+            }
+        }
+
+        this.fnmap.put(fnQName, method);
+    }
+
+    /**
+     * Creates an instance for this class, and stores the Method for the given
+     * EL function prefix and name. This method is used for the case when there
+     * is only one function in the EL expression.
+     * 
+     * @param fnQName
+     *            The EL function qualified name (including prefix)
+     * @param c
+     *            The class containing the Java method
+     * @param methodName
+     *            The name of the Java method
+     * @param args
+     *            The arguments of the Java method
+     * @throws RuntimeException
+     *             if no method with the given signature could be found.
+     */
+    public static ProtectedFunctionMapper getMapForFunction(String fnQName,
+            final Class c, final String methodName, final Class[] args) {
+        java.lang.reflect.Method method;
+        ProtectedFunctionMapper funcMapper;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            funcMapper = (ProtectedFunctionMapper) AccessController
+                    .doPrivileged(new PrivilegedAction() {
+                        public Object run() {
+                            return new ProtectedFunctionMapper();
+                        }
+                    });
+
+            try {
+                method = (java.lang.reflect.Method) AccessController
+                        .doPrivileged(new PrivilegedExceptionAction() {
+
+                            public Object run() throws Exception {
+                                return c.getDeclaredMethod(methodName, args);
+                            }
+                        });
+            } catch (PrivilegedActionException ex) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + ex.getException().getMessage());
+            }
+        } else {
+            funcMapper = new ProtectedFunctionMapper();
+            try {
+                method = c.getDeclaredMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + e.getMessage());
+            }
+        }
+        funcMapper.theMethod = method;
+        return funcMapper;
+    }
+
+    /**
+     * Resolves the specified local name and prefix into a Java.lang.Method.
+     * Returns null if the prefix and local name are not found.
+     * 
+     * @param prefix
+     *            the prefix of the function
+     * @param localName
+     *            the short name of the function
+     * @return the result of the method mapping. Null means no entry found.
+     */
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.fnmap != null) {
+            return (Method) this.fnmap.get(prefix + ":" + localName);
+        }
+        return theMethod;
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java
new file mode 100644
index 0000000..098e703
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java
@@ -0,0 +1,76 @@
+/*
+ * 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.jasper.runtime;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspWriter;
+
+/**
+ * ServletResponseWrapper used by the JSP 'include' action.
+ *
+ * This wrapper response object is passed to RequestDispatcher.include(), so
+ * that the output of the included resource is appended to that of the
+ * including page.
+ *
+ * @author Pierre Delisle
+ */
+
+public class ServletResponseWrapperInclude extends HttpServletResponseWrapper {
+
+    /**
+     * PrintWriter which appends to the JspWriter of the including page.
+     */
+    private PrintWriter printWriter;
+
+    private JspWriter jspWriter;
+
+    public ServletResponseWrapperInclude(ServletResponse response, 
+					 JspWriter jspWriter) {
+	super((HttpServletResponse)response);
+	this.printWriter = new PrintWriter(jspWriter);
+	this.jspWriter = jspWriter;
+    }
+
+    /**
+     * Returns a wrapper around the JspWriter of the including page.
+     */
+    public PrintWriter getWriter() throws IOException {
+	return printWriter;
+    }
+
+    public ServletOutputStream getOutputStream() throws IOException {
+	throw new IllegalStateException();
+    }
+
+    /**
+     * Clears the output buffer of the JspWriter associated with the including
+     * page.
+     */
+    public void resetBuffer() {
+	try {
+	    jspWriter.clearBuffer();
+	} catch (IOException ioe) {
+	}
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java
new file mode 100644
index 0000000..74f2b64
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java
@@ -0,0 +1,191 @@
+/*
+ * 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.jasper.runtime;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+
+import org.apache.AnnotationProcessor;
+import org.apache.jasper.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Pool of tag handlers that can be reused.
+ *
+ * @author Jan Luehe
+ */
+public class TagHandlerPool {
+
+    private Tag[] handlers;
+
+    public static String OPTION_TAGPOOL="tagpoolClassName";
+    public static String OPTION_MAXSIZE="tagpoolMaxSize";
+
+    private Log log = LogFactory.getLog(TagHandlerPool.class);
+    
+    // index of next available tag handler
+    private int current;
+    protected AnnotationProcessor annotationProcessor = null;
+
+    public static TagHandlerPool getTagHandlerPool( ServletConfig config) {
+        TagHandlerPool result=null;
+
+        String tpClassName=getOption( config, OPTION_TAGPOOL, null);
+        if( tpClassName != null ) {
+            try {
+                Class c=Class.forName( tpClassName );
+                result=(TagHandlerPool)c.newInstance();
+            } catch (Exception e) {
+                e.printStackTrace();
+                result=null;
+            }
+        }
+        if( result==null ) result=new TagHandlerPool();
+        result.init(config);
+
+        return result;
+    }
+
+    protected void init( ServletConfig config ) {
+        int maxSize=-1;
+        String maxSizeS=getOption(config, OPTION_MAXSIZE, null);
+        if( maxSizeS != null ) {
+            try {
+                maxSize=Integer.parseInt(maxSizeS);
+            } catch( Exception ex) {
+                maxSize=-1;
+            }
+        }
+        if( maxSize <0  ) {
+            maxSize=Constants.MAX_POOL_SIZE;
+        }
+        this.handlers = new Tag[maxSize];
+        this.current = -1;
+        this.annotationProcessor = 
+            (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName());
+    }
+
+    /**
+     * Constructs a tag handler pool with the default capacity.
+     */
+    public TagHandlerPool() {
+	// Nothing - jasper generated servlets call the other constructor,
+        // this should be used in future + init .
+    }
+
+    /**
+     * Constructs a tag handler pool with the given capacity.
+     *
+     * @param capacity Tag handler pool capacity
+     * @deprecated Use static getTagHandlerPool
+     */
+    public TagHandlerPool(int capacity) {
+	this.handlers = new Tag[capacity];
+	this.current = -1;
+    }
+
+    /**
+     * Gets the next available tag handler from this tag handler pool,
+     * instantiating one if this tag handler pool is empty.
+     *
+     * @param handlerClass Tag handler class
+     *
+     * @return Reused or newly instantiated tag handler
+     *
+     * @throws JspException if a tag handler cannot be instantiated
+     */
+    public Tag get(Class handlerClass) throws JspException {
+	Tag handler = null;
+        synchronized( this ) {
+            if (current >= 0) {
+                handler = handlers[current--];
+                return handler;
+            }
+        }
+
+        // Out of sync block - there is no need for other threads to
+        // wait for us to construct a tag for this thread.
+        try {
+            Tag instance = (Tag) handlerClass.newInstance();
+            AnnotationHelper.postConstruct(annotationProcessor, instance);
+            return instance;
+        } catch (Exception e) {
+            throw new JspException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Adds the given tag handler to this tag handler pool, unless this tag
+     * handler pool has already reached its capacity, in which case the tag
+     * handler's release() method is called.
+     *
+     * @param handler Tag handler to add to this tag handler pool
+     */
+    public void reuse(Tag handler) {
+        synchronized( this ) {
+            if (current < (handlers.length - 1)) {
+                handlers[++current] = handler;
+                return;
+            }
+        }
+        // There is no need for other threads to wait for us to release
+        handler.release();
+        if (annotationProcessor != null) {
+            try {
+                AnnotationHelper.preDestroy(annotationProcessor, handler);
+            } catch (Exception e) {
+                log.warn("Error processing preDestroy on tag instance of " 
+                        + handler.getClass().getName(), e);
+            }
+        }
+    }
+
+    /**
+     * Calls the release() method of all available tag handlers in this tag
+     * handler pool.
+     */
+    public synchronized void release() {
+        for (int i = current; i >= 0; i--) {
+            handlers[i].release();
+            if (annotationProcessor != null) {
+                try {
+                    AnnotationHelper.preDestroy(annotationProcessor, handlers[i]);
+                } catch (Exception e) {
+                    log.warn("Error processing preDestroy on tag instance of " 
+                            + handlers[i].getClass().getName(), e);
+                }
+            }
+        }
+    }
+
+    protected static String getOption( ServletConfig config, String name, String defaultV) {
+        if( config == null ) return defaultV;
+
+        String value=config.getInitParameter(name);
+        if( value != null ) return value;
+        if( config.getServletContext() ==null )
+            return defaultV;
+        value=config.getServletContext().getInitParameter(name);
+        if( value!=null ) return value;
+        return defaultV;
+    }
+
+}
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java
new file mode 100644
index 0000000..6c7cfc3
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jasper.security;
+
+/**
+ * Static class used to preload java classes when using the
+ * Java SecurityManager so that the defineClassInPackage
+ * RuntimePermission does not trigger an AccessControlException.
+ *
+ * @author Jean-Francois Arcand
+ */
+
+public final class SecurityClassLoad {
+
+    private static org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityClassLoad.class );
+
+    public static void securityClassLoad(ClassLoader loader){
+
+        if( System.getSecurityManager() == null ){
+            return;
+        }
+
+        String basePackage = "org.apache.jasper.";
+        try {
+            loader.loadClass( basePackage +
+                "runtime.JspFactoryImpl$PrivilegedGetPageContext");
+            loader.loadClass( basePackage +
+                "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
+
+            loader.loadClass( basePackage +
+                "runtime.JspRuntimeLibrary");
+            loader.loadClass( basePackage +
+                "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
+            
+            loader.loadClass( basePackage +
+                "runtime.ServletResponseWrapperInclude");
+            loader.loadClass( basePackage +
+                "runtime.TagHandlerPool");
+            loader.loadClass( basePackage +
+                "runtime.JspFragmentHelper");
+
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$1");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$2"); 
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$3");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$4"); 
+
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$1");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$2");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$3");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$4");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$5");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$6");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$7");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$8");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$9");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$10");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$11");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$12");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$13");      
+
+            loader.loadClass( basePackage +
+                "runtime.JspContextWrapper");   
+
+            loader.loadClass( basePackage +
+                "servlet.JspServletWrapper");
+
+            loader.loadClass( basePackage +
+                "runtime.JspWriterImpl$1");
+        } catch (ClassNotFoundException ex) {
+            log.error("SecurityClassLoad", ex);
+        }
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java
new file mode 100644
index 0000000..22d0668
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java
@@ -0,0 +1,81 @@
+/*
+ * 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.jasper.security;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Util class for Security related operations.
+ *
+ * @author Jean-Francois Arcand
+ */
+
+public final class SecurityUtil{
+    
+    private static boolean packageDefinitionEnabled =  
+         System.getProperty("package.definition") == null ? false : true;
+    
+    /**
+     * Return the <code>SecurityManager</code> only if Security is enabled AND
+     * package protection mechanism is enabled.
+     */
+    public static boolean isPackageProtectionEnabled(){
+        if (packageDefinitionEnabled && Constants.IS_SECURITY_ENABLED){
+            return true;
+        }
+        return false;
+    }
+    
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    public static String filter(String message) {
+
+        if (message == null)
+            return (null);
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuffer result = new StringBuffer(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java
new file mode 100644
index 0000000..7a3b0f7
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java
@@ -0,0 +1,172 @@
+/*
+ * 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.jasper.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Class loader for loading servlet class files (corresponding to JSP files) 
+ * and tag handler class files (corresponding to tag files).
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Jean-Francois Arcand
+ */
+public class JasperLoader extends URLClassLoader {
+
+    private PermissionCollection permissionCollection;
+    private CodeSource codeSource;
+    private String className;
+    private ClassLoader parent;
+    private SecurityManager securityManager;
+
+    public JasperLoader(URL[] urls, ClassLoader parent,
+			PermissionCollection permissionCollection,
+			CodeSource codeSource) {
+	super(urls, parent);
+	this.permissionCollection = permissionCollection;
+	this.codeSource = codeSource;
+	this.parent = parent;
+	this.securityManager = System.getSecurityManager();
+    }
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class loadClass(String name) throws ClassNotFoundException {
+
+        return (loadClass(name, false));
+    }
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>            
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>      
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>      
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *                                     
+     * @param name Name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *                                     
+     * @exception ClassNotFoundException if the class was not found
+     */                                    
+    public Class loadClass(final String name, boolean resolve)
+        throws ClassNotFoundException {
+
+        Class clazz = null;                
+                                           
+        // (0) Check our previously loaded class cache
+        clazz = findLoadedClass(name);     
+        if (clazz != null) {               
+            if (resolve)                   
+                resolveClass(clazz);       
+            return (clazz);        
+        }                          
+                          
+        // (.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {     
+            int dot = name.lastIndexOf('.');
+            if (dot >= 0) {                
+                try {        
+                    // Do not call the security manager since by default, we grant that package.
+                    if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
+                        securityManager.checkPackageAccess(name.substring(0,dot));
+                    }
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    se.printStackTrace();
+                    throw new ClassNotFoundException(error);
+                }                          
+            }                              
+        }
+
+        if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
+            // Class is not in org.apache.jsp, therefore, have our
+            // parent load it
+            clazz = parent.loadClass(name);            
+	    if( resolve )
+		resolveClass(clazz);
+	    return clazz;
+	}
+
+	return findClass(name);
+    }
+
+    
+    /**
+     * Delegate to parent
+     * 
+     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
+     */
+    public InputStream getResourceAsStream(String name) {
+        InputStream is = parent.getResourceAsStream(name);
+        if (is == null) {
+            URL url = findResource(name);
+            if (url != null) {
+                try {
+                    is = url.openStream();
+                } catch (IOException e) {
+                    is = null;
+                }
+            }
+        }
+        return is;
+    }
+    
+    
+    /**
+     * Get the Permissions for a CodeSource.
+     *
+     * Since this ClassLoader is only used for a JSP page in
+     * a web application context, we just return our preset
+     * PermissionCollection for the web app context.
+     *
+     * @param codeSource Code source where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    public final PermissionCollection getPermissions(CodeSource codeSource) {
+        return permissionCollection;
+    }
+}
\ No newline at end of file
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java
new file mode 100644
index 0000000..d0f3249
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java
@@ -0,0 +1,441 @@
+/*
+ * 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.jasper.servlet;
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+
+/**
+ * Simple <code>ServletContext</code> implementation without
+ * HTTP-specific methods.
+ *
+ * @author Peter Rossbach (pr@webapp.de)
+ */
+
+public class JspCServletContext implements ServletContext {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Servlet context attributes.
+     */
+    protected Hashtable myAttributes;
+
+
+    /**
+     * The log writer we will write log messages to.
+     */
+    protected PrintWriter myLogWriter;
+
+
+    /**
+     * The base URL (document root) for this context.
+     */
+    protected URL myResourceBaseURL;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new instance of this ServletContext implementation.
+     *
+     * @param aLogWriter PrintWriter which is used for <code>log()</code> calls
+     * @param aResourceBaseURL Resource base URL
+     */
+    public JspCServletContext(PrintWriter aLogWriter, URL aResourceBaseURL) {
+
+        myAttributes = new Hashtable();
+        myLogWriter = aLogWriter;
+        myResourceBaseURL = aResourceBaseURL;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the specified context attribute, if any.
+     *
+     * @param name Name of the requested attribute
+     */
+    public Object getAttribute(String name) {
+
+        return (myAttributes.get(name));
+
+    }
+
+
+    /**
+     * Return an enumeration of context attribute names.
+     */
+    public Enumeration getAttributeNames() {
+
+        return (myAttributes.keys());
+
+    }
+
+
+    /**
+     * Return the servlet context for the specified path.
+     *
+     * @param uripath Server-relative path starting with '/'
+     */
+    public ServletContext getContext(String uripath) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the context path.
+     */
+    public String getContextPath() {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the specified context initialization parameter.
+     *
+     * @param name Name of the requested parameter
+     */
+    public String getInitParameter(String name) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return an enumeration of the names of context initialization
+     * parameters.
+     */
+    public Enumeration getInitParameterNames() {
+
+        return (new Vector().elements());
+
+    }
+
+
+    /**
+     * Return the Servlet API major version number.
+     */
+    public int getMajorVersion() {
+
+        return (2);
+
+    }
+
+
+    /**
+     * Return the MIME type for the specified filename.
+     *
+     * @param file Filename whose MIME type is requested
+     */
+    public String getMimeType(String file) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the Servlet API minor version number.
+     */
+    public int getMinorVersion() {
+
+        return (3);
+
+    }
+
+
+    /**
+     * Return a request dispatcher for the specified servlet name.
+     *
+     * @param name Name of the requested servlet
+     */
+    public RequestDispatcher getNamedDispatcher(String name) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the real path for the specified context-relative
+     * virtual path.
+     *
+     * @param path The context-relative virtual path to resolve
+     */
+    public String getRealPath(String path) {
+
+        if (!myResourceBaseURL.getProtocol().equals("file"))
+            return (null);
+        if (!path.startsWith("/"))
+            return (null);
+        try {
+            return
+                (getResource(path).getFile().replace('/', File.separatorChar));
+        } catch (Throwable t) {
+            return (null);
+        }
+
+    }
+            
+            
+    /**
+     * Return a request dispatcher for the specified context-relative path.
+     *
+     * @param path Context-relative path for which to acquire a dispatcher
+     */
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return a URL object of a resource that is mapped to the
+     * specified context-relative path.
+     *
+     * @param path Context-relative path of the desired resource
+     *
+     * @exception MalformedURLException if the resource path is
+     *  not properly formed
+     */
+    public URL getResource(String path) throws MalformedURLException {
+
+        if (!path.startsWith("/"))
+            throw new MalformedURLException("Path '" + path +
+                                            "' does not start with '/'");
+        URL url = new URL(myResourceBaseURL, path.substring(1));
+        InputStream is = null;
+        try {
+            is = url.openStream();
+        } catch (Throwable t) {
+            url = null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (Throwable t2) {
+                    // Ignore
+                }
+            }
+        }
+        return url;
+
+    }
+
+
+    /**
+     * Return an InputStream allowing access to the resource at the
+     * specified context-relative path.
+     *
+     * @param path Context-relative path of the desired resource
+     */
+    public InputStream getResourceAsStream(String path) {
+
+        try {
+            return (getResource(path).openStream());
+        } catch (Throwable t) {
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the set of resource paths for the "directory" at the
+     * specified context path.
+     *
+     * @param path Context-relative base path
+     */
+    public Set getResourcePaths(String path) {
+
+        Set thePaths = new HashSet();
+        if (!path.endsWith("/"))
+            path += "/";
+        String basePath = getRealPath(path);
+        if (basePath == null)
+            return (thePaths);
+        File theBaseDir = new File(basePath);
+        if (!theBaseDir.exists() || !theBaseDir.isDirectory())
+            return (thePaths);
+        String theFiles[] = theBaseDir.list();
+        for (int i = 0; i < theFiles.length; i++) {
+            File testFile = new File(basePath + File.separator + theFiles[i]);
+            if (testFile.isFile())
+                thePaths.add(path + theFiles[i]);
+            else if (testFile.isDirectory())
+                thePaths.add(path + theFiles[i] + "/");
+        }
+        return (thePaths);
+
+    }
+
+
+    /**
+     * Return descriptive information about this server.
+     */
+    public String getServerInfo() {
+
+        return ("JspCServletContext/1.0");
+
+    }
+
+
+    /**
+     * Return a null reference for the specified servlet name.
+     *
+     * @param name Name of the requested servlet
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    public Servlet getServlet(String name) throws ServletException {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the name of this servlet context.
+     */
+    public String getServletContextName() {
+
+        return (getServerInfo());
+
+    }
+
+
+    /**
+     * Return an empty enumeration of servlet names.
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    public Enumeration getServletNames() {
+
+        return (new Vector().elements());
+
+    }
+
+
+    /**
+     * Return an empty enumeration of servlets.
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    public Enumeration getServlets() {
+
+        return (new Vector().elements());
+
+    }
+
+
+    /**
+     * Log the specified message.
+     *
+     * @param message The message to be logged
+     */
+    public void log(String message) {
+
+        myLogWriter.println(message);
+
+    }
+
+
+    /**
+     * Log the specified message and exception.
+     *
+     * @param exception The exception to be logged
+     * @param message The message to be logged
+     *
+     * @deprecated Use log(String,Throwable) instead
+     */
+    public void log(Exception exception, String message) {
+
+        log(message, exception);
+
+    }
+
+
+    /**
+     * Log the specified message and exception.
+     *
+     * @param message The message to be logged
+     * @param exception The exception to be logged
+     */
+    public void log(String message, Throwable exception) {
+
+        myLogWriter.println(message);
+        exception.printStackTrace(myLogWriter);
+
+    }
+
+
+    /**
+     * Remove the specified context attribute.
+     *
+     * @param name Name of the attribute to remove
+     */
+    public void removeAttribute(String name) {
+
+        myAttributes.remove(name);
+
+    }
+
+
+    /**
+     * Set or replace the specified context attribute.
+     *
+     * @param name Name of the context attribute to set
+     * @param value Corresponding attribute value
+     */
+    public void setAttribute(String name, Object value) {
+
+        myAttributes.put(name, value);
+
+    }
+
+
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java
new file mode 100644
index 0000000..1c12003
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java
@@ -0,0 +1,347 @@
+/*
+ * 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.jasper.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.PeriodicEventListener;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.EmbeddedServletOptions;
+import org.apache.jasper.Options;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.security.SecurityUtil;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * The JSP engine (a.k.a Jasper).
+ *
+ * The servlet container is responsible for providing a
+ * URLClassLoader for the web application context Jasper
+ * is being used in. Jasper will try get the Tomcat
+ * ServletContext attribute for its ServletContext class
+ * loader, if that fails, it uses the parent class loader.
+ * In either case, it must be a URLClassLoader.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Remy Maucherat
+ * @author Kin-man Chung
+ * @author Glenn Nielsen
+ */
+public class JspServlet extends HttpServlet implements PeriodicEventListener {
+
+    // Logger
+    private Log log = LogFactory.getLog(JspServlet.class);
+
+    private ServletContext context;
+    private ServletConfig config;
+    private Options options;
+    private JspRuntimeContext rctxt;
+
+
+    /*
+     * Initializes this JspServlet.
+     */
+    public void init(ServletConfig config) throws ServletException {
+        
+        super.init(config);
+        this.config = config;
+        this.context = config.getServletContext();
+        
+        // Initialize the JSP Runtime Context
+        // Check for a custom Options implementation
+        String engineOptionsName = 
+            config.getInitParameter("engineOptionsClass");
+        if (engineOptionsName != null) {
+            // Instantiate the indicated Options implementation
+            try {
+                ClassLoader loader = Thread.currentThread()
+                        .getContextClassLoader();
+                Class engineOptionsClass = loader.loadClass(engineOptionsName);
+                Class[] ctorSig = { ServletConfig.class, ServletContext.class };
+                Constructor ctor = engineOptionsClass.getConstructor(ctorSig);
+                Object[] args = { config, context };
+                options = (Options) ctor.newInstance(args);
+            } catch (Throwable e) {
+                // Need to localize this.
+                log.warn("Failed to load engineOptionsClass", e);
+                // Use the default Options implementation
+                options = new EmbeddedServletOptions(config, context);
+            }
+        } else {
+            // Use the default Options implementation
+            options = new EmbeddedServletOptions(config, context);
+        }
+        rctxt = new JspRuntimeContext(context, options);
+        
+        if (log.isDebugEnabled()) {
+            log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
+                    options.getScratchDir().toString()));
+            log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets"));
+        }
+    }
+
+
+    /**
+     * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
+     * the number of JSPs that have been loaded into the webapp with which
+     * this JspServlet is associated.
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs that have been loaded into the webapp with
+     * which this JspServlet is associated
+     */
+    public int getJspCount() {
+        return this.rctxt.getJspCount();
+    }
+
+
+    /**
+     * Resets the JSP reload counter.
+     *
+     * @param count Value to which to reset the JSP reload counter
+     */
+    public void setJspReloadCount(int count) {
+        this.rctxt.setJspReloadCount(count);
+    }
+
+
+    /**
+     * Gets the number of JSPs that have been reloaded.
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that have been reloaded
+     */
+    public int getJspReloadCount() {
+        return this.rctxt.getJspReloadCount();
+    }
+
+
+    /**
+     * <p>Look for a <em>precompilation request</em> as described in
+     * Section 8.4.2 of the JSP 1.2 Specification.  <strong>WARNING</strong> -
+     * we cannot use <code>request.getParameter()</code> for this, because
+     * that will trigger parsing all of the request parameters, and not give
+     * a servlet the opportunity to call
+     * <code>request.setCharacterEncoding()</code> first.</p>
+     *
+     * @param request The servlet requset we are processing
+     *
+     * @exception ServletException if an invalid parameter value for the
+     *  <code>jsp_precompile</code> parameter name is specified
+     */
+    boolean preCompile(HttpServletRequest request) throws ServletException {
+
+        String queryString = request.getQueryString();
+        if (queryString == null) {
+            return (false);
+        }
+        int start = queryString.indexOf(Constants.PRECOMPILE);
+        if (start < 0) {
+            return (false);
+        }
+        queryString =
+            queryString.substring(start + Constants.PRECOMPILE.length());
+        if (queryString.length() == 0) {
+            return (true);             // ?jsp_precompile
+        }
+        if (queryString.startsWith("&")) {
+            return (true);             // ?jsp_precompile&foo=bar...
+        }
+        if (!queryString.startsWith("=")) {
+            return (false);            // part of some other name or value
+        }
+        int limit = queryString.length();
+        int ampersand = queryString.indexOf("&");
+        if (ampersand > 0) {
+            limit = ampersand;
+        }
+        String value = queryString.substring(1, limit);
+        if (value.equals("true")) {
+            return (true);             // ?jsp_precompile=true
+        } else if (value.equals("false")) {
+	    // Spec says if jsp_precompile=false, the request should not
+	    // be delivered to the JSP page; the easiest way to implement
+	    // this is to set the flag to true, and precompile the page anyway.
+	    // This still conforms to the spec, since it says the
+	    // precompilation request can be ignored.
+            return (true);             // ?jsp_precompile=false
+        } else {
+            throw new ServletException("Cannot have request parameter " +
+                                       Constants.PRECOMPILE + " set to " +
+                                       value);
+        }
+
+    }
+    
+
+    public void service (HttpServletRequest request, 
+    			 HttpServletResponse response)
+                throws ServletException, IOException {
+
+        String jspUri = null;
+
+        String jspFile = (String) request.getAttribute(Constants.JSP_FILE);
+        if (jspFile != null) {
+            // JSP is specified via <jsp-file> in <servlet> declaration
+            jspUri = jspFile;
+        } else {
+            /*
+             * Check to see if the requested JSP has been the target of a
+             * RequestDispatcher.include()
+             */
+            jspUri = (String) request.getAttribute(Constants.INC_SERVLET_PATH);
+            if (jspUri != null) {
+                /*
+		 * Requested JSP has been target of
+                 * RequestDispatcher.include(). Its path is assembled from the
+                 * relevant javax.servlet.include.* request attributes
+                 */
+                String pathInfo = (String) request.getAttribute(
+                                    "javax.servlet.include.path_info");
+                if (pathInfo != null) {
+                    jspUri += pathInfo;
+                }
+            } else {
+                /*
+                 * Requested JSP has not been the target of a 
+                 * RequestDispatcher.include(). Reconstruct its path from the
+                 * request's getServletPath() and getPathInfo()
+                 */
+                jspUri = request.getServletPath();
+                String pathInfo = request.getPathInfo();
+                if (pathInfo != null) {
+                    jspUri += pathInfo;
+                }
+            }
+        }
+
+        if (log.isDebugEnabled()) {	    
+            log.debug("JspEngine --> " + jspUri);
+            log.debug("\t     ServletPath: " + request.getServletPath());
+            log.debug("\t        PathInfo: " + request.getPathInfo());
+            log.debug("\t        RealPath: " + context.getRealPath(jspUri));
+            log.debug("\t      RequestURI: " + request.getRequestURI());
+            log.debug("\t     QueryString: " + request.getQueryString());
+            log.debug("\t  Request Params: ");
+            Enumeration e = request.getParameterNames();
+            while (e.hasMoreElements()) {
+                String name = (String) e.nextElement();
+                log.debug("\t\t " + name + " = " 
+                          + request.getParameter(name));
+            }
+        }
+
+        try {
+            boolean precompile = preCompile(request);
+            serviceJspFile(request, response, jspUri, null, precompile);
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (ServletException e) {
+            throw e;
+        } catch (IOException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ServletException(e);
+        }
+
+    }
+
+    public void destroy() {
+        if (log.isDebugEnabled()) {
+            log.debug("JspServlet.destroy()");
+        }
+
+        rctxt.destroy();
+    }
+
+
+    public void periodicEvent() {
+        rctxt.checkCompile();
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+    private void serviceJspFile(HttpServletRequest request,
+                                HttpServletResponse response, String jspUri,
+                                Throwable exception, boolean precompile)
+        throws ServletException, IOException {
+
+        JspServletWrapper wrapper =
+            (JspServletWrapper) rctxt.getWrapper(jspUri);
+        if (wrapper == null) {
+            synchronized(this) {
+                wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri);
+                if (wrapper == null) {
+                    // Check if the requested JSP page exists, to avoid
+                    // creating unnecessary directories and files.
+                    if (null == context.getResource(jspUri)) {
+                        String includeRequestUri = (String)
+                        request.getAttribute(
+                                "javax.servlet.include.request_uri");
+                        if (includeRequestUri != null) {
+                            // This file was included. Throw an exception as
+                            // a response.sendError() will be ignored
+                            String msg = Localizer.getMessage(
+                                    "jsp.error.file.not.found",jspUri);
+                            // Strictly, filtering this is an application
+                            // responsibility but just in case...
+                            throw new ServletException(
+                                    SecurityUtil.filter(msg));
+                        } else {
+                            try {
+                                response.sendError(
+                                        HttpServletResponse.SC_NOT_FOUND,
+                                        request.getRequestURI());
+                            } catch (IllegalStateException ise) {
+                                log.error(Localizer.getMessage(
+                                        "jsp.error.file.not.found",
+                                        jspUri));
+                            }
+                        }
+                        return;
+                    }
+                    boolean isErrorPage = exception != null;
+                    wrapper = new JspServletWrapper(config, options, jspUri,
+                                                    isErrorPage, rctxt);
+                    rctxt.addWrapper(jspUri,wrapper);
+                }
+            }
+        }
+
+        wrapper.service(request, response, precompile);
+
+    }
+
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java
new file mode 100644
index 0000000..48db423
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java
@@ -0,0 +1,527 @@
+/*
+ * 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.jasper.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.tagext.TagInfo;
+
+import org.apache.AnnotationProcessor;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.compiler.ErrorDispatcher;
+import org.apache.jasper.compiler.JavacErrorDetail;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.runtime.JspSourceDependent;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * The JSP engine (a.k.a Jasper).
+ *
+ * The servlet container is responsible for providing a
+ * URLClassLoader for the web application context Jasper
+ * is being used in. Jasper will try get the Tomcat
+ * ServletContext attribute for its ServletContext class
+ * loader, if that fails, it uses the parent class loader.
+ * In either case, it must be a URLClassLoader.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Remy Maucherat
+ * @author Kin-man Chung
+ * @author Glenn Nielsen
+ * @author Tim Fennell
+ */
+
+public class JspServletWrapper {
+
+    // Logger
+    private Log log = LogFactory.getLog(JspServletWrapper.class);
+
+    private Servlet theServlet;
+    private String jspUri;
+    private Class servletClass;
+    private Class tagHandlerClass;
+    private JspCompilationContext ctxt;
+    private long available = 0L;
+    private ServletConfig config;
+    private Options options;
+    private boolean firstTime = true;
+    private boolean reload = true;
+    private boolean isTagFile;
+    private int tripCount;
+    private JasperException compileException;
+    private long servletClassLastModifiedTime;
+    private long lastModificationTest = 0L;
+
+    /*
+     * JspServletWrapper for JSP pages.
+     */
+    public JspServletWrapper(ServletConfig config, Options options, String jspUri,
+                      boolean isErrorPage, JspRuntimeContext rctxt)
+            throws JasperException {
+
+	this.isTagFile = false;
+        this.config = config;
+        this.options = options;
+        this.jspUri = jspUri;
+        ctxt = new JspCompilationContext(jspUri, isErrorPage, options,
+					 config.getServletContext(),
+					 this, rctxt);
+    }
+
+    /*
+     * JspServletWrapper for tag files.
+     */
+    public JspServletWrapper(ServletContext servletContext,
+			     Options options,
+			     String tagFilePath,
+			     TagInfo tagInfo,
+			     JspRuntimeContext rctxt,
+			     URL tagFileJarUrl)
+	    throws JasperException {
+
+	this.isTagFile = true;
+        this.config = null;	// not used
+        this.options = options;
+	this.jspUri = tagFilePath;
+	this.tripCount = 0;
+        ctxt = new JspCompilationContext(jspUri, tagInfo, options,
+					 servletContext, this, rctxt,
+					 tagFileJarUrl);
+    }
+
+    public JspCompilationContext getJspEngineContext() {
+        return ctxt;
+    }
+
+    public void setReload(boolean reload) {
+        this.reload = reload;
+    }
+
+    public Servlet getServlet()
+        throws ServletException, IOException, FileNotFoundException
+    {
+        if (reload) {
+            synchronized (this) {
+                // Synchronizing on jsw enables simultaneous loading
+                // of different pages, but not the same page.
+                if (reload) {
+                    // This is to maintain the original protocol.
+                    destroy();
+                    
+                    Servlet servlet = null;
+                    
+                    try {
+                        servletClass = ctxt.load();
+                        servlet = (Servlet) servletClass.newInstance();
+                        AnnotationProcessor annotationProcessor = (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName());
+                        if (annotationProcessor != null) {
+                           annotationProcessor.processAnnotations(servlet);
+                           annotationProcessor.postConstruct(servlet);
+                        }
+                    } catch (IllegalAccessException e) {
+                        throw new JasperException(e);
+                    } catch (InstantiationException e) {
+                        throw new JasperException(e);
+                    } catch (Exception e) {
+                        throw new JasperException(e);
+                    }
+                    
+                    servlet.init(config);
+
+                    if (!firstTime) {
+                        ctxt.getRuntimeContext().incrementJspReloadCount();
+                    }
+
+                    theServlet = servlet;
+                    reload = false;
+                }
+            }    
+        }
+        return theServlet;
+    }
+
+    public ServletContext getServletContext() {
+        return config.getServletContext();
+    }
+
+    /**
+     * Sets the compilation exception for this JspServletWrapper.
+     *
+     * @param je The compilation exception
+     */
+    public void setCompilationException(JasperException je) {
+        this.compileException = je;
+    }
+
+    /**
+     * Sets the last-modified time of the servlet class file associated with
+     * this JspServletWrapper.
+     *
+     * @param lastModified Last-modified time of servlet class
+     */
+    public void setServletClassLastModifiedTime(long lastModified) {
+        if (this.servletClassLastModifiedTime < lastModified) {
+            synchronized (this) {
+                if (this.servletClassLastModifiedTime < lastModified) {
+                    this.servletClassLastModifiedTime = lastModified;
+                    reload = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Compile (if needed) and load a tag file
+     */
+    public Class loadTagFile() throws JasperException {
+
+        try {
+            if (ctxt.isRemoved()) {
+                throw new FileNotFoundException(jspUri);
+            }
+            if (options.getDevelopment() || firstTime ) {
+                synchronized (this) {
+                    firstTime = false;
+                    ctxt.compile();
+                }
+            } else {
+                if (compileException != null) {
+                    throw compileException;
+                }
+            }
+
+            if (reload) {
+                tagHandlerClass = ctxt.load();
+                reload = false;
+            }
+        } catch (FileNotFoundException ex) {
+            throw new JasperException(ex);
+	}
+
+	return tagHandlerClass;
+    }
+
+    /**
+     * Compile and load a prototype for the Tag file.  This is needed
+     * when compiling tag files with circular dependencies.  A prototpe
+     * (skeleton) with no dependencies on other other tag files is
+     * generated and compiled.
+     */
+    public Class loadTagFilePrototype() throws JasperException {
+
+	ctxt.setPrototypeMode(true);
+	try {
+	    return loadTagFile();
+	} finally {
+	    ctxt.setPrototypeMode(false);
+	}
+    }
+
+    /**
+     * Get a list of files that the current page has source dependency on.
+     */
+    public java.util.List getDependants() {
+        try {
+            Object target;
+            if (isTagFile) {
+                if (reload) {
+                    tagHandlerClass = ctxt.load();
+                    reload = false;
+                }
+                target = tagHandlerClass.newInstance();
+            } else {
+                target = getServlet();
+            }
+            if (target != null && target instanceof JspSourceDependent) {
+                return ((java.util.List) ((JspSourceDependent) target).getDependants());
+            }
+        } catch (Throwable ex) {
+        }
+        return null;
+    }
+
+    public boolean isTagFile() {
+	return this.isTagFile;
+    }
+
+    public int incTripCount() {
+	return tripCount++;
+    }
+
+    public int decTripCount() {
+	return tripCount--;
+    }
+
+    public void service(HttpServletRequest request, 
+                        HttpServletResponse response,
+                        boolean precompile)
+	    throws ServletException, IOException, FileNotFoundException {
+        
+        try {
+
+            if (ctxt.isRemoved()) {
+                throw new FileNotFoundException(jspUri);
+            }
+
+            if ((available > 0L) && (available < Long.MAX_VALUE)) {
+                if (available > System.currentTimeMillis()) {
+                    response.setDateHeader("Retry-After", available);
+                    response.sendError
+                        (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                         Localizer.getMessage("jsp.error.unavailable"));
+                    return;
+                } else {
+                    // Wait period has expired. Reset.
+                    available = 0;
+                }
+            }
+
+            /*
+             * (1) Compile
+             */
+            if (options.getDevelopment() || firstTime ) {
+                synchronized (this) {
+                    firstTime = false;
+
+                    // The following sets reload to true, if necessary
+                    ctxt.compile();
+                }
+            } else {
+                if (compileException != null) {
+                    // Throw cached compilation exception
+                    throw compileException;
+                }
+            }
+
+            /*
+             * (2) (Re)load servlet class file
+             */
+            getServlet();
+
+            // If a page is to be precompiled only, return.
+            if (precompile) {
+                return;
+            }
+
+        } catch (ServletException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (IOException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (IllegalStateException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (Exception ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw new JasperException(ex);
+            }
+        }
+
+        try {
+            
+            /*
+             * (3) Service request
+             */
+            if (theServlet instanceof SingleThreadModel) {
+               // sync on the wrapper so that the freshness
+               // of the page is determined right before servicing
+               synchronized (this) {
+                   theServlet.service(request, response);
+                }
+            } else {
+                theServlet.service(request, response);
+            }
+
+        } catch (UnavailableException ex) {
+            String includeRequestUri = (String)
+                request.getAttribute("javax.servlet.include.request_uri");
+            if (includeRequestUri != null) {
+                // This file was included. Throw an exception as
+                // a response.sendError() will be ignored by the
+                // servlet engine.
+                throw ex;
+            } else {
+                int unavailableSeconds = ex.getUnavailableSeconds();
+                if (unavailableSeconds <= 0) {
+                    unavailableSeconds = 60;        // Arbitrary default
+                }
+                available = System.currentTimeMillis() +
+                    (unavailableSeconds * 1000L);
+                response.sendError
+                    (HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
+                     ex.getMessage());
+            }
+        } catch (ServletException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (IOException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (IllegalStateException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw ex;
+            }
+        } catch (Exception ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            } else {
+                throw new JasperException(ex);
+            }
+        }
+    }
+
+    public void destroy() {
+        if (theServlet != null) {
+            theServlet.destroy();
+            AnnotationProcessor annotationProcessor = (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName());
+            if (annotationProcessor != null) {
+                try {
+                    annotationProcessor.preDestroy(theServlet);
+                } catch (Exception e) {
+                    // Log any exception, since it can't be passed along
+                    log.error(Localizer.getMessage("jsp.error.file.not.found",
+                           e.getMessage()), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return Returns the lastModificationTest.
+     */
+    public long getLastModificationTest() {
+        return lastModificationTest;
+    }
+    /**
+     * @param lastModificationTest The lastModificationTest to set.
+     */
+    public void setLastModificationTest(long lastModificationTest) {
+        this.lastModificationTest = lastModificationTest;
+    }
+
+    /**
+     * <p>Attempts to construct a JasperException that contains helpful information
+     * about what went wrong. Uses the JSP compiler system to translate the line
+     * number in the generated servlet that originated the exception to a line
+     * number in the JSP.  Then constructs an exception containing that
+     * information, and a snippet of the JSP to help debugging.
+     * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and
+     * http://www.tfenne.com/jasper/ for more details.
+     *</p>
+     *
+     * @param ex the exception that was the cause of the problem.
+     * @return a JasperException with more detailed information
+     */
+    protected JasperException handleJspException(Exception ex) {
+        try {
+            Throwable realException = ex;
+            if (ex instanceof ServletException) {
+                realException = ((ServletException) ex).getRootCause();
+            }
+
+            // First identify the stack frame in the trace that represents the JSP
+            StackTraceElement[] frames = realException.getStackTrace();
+            StackTraceElement jspFrame = null;
+
+            for (int i=0; i<frames.length; ++i) {
+                if ( frames[i].getClassName().equals(this.getServlet().getClass().getName()) ) {
+                    jspFrame = frames[i];
+                    break;
+                }
+            }
+
+            if (jspFrame == null) {
+                // If we couldn't find a frame in the stack trace corresponding
+                // to the generated servlet class, we can't really add anything
+                return new JasperException(ex);
+            }
+            else {
+                int javaLineNumber = jspFrame.getLineNumber();
+                JavacErrorDetail detail = ErrorDispatcher.createJavacError(
+                        jspFrame.getMethodName(),
+                        this.ctxt.getCompiler().getPageNodes(),
+                        null,
+                        javaLineNumber,
+                        ctxt);
+
+                // If the line number is less than one we couldn't find out
+                // where in the JSP things went wrong
+                int jspLineNumber = detail.getJspBeginLineNumber();
+                if (jspLineNumber < 1) {
+                    throw new JasperException(ex);
+                }
+
+                if (options.getDisplaySourceFragment()) {
+                    return new JasperException(Localizer.getMessage
+                            ("jsp.exception", detail.getJspFileName(),
+                                    "" + jspLineNumber) +
+                                    "\n\n" + detail.getJspExtract() +
+                                    "\n\nStacktrace:", ex);
+                    
+                } else {
+                    return new JasperException(Localizer.getMessage
+                            ("jsp.exception", detail.getJspFileName(),
+                                    "" + jspLineNumber), ex);
+                }
+            }
+        } catch (Exception je) {
+            // If anything goes wrong, just revert to the original behaviour
+            if (ex instanceof JasperException) {
+                return (JasperException) ex;
+            } else {
+                return new JasperException(ex);
+            }
+        }
+    }
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml
new file mode 100644
index 0000000..d678381
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<mbeans-descriptors>
+
+  <mbean         name="JspMonitor"
+          description="JSP Monitoring"
+               domain="Catalina"
+                group="Monitoring"
+                 type="org.apache.jasper.servlet.JspServlet">
+
+    <attribute   name="jspCount"
+          description="The number of JSPs that have been loaded into a webapp"
+                 type="int"/>
+
+    <attribute   name="jspReloadCount"
+          description="The number of JSPs that have been reloaded"
+                 type="int"/>
+
+  </mbean>
+
+</mbeans-descriptors>
\ No newline at end of file
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java
new file mode 100644
index 0000000..dfe0f2f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java
@@ -0,0 +1,328 @@
+/*
+ * 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.jasper.tagplugins.jstl;
+
+import org.apache.jasper.Constants;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * Util contains some often used consts, static methods and embedded class
+ * to support the JSTL tag plugin.
+ */
+
+public class Util {
+    
+    public static final String VALID_SCHEME_CHAR = 
+        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-";
+    
+    public static final String DEFAULT_ENCODING = 
+        "ISO-8859-1";
+    
+    public static final int HIGHEST_SPECIAL = '>';
+    
+    public static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
+    
+    static {
+        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
+        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
+        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
+        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
+        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
+    }
+    
+    /**
+     * Converts the given string description of a scope to the corresponding
+     * PageContext constant.
+     *
+     * The validity of the given scope has already been checked by the
+     * appropriate TLV.
+     *
+     * @param scope String description of scope
+     *
+     * @return PageContext constant corresponding to given scope description
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.Util  
+     */
+    public static int getScope(String scope){
+        int ret = PageContext.PAGE_SCOPE;
+        
+        if("request".equalsIgnoreCase(scope)){
+            ret = PageContext.REQUEST_SCOPE;
+        }else if("session".equalsIgnoreCase(scope)){
+            ret = PageContext.SESSION_SCOPE;
+        }else if("application".equalsIgnoreCase(scope)){
+            ret = PageContext.APPLICATION_SCOPE;
+        }
+        
+        return ret;
+    }
+    
+    /**
+     * Returns <tt>true</tt> if our current URL is absolute,
+     * <tt>false</tt> otherwise.
+     * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport
+     */
+    public static boolean isAbsoluteUrl(String url){
+        if(url == null){
+            return false;
+        }
+        
+        int colonPos = url.indexOf(":");
+        if(colonPos == -1){
+            return false;
+        }
+        
+        for(int i=0;i<colonPos;i++){
+            if(VALID_SCHEME_CHAR.indexOf(url.charAt(i)) == -1){
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Get the value associated with a content-type attribute.
+     * Syntax defined in RFC 2045, section 5.1.
+     * taken from org.apache.taglibs.standard.tag.common.core.Util
+     */
+    public static String getContentTypeAttribute(String input, String name) {
+        int begin;
+        int end;
+        int index = input.toUpperCase().indexOf(name.toUpperCase());
+        if (index == -1) return null;
+        index = index + name.length(); // positioned after the attribute name
+        index = input.indexOf('=', index); // positioned at the '='
+        if (index == -1) return null;
+        index += 1; // positioned after the '='
+        input = input.substring(index).trim();
+        
+        if (input.charAt(0) == '"') {
+            // attribute value is a quoted string
+            begin = 1;
+            end = input.indexOf('"', begin);
+            if (end == -1) return null;
+        } else {
+            begin = 0;
+            end = input.indexOf(';');
+            if (end == -1) end = input.indexOf(' ');
+            if (end == -1) end = input.length();
+        }
+        return input.substring(begin, end).trim();
+    }
+    
+    /**
+     * Strips a servlet session ID from <tt>url</tt>.  The session ID
+     * is encoded as a URL "path parameter" beginning with "jsessionid=".
+     * We thus remove anything we find between ";jsessionid=" (inclusive)
+     * and either EOS or a subsequent ';' (exclusive).
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport
+     */
+    public static String stripSession(String url) {
+        StringBuffer u = new StringBuffer(url);
+        int sessionStart;
+        while ((sessionStart = u.toString().indexOf(";" + Constants.SESSION_PARAMETER_NAME + "=")) != -1) {
+            int sessionEnd = u.toString().indexOf(";", sessionStart + 1);
+            if (sessionEnd == -1)
+                sessionEnd = u.toString().indexOf("?", sessionStart + 1);
+            if (sessionEnd == -1) 				// still
+                sessionEnd = u.length();
+            u.delete(sessionStart, sessionEnd);
+        }
+        return u.toString();
+    }
+    
+    
+    /**
+     * Performs the following substring replacements
+     * (to facilitate output to XML/HTML pages):
+     *
+     *    & -> &amp;
+     *    < -> &lt;
+     *    > -> &gt;
+     *    " -> &#034;
+     *    ' -> &#039;
+     *
+     * See also OutSupport.writeEscapedXml().
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.Util
+     */
+    public static String escapeXml(String buffer) {
+        int start = 0;
+        int length = buffer.length();
+        char[] arrayBuffer = buffer.toCharArray();
+        StringBuffer escapedBuffer = null;
+        
+        for (int i = 0; i < length; i++) {
+            char c = arrayBuffer[i];
+            if (c <= HIGHEST_SPECIAL) {
+                char[] escaped = specialCharactersRepresentation[c];
+                if (escaped != null) {
+                    // create StringBuffer to hold escaped xml string
+                    if (start == 0) {
+                        escapedBuffer = new StringBuffer(length + 5);
+                    }
+                    // add unescaped portion
+                    if (start < i) {
+                        escapedBuffer.append(arrayBuffer,start,i-start);
+                    }
+                    start = i + 1;
+                    // add escaped xml
+                    escapedBuffer.append(escaped);
+                }
+            }
+        }
+        // no xml escaping was necessary
+        if (start == 0) {
+            return buffer;
+        }
+        // add rest of unescaped portion
+        if (start < length) {
+            escapedBuffer.append(arrayBuffer,start,length-start);
+        }
+        return escapedBuffer.toString();
+    }
+    
+    /** Utility methods
+     * taken from org.apache.taglibs.standard.tag.common.core.UrlSupport
+     */
+    public static String resolveUrl(
+            String url, String context, PageContext pageContext)
+    throws JspException {
+        // don't touch absolute URLs
+        if (isAbsoluteUrl(url))
+            return url;
+        
+        // normalize relative URLs against a context root
+        HttpServletRequest request =
+            (HttpServletRequest) pageContext.getRequest();
+        if (context == null) {
+            if (url.startsWith("/"))
+                return (request.getContextPath() + url);
+            else
+                return url;
+        } else {
+            if (!context.startsWith("/") || !url.startsWith("/")) {
+                throw new JspTagException(
+                "In URL tags, when the \"context\" attribute is specified, values of both \"context\" and \"url\" must start with \"/\".");
+            }
+            if (context.equals("/")) {
+                // Don't produce string starting with '//', many
+                // browsers interpret this as host name, not as
+                // path on same host.
+                return url;
+            } else {
+                return (context + url);
+            }
+        }
+    }
+    
+    /** Wraps responses to allow us to retrieve results as Strings. 
+     * mainly taken from org.apache.taglibs.standard.tag.common.core.importSupport 
+     */
+    public static class ImportResponseWrapper extends HttpServletResponseWrapper{
+        
+        private StringWriter sw = new StringWriter();
+        private ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        private ServletOutputStream sos = new ServletOutputStream() {
+            public void write(int b) throws IOException {
+                bos.write(b);
+            }
+        };
+        private boolean isWriterUsed;
+        private boolean isStreamUsed;
+        private int status = 200;
+        private String charEncoding;
+        
+        public ImportResponseWrapper(HttpServletResponse arg0) {
+            super(arg0);
+            // TODO Auto-generated constructor stub
+        }
+        
+        public PrintWriter getWriter() {
+            if (isStreamUsed)
+                throw new IllegalStateException("Unexpected internal error during &lt;import&gt: " +
+                "Target servlet called getWriter(), then getOutputStream()");
+            isWriterUsed = true;
+            return new PrintWriter(sw);
+        }
+        
+        public ServletOutputStream getOutputStream() {
+            if (isWriterUsed)
+                throw new IllegalStateException("Unexpected internal error during &lt;import&gt: " +
+                "Target servlet called getOutputStream(), then getWriter()");
+            isStreamUsed = true;
+            return sos;
+        }
+        
+        /** Has no effect. */
+        public void setContentType(String x) {
+            // ignore
+        }
+        
+        /** Has no effect. */
+        public void setLocale(Locale x) {
+            // ignore
+        }
+        
+        public void setStatus(int status) {
+            this.status = status;
+        }
+        
+        public int getStatus() {
+            return status;
+        }
+        
+        public String getCharEncoding(){
+            return this.charEncoding;
+        }
+        
+        public void setCharEncoding(String ce){
+            this.charEncoding = ce;
+        }
+        
+        public String getString() throws UnsupportedEncodingException {
+            if (isWriterUsed)
+                return sw.toString();
+            else if (isStreamUsed) {
+                if (this.charEncoding != null && !this.charEncoding.equals(""))
+                    return bos.toString(charEncoding);
+                else
+                    return bos.toString("ISO-8859-1");
+            } else
+                return "";		// target didn't write anything
+        }
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java
new file mode 100644
index 0000000..c7d154a
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Catch implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flag for the existence of the var attribute
+        boolean hasVar = ctxt.isAttributeSpecified("var");
+        
+        //temp name for exception and caught
+        String exceptionName = ctxt.getTemporaryVariableName();
+        String caughtName = ctxt.getTemporaryVariableName();
+        
+        //main part to generate code
+        ctxt.generateJavaSource("boolean " + caughtName + " = false;");
+        ctxt.generateJavaSource("try{");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+        
+        //do catch
+        ctxt.generateJavaSource("catch(Throwable " + exceptionName + "){");
+        
+        //if the var specified, the exception object should 
+        //be set to the attribute "var" defines in page scope 
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("    pageContext.setAttribute(\"" + strVar + "\", " 
+                    + exceptionName + ", PageContext.PAGE_SCOPE);");
+        }
+        
+        //whenever there's exception caught, 
+        //the flag caught should be set true;
+        ctxt.generateJavaSource("    " + caughtName + " = true;");
+        ctxt.generateJavaSource("}");
+        
+        //do finally
+        ctxt.generateJavaSource("finally{");
+        
+        //if var specified, the attribute it defines 
+        //in page scope should be removed
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("    if(!" + caughtName + "){");
+            ctxt.generateJavaSource("        pageContext.removeAttribute(\"" + strVar + "\", PageContext.PAGE_SCOPE);");
+            ctxt.generateJavaSource("    }");
+        }
+        
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java
new file mode 100644
index 0000000..7622cee
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java
@@ -0,0 +1,34 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.*;
+
+public final class Choose implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        // Not much to do here, much of the work will be done in the
+        // containing tags, <c:when> and <c:otherwise>.
+        
+        ctxt.generateBody();
+        // See comments in When.java for the reason "}" is generated here.
+        ctxt.generateJavaSource("}");
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java
new file mode 100644
index 0000000..49c9a3f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java
@@ -0,0 +1,344 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.*;
+
+public final class ForEach implements TagPlugin {
+    
+    private boolean hasVar, hasBegin, hasEnd, hasStep;
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        String index = null;
+        
+        boolean hasVarStatus = ctxt.isAttributeSpecified("varStatus");
+        if (hasVarStatus) {
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasBegin = ctxt.isAttributeSpecified("begin");
+        hasEnd = ctxt.isAttributeSpecified("end");
+        hasStep = ctxt.isAttributeSpecified("step");
+        
+        boolean hasItems = ctxt.isAttributeSpecified("items");
+        if (hasItems) {
+            doCollection(ctxt);
+            return;
+        }
+        
+        // We must have a begin and end attributes
+        index = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("for (int " + index + " = ");
+        ctxt.generateAttribute("begin");
+        ctxt.generateJavaSource("; " + index + " <= ");
+        ctxt.generateAttribute("end");
+        if (hasStep) {
+            ctxt.generateJavaSource("; " + index + "+=");
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(") {");
+        }
+        else {
+            ctxt.generateJavaSource("; " + index + "++) {");
+        }
+        
+        // If var is specified and the body contains an EL, then sycn
+        // the var attribute
+        if (hasVar /* && ctxt.hasEL() */) {
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", String.valueOf(" + index + "));");
+        }
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+    }
+    
+    /**
+     * Generate codes for Collections
+     * The pseudo code is:
+     */
+    private void doCollection(TagPluginContext ctxt) {
+        
+        ctxt.generateImport("java.util.*");
+        generateIterators(ctxt);
+        
+        String itemsV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("Object " + itemsV + "= ");
+        ctxt.generateAttribute("items");
+        ctxt.generateJavaSource(";");
+        
+        String indexV=null, beginV=null, endV=null, stepV=null;
+        if (hasBegin) {
+            beginV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + beginV + " = ");
+            ctxt.generateAttribute("begin");
+            ctxt.generateJavaSource(";");
+        }
+        if (hasEnd) {
+            indexV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + indexV + " = 0;");
+            endV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + endV + " = ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource(";");
+        }
+        if (hasStep) {
+            stepV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + stepV + " = ");
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(";");
+        }
+        
+        String iterV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("Iterator " + iterV + " = null;");
+        // Object[]
+        ctxt.generateJavaSource("if (" + itemsV + " instanceof Object[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((Object[])" + itemsV + ");");
+        // boolean[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof boolean[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((boolean[])" + itemsV + ");");
+        // byte[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof byte[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((byte[])" + itemsV + ");");
+        // char[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof char[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((char[])" + itemsV + ");");
+        // short[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof short[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((short[])" + itemsV + ");");
+        // int[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof int[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((int[])" + itemsV + ");");
+        // long[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof long[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((long[])" + itemsV + ");");
+        // float[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof float[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((float[])" + itemsV + ");");
+        // double[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof double[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((double[])" + itemsV + ");");
+        
+        // Collection
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Collection)");
+        ctxt.generateJavaSource(iterV + "=((Collection)" + itemsV + ").iterator();");
+        
+        // Iterator
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Iterator)");
+        ctxt.generateJavaSource(iterV + "=(Iterator)" + itemsV + ";");
+        
+        // Enumeration
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Enumeration)");
+        ctxt.generateJavaSource(iterV + "=toIterator((Enumeration)" + itemsV + ");");
+        
+        // Map
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Map)");
+        ctxt.generateJavaSource(iterV + "=((Map)" + itemsV + ").entrySet().iterator();");
+        
+        if (hasBegin) {
+            String tV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("for (int " + tV + "=" + beginV + ";" +
+                    tV + ">0 && " + iterV + ".hasNext(); " +
+                    tV + "--)");
+            ctxt.generateJavaSource(iterV + ".next();");
+        }
+        
+        ctxt.generateJavaSource("while (" + iterV + ".hasNext()){");
+        if (hasVar) {
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", " + iterV + ".next());");
+        }
+        
+        ctxt.generateBody();
+        
+        if (hasStep) {
+            String tV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("for (int " + tV + "=" + stepV + "-1;" +
+                    tV + ">0 && " + iterV + ".hasNext(); " +
+                    tV + "--)");
+            ctxt.generateJavaSource(iterV + ".next();");
+        }
+        if (hasEnd) {
+            if (hasStep) {
+                ctxt.generateJavaSource(indexV + "+=" + stepV + ";");
+            }
+            else {
+                ctxt.generateJavaSource(indexV + "++;");
+            }
+            if (hasBegin) {
+                ctxt.generateJavaSource("if(" + beginV + "+" + indexV +
+                        ">"+ endV + ")");
+            }
+            else {
+                ctxt.generateJavaSource("if(" + indexV + ">" + endV + ")");
+            }
+            ctxt.generateJavaSource("break;");
+        }
+        ctxt.generateJavaSource("}");	// while
+    }
+    
+    /**
+     * Generate iterators for data types supported in items
+     */
+    private void generateIterators(TagPluginContext ctxt) {
+        
+        // Object[]
+        ctxt.generateDeclaration("ObjectArrayIterator", 
+                "private Iterator toIterator(final Object[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return a[index++];}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // boolean[]
+        ctxt.generateDeclaration("booleanArrayIterator", 
+                "private Iterator toIterator(final boolean[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Boolean(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // byte[]
+        ctxt.generateDeclaration("byteArrayIterator", 
+                "private Iterator toIterator(final byte[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Byte(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // char[]
+        ctxt.generateDeclaration("charArrayIterator", 
+                "private Iterator toIterator(final char[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Character(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // short[]
+        ctxt.generateDeclaration("shortArrayIterator", 
+                "private Iterator toIterator(final short[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Short(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // int[]
+        ctxt.generateDeclaration("intArrayIterator", 
+                "private Iterator toIterator(final int[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Integer(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // long[]
+        ctxt.generateDeclaration("longArrayIterator", 
+                "private Iterator toIterator(final long[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Long(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // float[]
+        ctxt.generateDeclaration("floatArrayIterator",
+                "private Iterator toIterator(final float[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Float(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // double[]
+        ctxt.generateDeclaration("doubleArrayIterator",
+                "private Iterator toIterator(final double[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Double(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // Enumeration
+        ctxt.generateDeclaration("enumIterator",
+                "private Iterator toIterator(final Enumeration e){\n" +
+                "  return (new Iterator() {\n" +
+                "    public boolean hasNext() {\n" +
+                "      return e.hasMoreElements();}\n" +
+                "    public Object next() {\n" +
+                "      return e.nextElement();}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java
new file mode 100644
index 0000000..c72f0ad
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java
@@ -0,0 +1,119 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class ForTokens implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        boolean hasVar, hasVarStatus, hasBegin, hasEnd, hasStep;
+        
+        //init the flags
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasVarStatus = ctxt.isAttributeSpecified("varStatus");
+        hasBegin = ctxt.isAttributeSpecified("begin");
+        hasEnd = ctxt.isAttributeSpecified("end");
+        hasStep = ctxt.isAttributeSpecified("step");
+        
+        if(hasVarStatus){
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        //define all the temp variables' names
+        String itemsName = ctxt.getTemporaryVariableName();
+        String delimsName = ctxt.getTemporaryVariableName();
+        String stName = ctxt.getTemporaryVariableName();
+        String beginName = ctxt.getTemporaryVariableName();
+        String endName  = ctxt.getTemporaryVariableName();
+        String stepName = ctxt.getTemporaryVariableName();
+        String index = ctxt.getTemporaryVariableName();
+        String temp  = ctxt.getTemporaryVariableName();
+        String tokensCountName = ctxt.getTemporaryVariableName();
+        
+        //get the value of the "items" attribute 
+        ctxt.generateJavaSource("String " + itemsName + " = (String)");
+        ctxt.generateAttribute("items");
+        ctxt.generateJavaSource(";");
+        
+        //get the value of the "delim" attribute
+        ctxt.generateJavaSource("String " + delimsName + " = (String)");
+        ctxt.generateAttribute("delims");
+        ctxt.generateJavaSource(";");
+        
+        //new a StringTokenizer Object according to the "items" and the "delim"
+        ctxt.generateJavaSource("java.util.StringTokenizer " + stName + " = " +
+                "new java.util.StringTokenizer(" + itemsName + ", " + delimsName + ");");
+        
+        //if "begin" specified, move the token to the "begin" place
+        //and record the begin index. default begin place is 0.
+        ctxt.generateJavaSource("int " + tokensCountName + " = " + stName + ".countTokens();");
+        if(hasBegin){
+            ctxt.generateJavaSource("int " + beginName + " = "  );
+            ctxt.generateAttribute("begin");
+            ctxt.generateJavaSource(";");
+            ctxt.generateJavaSource("for(int " + index + " = 0; " + index + " < " + beginName + " && " + stName + ".hasMoreTokens(); " + index + "++, " + stName + ".nextToken()){}");
+        }else{
+            ctxt.generateJavaSource("int " + beginName + " = 0;");
+        }
+        
+        //when "end" is specified, if the "end" is more than the last index,
+        //record the end place as the last index, otherwise, record it as "end";
+        //default end place is the last index 
+        if(hasEnd){
+            ctxt.generateJavaSource("int " + endName + " = 0;"  );
+            ctxt.generateJavaSource("if((" + tokensCountName + " - 1) < ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource("){");
+            ctxt.generateJavaSource("    " + endName + " = " + tokensCountName + " - 1;");
+            ctxt.generateJavaSource("}else{");
+            ctxt.generateJavaSource("    " + endName + " = ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource(";}");
+        }else{
+            ctxt.generateJavaSource("int " + endName + " = " + tokensCountName + " - 1;");
+        }
+        
+        //get the step value from "step" if specified.
+        //default step value is 1.
+        if(hasStep){
+            ctxt.generateJavaSource("int " + stepName + " = "  );
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(";");
+        }else{
+            ctxt.generateJavaSource("int " + stepName + " = 1;");
+        }
+        
+        //the loop
+        ctxt.generateJavaSource("for(int " + index + " = " + beginName + "; " + index + " <= " + endName + "; " + index + "++){");
+        ctxt.generateJavaSource("    String " + temp + " = " + stName + ".nextToken();");
+        ctxt.generateJavaSource("    if(((" + index + " - " + beginName + ") % " + stepName + ") == 0){");
+        //if var specified, put the current token into the attribute "var" defines.
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("        pageContext.setAttribute(\"" + strVar + "\", " + temp + ");");
+        }
+        ctxt.generateBody();
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java
new file mode 100644
index 0000000..5f5a6df
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java
@@ -0,0 +1,50 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.*;
+
+public final class If implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        String condV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("boolean " + condV + "=");
+        ctxt.generateAttribute("test");
+        ctxt.generateJavaSource(";");
+        if (ctxt.isAttributeSpecified("var")) {
+            String scope = "PageContext.PAGE_SCOPE";
+            if (ctxt.isAttributeSpecified("scope")) {
+                String scopeStr = ctxt.getConstantAttribute("scope");
+                if ("request".equals(scopeStr)) {
+                    scope = "PageContext.REQUEST_SCOPE";
+                } else if ("session".equals(scopeStr)) {
+                    scope = "PageContext.SESSION_SCOPE";
+                } else if ("application".equals(scopeStr)) {
+                    scope = "PageContext.APPLICATION_SCOPE";
+                }
+            }
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", new Boolean(" + condV + ")," + scope + ");");
+        }
+        ctxt.generateJavaSource("if (" + condV + "){");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java
new file mode 100644
index 0000000..0803429
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java
@@ -0,0 +1,382 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Import implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        boolean hasContext, hasVar, hasScope, hasVarReader, hasCharEncoding;
+        
+        //flags
+        hasContext  = ctxt.isAttributeSpecified("context");
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        hasVarReader = ctxt.isAttributeSpecified("varReader");
+        hasCharEncoding = ctxt.isAttributeSpecified("charEncoding");
+        
+        //variables' names
+        String urlName = ctxt.getTemporaryVariableName();           
+        String contextName = ctxt.getTemporaryVariableName();       
+        String iauName = ctxt.getTemporaryVariableName();           // is absolute url
+        String urlObjName = ctxt.getTemporaryVariableName();        //URL object
+        String ucName = ctxt.getTemporaryVariableName();            //URLConnection
+        String inputStreamName = ctxt.getTemporaryVariableName();   
+        String tempReaderName = ctxt.getTemporaryVariableName();
+        String tempReaderName2 = ctxt.getTemporaryVariableName();
+        String charSetName = ctxt.getTemporaryVariableName();
+        String charEncodingName = ctxt.getTemporaryVariableName();
+        String contentTypeName = ctxt.getTemporaryVariableName();
+        String varReaderName = ctxt.getTemporaryVariableName();
+        String servletContextName = ctxt.getTemporaryVariableName();
+        String servletPathName = ctxt.getTemporaryVariableName();
+        String requestDispatcherName = ctxt.getTemporaryVariableName();
+        String irwName = ctxt.getTemporaryVariableName();           //ImportResponseWrapper name
+        String brName = ctxt.getTemporaryVariableName();            //BufferedReader name
+        String sbName = ctxt.getTemporaryVariableName();            //StringBuffer name
+        String tempStringName = ctxt.getTemporaryVariableName();
+        
+        //is absolute url
+        ctxt.generateJavaSource("boolean " + iauName + ";");
+        
+        //get the url value
+        ctxt.generateJavaSource("String " + urlName + " = ");
+        ctxt.generateAttribute("url");
+        ctxt.generateJavaSource(";");
+        
+        //validate the url
+        ctxt.generateJavaSource("if(" + urlName + " == null || " + urlName + ".equals(\"\")){");
+        ctxt.generateJavaSource("    throw new JspTagException(\"The \\\"url\\\" attribute " +
+        "illegally evaluated to \\\"null\\\" or \\\"\\\" in &lt;import&gt;\");");
+        ctxt.generateJavaSource("}");
+        
+        //initialize the is_absolute_url
+        ctxt.generateJavaSource(iauName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + urlName + ");");
+        
+        //validate the context
+        if(hasContext){
+            
+            ctxt.generateJavaSource("String " + contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+            
+            ctxt.generateJavaSource("if((!" + contextName + ".startsWith(\"/\")) " +
+                    "|| (!" + urlName + ".startsWith(\"/\"))){");
+            ctxt.generateJavaSource("    throw new JspTagException" +
+                    "(\"In URL tags, when the \\\"context\\\" attribute is specified, " +
+            "values of both \\\"context\\\" and \\\"url\\\" must start with \\\"/\\\".\");");
+            ctxt.generateJavaSource("}");
+            
+        }
+        
+        //define charset
+        ctxt.generateJavaSource("String " + charSetName + " = null;");
+        
+        //if the charEncoding attribute is specified
+        if(hasCharEncoding){
+            
+            //initialize the charEncoding
+            ctxt.generateJavaSource("String " + charEncodingName + " = ");
+            ctxt.generateAttribute("charEncoding");
+            ctxt.generateJavaSource(";");
+            
+            //assign appropriate value tp the charset
+            ctxt.generateJavaSource("if(null != " + charEncodingName + " " +
+                    "&& !" + charEncodingName + ".equals(\"\")){");
+            ctxt.generateJavaSource("    " + charSetName + " = " 
+                    + charEncodingName + ";");
+            ctxt.generateJavaSource("}");
+        }
+        
+        //reshape the url string
+        ctxt.generateJavaSource("if(!"+iauName+"){");
+        ctxt.generateJavaSource("    if(!" + urlName + ".startsWith(\"/\")){");
+        ctxt.generateJavaSource("        String " + servletPathName + " = " +
+        "((HttpServletRequest)pageContext.getRequest()).getServletPath();");
+        ctxt.generateJavaSource("        " + urlName + " = " 
+                + servletPathName + ".substring(0," + servletPathName + ".lastIndexOf('/')) + '/' + " + urlName + ";");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");
+        
+        //if the varReader attribute specified
+        if(hasVarReader){
+            
+            //get the String value of varReader
+            ctxt.generateJavaSource("String " + varReaderName + " = ");
+            ctxt.generateAttribute("varReader");
+            ctxt.generateJavaSource(";");
+            
+            //if the url is absolute url
+            ctxt.generateJavaSource("if(" + iauName + "){");
+            
+            //get the content of the target
+            ctxt.generateJavaSource("    java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");");
+            ctxt.generateJavaSource("    java.net.URLConnection " + ucName + " = " 
+                    + urlObjName + ".openConnection();");
+            ctxt.generateJavaSource("    java.io.InputStream " + inputStreamName + " = " 
+                    + ucName + ".getInputStream();");
+            
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        String " + contentTypeName + " = " 
+                    + ucName + ".getContentType();");
+            ctxt.generateJavaSource("        if(null != " + contentTypeName + "){");
+            ctxt.generateJavaSource("            " + charSetName + " = " +
+                    "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+            ctxt.generateJavaSource("            if(" + charSetName + " == null) " 
+                    + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            if(!hasCharEncoding){
+                ctxt.generateJavaSource("    String " + contentTypeName + " = " + ucName + ".getContentType();");
+            }
+            
+            //define the Reader
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = null;");
+            
+            //initialize the Reader object
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", " + charSetName + ");");
+            ctxt.generateJavaSource("    }catch(Exception ex){");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response
+            ctxt.generateJavaSource("    if(" + ucName + " instanceof java.net.HttpURLConnection){");
+            ctxt.generateJavaSource("        int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();");
+            ctxt.generateJavaSource("        if(status < 200 || status > 299){");
+            ctxt.generateJavaSource("            throw new JspTagException(status + \" \" + " + urlName + ");");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            //set attribute in the page context scope
+            ctxt.generateJavaSource("    pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");");
+            
+            //if the url is relative
+            ctxt.generateJavaSource("}else{");
+            
+            //if the url is relative, http request is needed
+            ctxt.generateJavaSource("    if (!(pageContext.getRequest() instanceof HttpServletRequest  " +
+            "&& pageContext.getResponse() instanceof HttpServletResponse)){");
+            ctxt.generateJavaSource("        throw new JspTagException(\"Relative &lt;import&gt; from non-HTTP request not allowed\");");
+            ctxt.generateJavaSource("    }");
+            
+            //get the servlet context of the context defined in the context attribute
+            ctxt.generateJavaSource("    ServletContext " + servletContextName + " = null;");
+            if(hasContext){
+                ctxt.generateJavaSource("    if(null != " + contextName + "){");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" );
+                ctxt.generateJavaSource("    }else{");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext();");
+                ctxt.generateJavaSource("    }");
+            }else{
+                ctxt.generateJavaSource("    " + servletContextName + " = pageContext.getServletContext();");
+            }
+            
+            //
+            ctxt.generateJavaSource("    if(" + servletContextName + " == null){");
+            if(hasContext){
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \"+" + contextName + "+\" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }else{
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for  URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }
+            ctxt.generateJavaSource("    }");
+            
+            //get the request dispatcher
+            ctxt.generateJavaSource("    RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            ctxt.generateJavaSource("    if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            
+            //initialize a ImportResponseWrapper to include the resource
+            ctxt.generateJavaSource("    org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("    " + irwName + ".setCharEncoding(" + charSetName + ");");
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");");
+            ctxt.generateJavaSource("    }catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(RuntimeException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(ServletException ex){");
+            ctxt.generateJavaSource("        Throwable rc = ex.getRootCause();");
+            ctxt.generateJavaSource("        if (rc == null)");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        else");
+            ctxt.generateJavaSource("            throw new JspException(rc);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response status
+            ctxt.generateJavaSource("    if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){");
+            ctxt.generateJavaSource("        throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+            ctxt.generateJavaSource("    }");
+            
+            //push in the page context
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = new java.io.StringReader(" + irwName + ".getString());");
+            ctxt.generateJavaSource("    pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");");
+            
+            ctxt.generateJavaSource("}");
+            
+            //execute the body action
+            ctxt.generateBody();
+            
+            //close the reader
+            ctxt.generateJavaSource("java.io.Reader " + tempReaderName2 + " = (java.io.Reader)pageContext.getAttribute(" + varReaderName + ");");
+            ctxt.generateJavaSource("if(" + tempReaderName2 + " != null) " + tempReaderName2 + ".close();");
+            ctxt.generateJavaSource("pageContext.removeAttribute(" + varReaderName + ",1);");
+        }
+        
+        //if the varReader is not specified 
+        else{
+            
+            ctxt.generateJavaSource("pageContext.setAttribute(\"url_without_param\"," + urlName + ");");
+            ctxt.generateBody();
+            ctxt.generateJavaSource(urlName + " = (String)pageContext.getAttribute(\"url_without_param\");");
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");");
+            String strScope = "page";
+            if(hasScope){
+                strScope = ctxt.getConstantAttribute("scope");
+            }
+            int iScope = Util.getScope(strScope);
+            
+            ctxt.generateJavaSource("String " + tempStringName + " = null;");
+            
+            ctxt.generateJavaSource("if(" + iauName + "){");
+            
+            //get the content of the target
+            ctxt.generateJavaSource("    java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");");
+            ctxt.generateJavaSource("    java.net.URLConnection " + ucName + " = " + urlObjName + ".openConnection();");
+            ctxt.generateJavaSource("    java.io.InputStream " + inputStreamName + " = " + ucName + ".getInputStream();");
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = null;");
+            
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        String " + contentTypeName + " = " 
+                    + ucName + ".getContentType();");
+            ctxt.generateJavaSource("        if(null != " + contentTypeName + "){");
+            ctxt.generateJavaSource("            " + charSetName + " = " +
+                    "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+            ctxt.generateJavaSource("            if(" + charSetName + " == null) " 
+                    + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + "," + charSetName + ");");
+            ctxt.generateJavaSource("    }catch(Exception ex){");
+            //ctxt.generateJavaSource("        throw new JspTagException(ex.toString());");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ",org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response
+            ctxt.generateJavaSource("    if(" + ucName + " instanceof java.net.HttpURLConnection){");
+            ctxt.generateJavaSource("        int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();");
+            ctxt.generateJavaSource("        if(status < 200 || status > 299){");
+            ctxt.generateJavaSource("            throw new JspTagException(status + \" \" + " + urlName + ");");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    java.io.BufferedReader " + brName + " =  new java.io.BufferedReader(" + tempReaderName + ");");
+            ctxt.generateJavaSource("    StringBuffer " + sbName + " = new StringBuffer();");
+            String index = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("    int " + index + ";");
+            ctxt.generateJavaSource("    while(("+index+" = "+brName+".read()) != -1) "+sbName+".append((char)"+index+");");
+            ctxt.generateJavaSource("    " + tempStringName + " = " +sbName + ".toString();");
+            
+            ctxt.generateJavaSource("}else{");
+            
+            //if the url is relative, http request is needed.
+            ctxt.generateJavaSource("    if (!(pageContext.getRequest() instanceof HttpServletRequest  " +
+            "&& pageContext.getResponse() instanceof HttpServletResponse)){");
+            ctxt.generateJavaSource("        throw new JspTagException(\"Relative &lt;import&gt; from non-HTTP request not allowed\");");
+            ctxt.generateJavaSource("    }");
+            
+            //get the servlet context of the context defined in the context attribute
+            ctxt.generateJavaSource("    ServletContext " + servletContextName + " = null;");
+            if(hasContext){
+                ctxt.generateJavaSource("    if(null != " + contextName + "){");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" );
+                ctxt.generateJavaSource("    }else{");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext();");
+                ctxt.generateJavaSource("    }");
+            }else{
+                ctxt.generateJavaSource("    " + servletContextName + " = pageContext.getServletContext();");
+            }
+            
+            //
+            ctxt.generateJavaSource("    if(" + servletContextName + " == null){");
+            if(hasContext){
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \" +" + contextName + "+ \" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }else{
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }
+            ctxt.generateJavaSource("    }");
+            
+            //get the request dispatcher
+            ctxt.generateJavaSource("    RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            ctxt.generateJavaSource("    if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            
+            //initialize a ImportResponseWrapper to include the resource
+            ctxt.generateJavaSource("    org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("    " + irwName + ".setCharEncoding(" + charSetName + ");");
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");");
+            ctxt.generateJavaSource("    }catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(RuntimeException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(ServletException ex){");
+            ctxt.generateJavaSource("        Throwable rc = ex.getRootCause();");
+            ctxt.generateJavaSource("        if (rc == null)");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        else");
+            ctxt.generateJavaSource("            throw new JspException(rc);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response status
+            ctxt.generateJavaSource("    if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){");
+            ctxt.generateJavaSource("        throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    " + tempStringName + " = " + irwName + ".getString();");
+            
+            ctxt.generateJavaSource("}");
+            
+            if(hasVar){
+                String strVar = ctxt.getConstantAttribute("var");
+                ctxt.generateJavaSource("pageContext.setAttribute(\""+strVar+"\"," + tempStringName + "," + iScope + ");");
+            }else{
+                ctxt.generateJavaSource("pageContext.getOut().print(" + tempStringName + ");");
+            }
+        }
+    }
+    
+    
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java
new file mode 100644
index 0000000..3119d30
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java
@@ -0,0 +1,32 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.*;
+
+public final class Otherwise implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        // See When.java for the reason whey "}" is need at the beginng and
+        // not at the end.
+        ctxt.generateJavaSource("} else {");
+        ctxt.generateBody();
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java
new file mode 100644
index 0000000..92ac1fe
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java
@@ -0,0 +1,90 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+
+public final class Out implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //these two data member are to indicate 
+        //whether the corresponding attribute is specified
+        boolean hasDefault=false, hasEscapeXml=false;
+        hasDefault = ctxt.isAttributeSpecified("default");
+        hasEscapeXml = ctxt.isAttributeSpecified("escapeXml");
+        
+        //strValName, strEscapeXmlName & strDefName are two variables' name 
+        //standing for value, escapeXml and default attribute
+        String strValName = ctxt.getTemporaryVariableName();
+        String strDefName = ctxt.getTemporaryVariableName();
+        String strEscapeXmlName = ctxt.getTemporaryVariableName();
+        
+        //according to the tag file, the value attribute is mandatory.
+        ctxt.generateJavaSource("String " + strValName + " = null;");
+        ctxt.generateJavaSource("if(");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource("!=null){");
+        ctxt.generateJavaSource("    " + strValName + " = (");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(").toString();");
+        ctxt.generateJavaSource("}");
+        
+        //initiate the strDefName with null.
+        //if the default has been specified, then assign the value to it;
+        ctxt.generateJavaSource("String " + strDefName + " = null;\n");
+        if(hasDefault){
+            ctxt.generateJavaSource("if(");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(" != null){");
+            ctxt.generateJavaSource(strDefName + " = (");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(").toString();");
+            ctxt.generateJavaSource("}");
+        }
+        
+        //initiate the strEscapeXmlName with true;
+        //if the escapeXml is specified, assign the value to it;
+        ctxt.generateJavaSource("boolean " + strEscapeXmlName + " = true;");
+        if(hasEscapeXml){
+            ctxt.generateJavaSource(strEscapeXmlName + " = Boolean.parseBoolean((");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(").toString());");
+        }
+        
+        //main part. 
+        ctxt.generateJavaSource("if(null != " + strValName +"){");
+        ctxt.generateJavaSource("    if(" + strEscapeXmlName + "){");
+        ctxt.generateJavaSource("        " + strValName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strValName + ");");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("    out.write(" + strValName + ");");
+        ctxt.generateJavaSource("}else{");
+        ctxt.generateJavaSource("    if(null != " + strDefName + "){");
+        ctxt.generateJavaSource("        if(" + strEscapeXmlName + "){");
+        ctxt.generateJavaSource("            " + strDefName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strDefName + ");");
+        ctxt.generateJavaSource("        }");
+        ctxt.generateJavaSource("        out.write(" + strDefName + ");");
+        ctxt.generateJavaSource("    }else{");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");   
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java
new file mode 100644
index 0000000..42256b8
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java
@@ -0,0 +1,77 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Param implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //don't support the body content
+        
+        //define names of all the temp variables
+        String nameName = ctxt.getTemporaryVariableName();
+        String valueName = ctxt.getTemporaryVariableName();
+        String urlName = ctxt.getTemporaryVariableName();
+        String encName = ctxt.getTemporaryVariableName();
+        String index = ctxt.getTemporaryVariableName();
+        
+        //if the param tag has no parents, throw a exception
+        TagPluginContext parent = ctxt.getParentContext();
+        if(parent == null){
+            ctxt.generateJavaSource(" throw new JspTagExcption" +
+            "(\"&lt;param&gt; outside &lt;import&gt; or &lt;urlEncode&gt;\");");
+            return;
+        }
+        
+        //get the url string before adding this param
+        ctxt.generateJavaSource("String " + urlName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        
+        //get the value of "name"
+        ctxt.generateJavaSource("String " + nameName + " = ");
+        ctxt.generateAttribute("name");
+        ctxt.generateJavaSource(";");
+        
+        //if the "name" is null then do nothing.
+        //else add such string "name=value" to the url.
+        //and the url should be encoded
+        ctxt.generateJavaSource("if(" + nameName + " != null && !" + nameName + ".equals(\"\")){");
+        ctxt.generateJavaSource("    String " + valueName + " = ");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(";");
+        ctxt.generateJavaSource("    if(" + valueName + " == null) " + valueName + " = \"\";");
+        ctxt.generateJavaSource("    String " + encName + " = pageContext.getResponse().getCharacterEncoding();");
+        ctxt.generateJavaSource("    " + nameName + " = java.net.URLEncoder.encode(" + nameName + ", " + encName + ");");
+        ctxt.generateJavaSource("    " + valueName + " = java.net.URLEncoder.encode(" + valueName + ", " + encName + ");");
+        ctxt.generateJavaSource("    int " + index + ";");
+        ctxt.generateJavaSource("    " + index + " = " + urlName + ".indexOf(\'?\');");
+        //if the current param is the first one, add a "?" ahead of it
+        //else add a "&" ahead of it
+        ctxt.generateJavaSource("    if(" + index + " == -1){");
+        ctxt.generateJavaSource("        " + urlName + " = " + urlName + " + \"?\" + " + nameName + " + \"=\" + " + valueName + ";");
+        ctxt.generateJavaSource("    }else{");
+        ctxt.generateJavaSource("        " + urlName + " = " + urlName + " + \"&\" + " + nameName + " + \"=\" + " + valueName + ";");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("    pageContext.setAttribute(\"url_without_param\"," + urlName + ");");
+        ctxt.generateJavaSource("}");	
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java
new file mode 100644
index 0000000..6a5813f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java
@@ -0,0 +1,83 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Redirect implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flag for the existence of the "context"
+        boolean hasContext = ctxt.isAttributeSpecified("context");
+        
+        //names of the temp variables
+        String urlName = ctxt.getTemporaryVariableName();
+        String contextName = ctxt.getTemporaryVariableName();
+        String baseUrlName = ctxt.getTemporaryVariableName();
+        String resultName = ctxt.getTemporaryVariableName();
+        String responseName = ctxt.getTemporaryVariableName();
+        
+        //get context
+        ctxt.generateJavaSource("String " + contextName + " = null;");
+        if(hasContext){
+            ctxt.generateJavaSource(contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+        }
+        
+        //get the url
+        ctxt.generateJavaSource("String " + urlName + " = ");
+        ctxt.generateAttribute("url");
+        ctxt.generateJavaSource(";");
+        
+        //get the raw url according to "url" and "context"
+        ctxt.generateJavaSource("String " + baseUrlName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + urlName + ", " + contextName + ", pageContext);");
+        ctxt.generateJavaSource("pageContext.setAttribute" +
+                "(\"url_without_param\", " + baseUrlName + ");");
+        
+        //add params
+        ctxt.generateBody();
+        
+        ctxt.generateJavaSource("String " + resultName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        ctxt.generateJavaSource("pageContext.removeAttribute" +
+        "(\"url_without_param\");");
+        
+        //get the response object
+        ctxt.generateJavaSource("HttpServletResponse " + responseName + " = " +
+        "((HttpServletResponse) pageContext.getResponse());");
+        
+        //if the url is relative, encode it
+        ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){");
+        ctxt.generateJavaSource("    " + resultName + " = "
+                + responseName + ".encodeRedirectURL(" + resultName + ");");
+        ctxt.generateJavaSource("}");
+        
+        //do redirect
+        ctxt.generateJavaSource("try{");
+        ctxt.generateJavaSource("    " + responseName + ".sendRedirect(" + resultName + ");");
+        ctxt.generateJavaSource("}catch(java.io.IOException ex){");
+        ctxt.generateJavaSource("    throw new JspTagException(ex.toString(), ex);");
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java
new file mode 100644
index 0000000..f21fa8f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java
@@ -0,0 +1,45 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Remove implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //scope flag
+        boolean hasScope = ctxt.isAttributeSpecified("scope");
+        
+        //the value of the "var"
+        String strVar = ctxt.getConstantAttribute("var");
+        
+        //remove attribute from certain scope.
+        //default scope is "page".
+        if(hasScope){
+            int iScope = Util.getScope(ctxt.getConstantAttribute("scope"));
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");");
+        }else{
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\");");
+        }
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java
new file mode 100644
index 0000000..bfb2c8b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java
@@ -0,0 +1,167 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Set implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //the flags to indicate whether the attributes have been specified
+        boolean hasValue = false, hasVar = false, hasScope = false, 
+        hasTarget = false;
+        
+        //the scope name
+        String strScope;
+        //the id of the scope
+        int iScope;
+        
+        //initialize the flags
+        hasValue = ctxt.isAttributeSpecified("value");
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        hasTarget = ctxt.isAttributeSpecified("target");
+        
+        //the temp variables name
+        String resultName = ctxt.getTemporaryVariableName();
+        String targetName = ctxt.getTemporaryVariableName();
+        String propertyName = ctxt.getTemporaryVariableName();
+        
+        //initialize the "result" which will be assigned to the var or target.property
+        ctxt.generateJavaSource("Object " + resultName + " = null;");
+        if(hasValue){
+            ctxt.generateJavaSource(resultName + " = ");
+            ctxt.generateAttribute("value");
+            ctxt.generateJavaSource(";");
+        }else{
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        //initialize the strScope
+        if(hasScope){
+            strScope = ctxt.getConstantAttribute("scope");
+        }else{
+            strScope = "page";
+        }
+        
+        //get the iScope according to the strScope
+        iScope = Util.getScope(strScope);
+        
+        //if the attribute var has been specified then assign the result to the var;
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("if(null != " + resultName + "){");
+            ctxt.generateJavaSource("    pageContext.setAttribute(\"" + strVar + "\"," + resultName + "," + iScope + ");");
+            ctxt.generateJavaSource("} else {");
+            if(hasScope){
+                ctxt.generateJavaSource("    pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");");
+            }else{
+                ctxt.generateJavaSource("    pageContext.removeAttribute(\"" + strVar + "\");");
+            }
+            ctxt.generateJavaSource("}");
+            
+            //else assign the result to the target.property
+        }else if(hasTarget){
+            
+            //generate the temp variable name
+            String pdName = ctxt.getTemporaryVariableName();
+            String successFlagName = ctxt.getTemporaryVariableName();
+            String index = ctxt.getTemporaryVariableName();
+            String methodName = ctxt.getTemporaryVariableName();
+            
+            //initialize the property
+            ctxt.generateJavaSource("String " + propertyName + " = null;");
+            ctxt.generateJavaSource("if(");
+            ctxt.generateAttribute("property");
+            ctxt.generateJavaSource(" != null){");
+            ctxt.generateJavaSource("    " + propertyName + " = (");
+            ctxt.generateAttribute("property");
+            ctxt.generateJavaSource(").toString();");
+            ctxt.generateJavaSource("}");
+            
+            //initialize the target
+            ctxt.generateJavaSource("Object " + targetName + " = ");
+            ctxt.generateAttribute("target");
+            ctxt.generateJavaSource(";");
+            
+            //the target is ok
+            ctxt.generateJavaSource("if(" + targetName + " != null){");
+            
+            //if the target is a map, then put the result into the map with the key property
+            ctxt.generateJavaSource("    if(" + targetName + " instanceof java.util.Map){");
+            ctxt.generateJavaSource("        if(null != " + resultName + "){");
+            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").put(" + propertyName + "," + resultName + ");");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").remove(" + propertyName + ");");
+            ctxt.generateJavaSource("        }");
+            
+            //else assign the result to the target.property
+            ctxt.generateJavaSource("    }else{");
+            ctxt.generateJavaSource("        try{");
+            
+            //get all the property of the target
+            ctxt.generateJavaSource("            java.beans.PropertyDescriptor " + pdName + "[] = java.beans.Introspector.getBeanInfo(" + targetName + ".getClass()).getPropertyDescriptors();");
+            
+            //the success flag is to imply whether the assign is successful
+            ctxt.generateJavaSource("            boolean " + successFlagName + " = false;");
+            
+            //find the right property
+            ctxt.generateJavaSource("            for(int " + index + "=0;" + index + "<" + pdName + ".length;" + index + "++){");
+            ctxt.generateJavaSource("                if(" + pdName + "[" + index + "].getName().equals(" + propertyName + ")){");
+            
+            //get the "set" method;
+            ctxt.generateJavaSource("                    java.lang.reflect.Method " + methodName + " = " + pdName + "[" + index + "].getWriteMethod();");
+            ctxt.generateJavaSource("                    if(null == " + methodName + "){");
+            ctxt.generateJavaSource("                        throw new JspException(\"No setter method in &lt;set&gt; for property \"+" + propertyName + ");");
+            ctxt.generateJavaSource("                    }");
+            
+            //invoke the method through the reflection
+            ctxt.generateJavaSource("                    if(" + resultName + " != null){");
+            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{(" + methodName + ".getParameterTypes()[0]).cast(" + resultName + ")});");
+            ctxt.generateJavaSource("                    }else{");
+            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{null});");
+            ctxt.generateJavaSource("                    }");
+            ctxt.generateJavaSource("                    " + successFlagName + " = true;");
+            ctxt.generateJavaSource("                }");
+            ctxt.generateJavaSource("            }");
+            ctxt.generateJavaSource("            if(!" + successFlagName + "){");
+            ctxt.generateJavaSource("                throw new JspException(\"Invalid property in &lt;set&gt;:\"+" + propertyName + ");");
+            ctxt.generateJavaSource("            }");
+            ctxt.generateJavaSource("        }");
+            
+            //catch the el exception and throw it as a JspException
+            ctxt.generateJavaSource("        catch (IllegalAccessException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        } catch (java.beans.IntrospectionException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        } catch (java.lang.reflect.InvocationTargetException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("}else{");
+            ctxt.generateJavaSource("    throw new JspException();");
+            ctxt.generateJavaSource("}");
+        }
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java
new file mode 100644
index 0000000..9db64f6
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java
@@ -0,0 +1,101 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Url implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flags
+        boolean hasVar, hasContext, hasScope;
+        
+        //init flags
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasContext = ctxt.isAttributeSpecified("context");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        
+        //define name of the temp variables
+        String valueName = ctxt.getTemporaryVariableName();
+        String contextName = ctxt.getTemporaryVariableName();
+        String baseUrlName = ctxt.getTemporaryVariableName();
+        String resultName = ctxt.getTemporaryVariableName();
+        String responseName = ctxt.getTemporaryVariableName();
+        
+        //get the scope
+        String strScope = "page";
+        if(hasScope){
+            strScope = ctxt.getConstantAttribute("scope");
+        }
+        int iScope = Util.getScope(strScope);
+        
+        //get the value
+        ctxt.generateJavaSource("String " + valueName + " = ");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(";");
+        
+        //get the context
+        ctxt.generateJavaSource("String " + contextName + " = null;");
+        if(hasContext){
+            ctxt.generateJavaSource(contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+        }
+        
+        //get the raw url
+        ctxt.generateJavaSource("String " + baseUrlName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + valueName + ", " + contextName + ", pageContext);");
+        ctxt.generateJavaSource("pageContext.setAttribute" +
+                "(\"url_without_param\", " + baseUrlName + ");");
+        
+        //add params
+        ctxt.generateBody();
+        
+        ctxt.generateJavaSource("String " + resultName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");");
+        
+        //if the url is relative, encode it
+        ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){");
+        ctxt.generateJavaSource("    HttpServletResponse " + responseName + " = " +
+        "((HttpServletResponse) pageContext.getResponse());");
+        ctxt.generateJavaSource("    " + resultName + " = "
+                + responseName + ".encodeURL(" + resultName + ");");
+        ctxt.generateJavaSource("}");
+        
+        //if "var" is specified, the url string store in the attribute var defines
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("pageContext.setAttribute" +
+                    "(\"" + strVar + "\", " + resultName + ", " + iScope + ");");
+            
+            //if var is not specified, just print out the url string
+        }else{
+            ctxt.generateJavaSource("try{");
+            ctxt.generateJavaSource("    pageContext.getOut().print(" + resultName + ");");
+            ctxt.generateJavaSource("}catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("    throw new JspTagException(ex.toString(), ex);");
+            ctxt.generateJavaSource("}");
+        }
+    }
+    
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java
new file mode 100644
index 0000000..10aefc8
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java
@@ -0,0 +1,50 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.*;
+
+public final class When implements TagPlugin {
+    
+    public void doTag(TagPluginContext ctxt) {
+        // Get the parent context to determine if this is the first <c:when>
+        TagPluginContext parentContext = ctxt.getParentContext();
+        if (parentContext == null) {
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        if ("true".equals(parentContext.getPluginAttribute("hasBeenHere"))) {
+            ctxt.generateJavaSource("} else if(");
+            // See comment below for the reason we generate the extra "}" here.
+        }
+        else {
+            ctxt.generateJavaSource("if(");
+            parentContext.setPluginAttribute("hasBeenHere", "true");
+        }
+        ctxt.generateAttribute("test");
+        ctxt.generateJavaSource("){");
+        ctxt.generateBody();
+        
+        // We don't generate the closing "}" for the "if" here because there
+        // may be whitespaces in between <c:when>'s.  Instead we delay
+        // generating it until the next <c:when> or <c:otherwise> or
+        // <c:choose>
+    }
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml
new file mode 100644
index 0000000..859c6c8
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<tag-plugins>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.If</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Choose</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.When</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Otherwise</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.ForEach</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+  	<tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+  	<plugin-class>org.apache.jasper.tagplugins.jstl.core.Out</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Set</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Remove</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Catch</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.ForTokens</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Import</plugin-class>
+  </tag-plugin>
+</tag-plugins>
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/util/Enumerator.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/util/Enumerator.java
new file mode 100644
index 0000000..8c6e7e3
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/util/Enumerator.java
@@ -0,0 +1,176 @@
+/*
+ * 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.jasper.util;
+
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+
+/**
+ * Adapter class that wraps an <code>Enumeration</code> around a Java2
+ * collection classes object <code>Iterator</code> so that existing APIs
+ * returning Enumerations can easily run on top of the new collections.
+ * Constructors are provided to easliy create such wrappers.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision$ $Date$
+ */
+
+public final class Enumerator implements Enumeration {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     */
+    public Enumerator(Collection collection) {
+
+        this(collection.iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Collection collection, boolean clone) {
+
+        this(collection.iterator(), clone);
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     */
+    public Enumerator(Iterator iterator) {
+
+        super();
+        this.iterator = iterator;
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Iterator iterator, boolean clone) {
+
+        super();
+        if (!clone) {
+            this.iterator = iterator;
+        } else {
+            List list = new ArrayList();
+            while (iterator.hasNext()) {
+                list.add(iterator.next());
+            }
+            this.iterator = list.iterator();   
+        }
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     */
+    public Enumerator(Map map) {
+
+        this(map.values().iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Map map, boolean clone) {
+
+        this(map.values().iterator(), clone);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>Iterator</code> over which the <code>Enumeration</code>
+     * represented by this class actually operates.
+     */
+    private Iterator iterator = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Tests if this enumeration contains more elements.
+     *
+     * @return <code>true</code> if and only if this enumeration object
+     *  contains at least one more element to provide, <code>false</code>
+     *  otherwise
+     */
+    public boolean hasMoreElements() {
+
+        return (iterator.hasNext());
+
+    }
+
+
+    /**
+     * Returns the next element of this enumeration if this enumeration
+     * has at least one more element to provide.
+     *
+     * @return the next element of this enumeration
+     *
+     * @exception NoSuchElementException if no more elements exist
+     */
+    public Object nextElement() throws NoSuchElementException {
+
+        return (iterator.next());
+
+    }
+
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java
new file mode 100644
index 0000000..7c3ca2d
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java
@@ -0,0 +1,204 @@
+/*
+ * 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.jasper.xmlparser;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.Reader;
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * A simple ASCII byte reader. This is an optimized reader for reading
+ * byte streams that only contain 7-bit ASCII characters.
+ *
+ * @author Andy Clark, IBM
+ *
+ * @version $Id$
+ */
+public class ASCIIReader
+    extends Reader {
+
+    //
+    // Constants
+    //
+
+    /** Default byte buffer size (2048). */
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * and buffer size.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     */
+    public ASCIIReader(InputStream inputStream, int size) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+    }
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 127
+     *             (<tt>0x00-0x7f</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read() throws IOException {
+        int b0 = fInputStream.read();
+        if (b0 > 0x80) {
+            throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII",
+						       Integer.toString(b0)));
+        }
+        return b0;
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read(char ch[], int offset, int length) throws IOException {
+        if (length > fBuffer.length) {
+            length = fBuffer.length;
+        }
+        int count = fInputStream.read(fBuffer, 0, length);
+        for (int i = 0; i < count; i++) {
+            int b0 = (0xff & fBuffer[i]); // Convert to unsigned
+            if (b0 > 0x80) {
+                throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII",
+							   Integer.toString(b0)));
+            }
+            ch[offset + i] = (char)b0;
+        }
+        return count;
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public long skip(long n) throws IOException {
+        return fInputStream.skip(n);
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public boolean ready() throws IOException {
+	return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    public boolean markSupported() {
+	return fInputStream.markSupported();
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    public void mark(int readAheadLimit) throws IOException {
+	fInputStream.mark(readAheadLimit);
+    } // mark(int)
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    public void reset() throws IOException {
+        fInputStream.reset();
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+     public void close() throws IOException {
+         fInputStream.close();
+     } // close()
+
+} // class ASCIIReader
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java
new file mode 100644
index 0000000..0ebf31f
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java
@@ -0,0 +1,1020 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.util.Hashtable;
+
+/**
+ * EncodingMap is a convenience class which handles conversions between 
+ * IANA encoding names and Java encoding names, and vice versa. The
+ * encoding names used in XML instance documents <strong>must</strong>
+ * be the IANA encoding names specified or one of the aliases for those names
+ * which IANA defines.
+ * <p>
+ * <TABLE BORDER="0" WIDTH="100%">
+ *  <TR>
+ *      <TD WIDTH="33%">
+ *          <P ALIGN="CENTER"><B>Common Name</B>
+ *      </TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER"><B>Use this name in XML files</B>
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER"><B>Name Type</B>
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER"><B>Xerces converts to this Java Encoder Name</B>
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">8 bit Unicode</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">UTF-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">UTF8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 1</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 2</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 3</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 4</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Greek</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: US</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-us
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Canada</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ca
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Netherlands</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-nl
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Denmark</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-dk
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Norway</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-no
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Finland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fi
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Sweden</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-se
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Italy</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-it
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp280
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Spain, Latin America</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-es
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp284
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Great Britain</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-gb
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp285
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: France</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp297
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp420
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-he
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp424
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Switzerland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ch
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp500
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Roece</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-roece
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Yugoslavia</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-yu
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Iceland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-is
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp871
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Urdu</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp918
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese for PRC, mixed 1/2 byte</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">gb2312
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">GB2312
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Japanese</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">eucjis
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: iso-2022-jp</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">iso-2020-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">JIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: Shift JIS</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Shift_JIS
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">SJIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese: Big5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Korean</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-kr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">iso2022kr
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *  </TR>
+ * </TABLE>
+ * 
+ * @author TAMURA Kent, IBM
+ * @author Andy Clark, IBM
+ *
+ * @version $Id$
+ */
+public class EncodingMap {
+
+    //
+    // Data
+    //
+
+    /** fIANA2JavaMap */
+    protected final static Hashtable fIANA2JavaMap = new Hashtable();
+
+    /** fJava2IANAMap */
+    protected final static Hashtable fJava2IANAMap = new Hashtable();
+
+    //
+    // Static initialization
+    //
+
+    static {
+
+        // add IANA to Java encoding mappings.
+        fIANA2JavaMap.put("BIG5",            "Big5");
+        fIANA2JavaMap.put("CSBIG5",            "Big5");
+        fIANA2JavaMap.put("CP037",    "CP037");
+        fIANA2JavaMap.put("IBM037",    "CP037");
+        fIANA2JavaMap.put("CSIBM037",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-US",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-CA",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-NL",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-WT",    "CP037");
+        fIANA2JavaMap.put("IBM273",    "CP273");
+        fIANA2JavaMap.put("CP273",    "CP273");
+        fIANA2JavaMap.put("CSIBM273",    "CP273");
+        fIANA2JavaMap.put("IBM277",    "CP277");
+        fIANA2JavaMap.put("CP277",    "CP277");
+        fIANA2JavaMap.put("CSIBM277",    "CP277");
+        fIANA2JavaMap.put("EBCDIC-CP-DK",    "CP277");
+        fIANA2JavaMap.put("EBCDIC-CP-NO",    "CP277");
+        fIANA2JavaMap.put("IBM278",    "CP278");
+        fIANA2JavaMap.put("CP278",    "CP278");
+        fIANA2JavaMap.put("CSIBM278",    "CP278");
+        fIANA2JavaMap.put("EBCDIC-CP-FI",    "CP278");
+        fIANA2JavaMap.put("EBCDIC-CP-SE",    "CP278");
+        fIANA2JavaMap.put("IBM280",    "CP280");
+        fIANA2JavaMap.put("CP280",    "CP280");
+        fIANA2JavaMap.put("CSIBM280",    "CP280");
+        fIANA2JavaMap.put("EBCDIC-CP-IT",    "CP280");
+        fIANA2JavaMap.put("IBM284",    "CP284");
+        fIANA2JavaMap.put("CP284",    "CP284");
+        fIANA2JavaMap.put("CSIBM284",    "CP284");
+        fIANA2JavaMap.put("EBCDIC-CP-ES",    "CP284");
+        fIANA2JavaMap.put("EBCDIC-CP-GB",    "CP285");
+        fIANA2JavaMap.put("IBM285",    "CP285");
+        fIANA2JavaMap.put("CP285",    "CP285");
+        fIANA2JavaMap.put("CSIBM285",    "CP285");
+        fIANA2JavaMap.put("EBCDIC-JP-KANA",    "CP290");
+        fIANA2JavaMap.put("IBM290",    "CP290");
+        fIANA2JavaMap.put("CP290",    "CP290");
+        fIANA2JavaMap.put("CSIBM290",    "CP290");
+        fIANA2JavaMap.put("EBCDIC-CP-FR",    "CP297");
+        fIANA2JavaMap.put("IBM297",    "CP297");
+        fIANA2JavaMap.put("CP297",    "CP297");
+        fIANA2JavaMap.put("CSIBM297",    "CP297");
+        fIANA2JavaMap.put("EBCDIC-CP-AR1",   "CP420");
+        fIANA2JavaMap.put("IBM420",    "CP420");
+        fIANA2JavaMap.put("CP420",    "CP420");
+        fIANA2JavaMap.put("CSIBM420",    "CP420");
+        fIANA2JavaMap.put("EBCDIC-CP-HE",    "CP424");
+        fIANA2JavaMap.put("IBM424",    "CP424");
+        fIANA2JavaMap.put("CP424",    "CP424");
+        fIANA2JavaMap.put("CSIBM424",    "CP424");
+        fIANA2JavaMap.put("IBM437",    "CP437");
+        fIANA2JavaMap.put("437",    "CP437");
+        fIANA2JavaMap.put("CP437",    "CP437");
+        fIANA2JavaMap.put("CSPC8CODEPAGE437",    "CP437");
+        fIANA2JavaMap.put("EBCDIC-CP-CH",    "CP500");
+        fIANA2JavaMap.put("IBM500",    "CP500");
+        fIANA2JavaMap.put("CP500",    "CP500");
+        fIANA2JavaMap.put("CSIBM500",    "CP500");
+        fIANA2JavaMap.put("EBCDIC-CP-CH",    "CP500");
+        fIANA2JavaMap.put("EBCDIC-CP-BE",    "CP500"); 
+        fIANA2JavaMap.put("IBM775",    "CP775");
+        fIANA2JavaMap.put("CP775",    "CP775");
+        fIANA2JavaMap.put("CSPC775BALTIC",    "CP775");
+        fIANA2JavaMap.put("IBM850",    "CP850");
+        fIANA2JavaMap.put("850",    "CP850");
+        fIANA2JavaMap.put("CP850",    "CP850");
+        fIANA2JavaMap.put("CSPC850MULTILINGUAL",    "CP850");
+        fIANA2JavaMap.put("IBM852",    "CP852");
+        fIANA2JavaMap.put("852",    "CP852");
+        fIANA2JavaMap.put("CP852",    "CP852");
+        fIANA2JavaMap.put("CSPCP852",    "CP852");
+        fIANA2JavaMap.put("IBM855",    "CP855");
+        fIANA2JavaMap.put("855",    "CP855");
+        fIANA2JavaMap.put("CP855",    "CP855");
+        fIANA2JavaMap.put("CSIBM855",    "CP855");
+        fIANA2JavaMap.put("IBM857",    "CP857");
+        fIANA2JavaMap.put("857",    "CP857");
+        fIANA2JavaMap.put("CP857",    "CP857");
+        fIANA2JavaMap.put("CSIBM857",    "CP857");
+        fIANA2JavaMap.put("IBM00858",    "CP858");
+        fIANA2JavaMap.put("CP00858",    "CP858");
+        fIANA2JavaMap.put("CCSID00858",    "CP858");
+        fIANA2JavaMap.put("IBM860",    "CP860");
+        fIANA2JavaMap.put("860",    "CP860");
+        fIANA2JavaMap.put("CP860",    "CP860");
+        fIANA2JavaMap.put("CSIBM860",    "CP860");
+        fIANA2JavaMap.put("IBM861",    "CP861");
+        fIANA2JavaMap.put("861",    "CP861");
+        fIANA2JavaMap.put("CP861",    "CP861");
+        fIANA2JavaMap.put("CP-IS",    "CP861");
+        fIANA2JavaMap.put("CSIBM861",    "CP861");
+        fIANA2JavaMap.put("IBM862",    "CP862");
+        fIANA2JavaMap.put("862",    "CP862");
+        fIANA2JavaMap.put("CP862",    "CP862");
+        fIANA2JavaMap.put("CSPC862LATINHEBREW",    "CP862");
+        fIANA2JavaMap.put("IBM863",    "CP863");
+        fIANA2JavaMap.put("863",    "CP863");
+        fIANA2JavaMap.put("CP863",    "CP863");
+        fIANA2JavaMap.put("CSIBM863",    "CP863");
+        fIANA2JavaMap.put("IBM864",    "CP864");
+        fIANA2JavaMap.put("CP864",    "CP864");
+        fIANA2JavaMap.put("CSIBM864",    "CP864");
+        fIANA2JavaMap.put("IBM865",    "CP865");
+        fIANA2JavaMap.put("865",    "CP865");
+        fIANA2JavaMap.put("CP865",    "CP865");
+        fIANA2JavaMap.put("CSIBM865",    "CP865");
+        fIANA2JavaMap.put("IBM866",    "CP866");
+        fIANA2JavaMap.put("866",    "CP866");
+        fIANA2JavaMap.put("CP866",    "CP866");
+        fIANA2JavaMap.put("CSIBM866",    "CP866");
+        fIANA2JavaMap.put("IBM868",    "CP868");
+        fIANA2JavaMap.put("CP868",    "CP868");
+        fIANA2JavaMap.put("CSIBM868",    "CP868");
+        fIANA2JavaMap.put("CP-AR",        "CP868");
+        fIANA2JavaMap.put("IBM869",    "CP869");
+        fIANA2JavaMap.put("CP869",    "CP869");
+        fIANA2JavaMap.put("CSIBM869",    "CP869");
+        fIANA2JavaMap.put("CP-GR",        "CP869");
+        fIANA2JavaMap.put("IBM870",    "CP870");
+        fIANA2JavaMap.put("CP870",    "CP870");
+        fIANA2JavaMap.put("CSIBM870",    "CP870");
+        fIANA2JavaMap.put("EBCDIC-CP-ROECE", "CP870");
+        fIANA2JavaMap.put("EBCDIC-CP-YU",    "CP870");
+        fIANA2JavaMap.put("IBM871",    "CP871");
+        fIANA2JavaMap.put("CP871",    "CP871");
+        fIANA2JavaMap.put("CSIBM871",    "CP871");
+        fIANA2JavaMap.put("EBCDIC-CP-IS",    "CP871");
+        fIANA2JavaMap.put("IBM918",    "CP918");
+        fIANA2JavaMap.put("CP918",    "CP918");
+        fIANA2JavaMap.put("CSIBM918",    "CP918");
+        fIANA2JavaMap.put("EBCDIC-CP-AR2",   "CP918");
+        fIANA2JavaMap.put("IBM00924",    "CP924");
+        fIANA2JavaMap.put("CP00924",    "CP924");
+        fIANA2JavaMap.put("CCSID00924",    "CP924");
+        // is this an error???
+        fIANA2JavaMap.put("EBCDIC-LATIN9--EURO",    "CP924");
+        fIANA2JavaMap.put("IBM1026",    "CP1026");
+        fIANA2JavaMap.put("CP1026",    "CP1026");
+        fIANA2JavaMap.put("CSIBM1026",    "CP1026");
+        fIANA2JavaMap.put("IBM01140",    "Cp1140");
+        fIANA2JavaMap.put("CP01140",    "Cp1140");
+        fIANA2JavaMap.put("CCSID01140",    "Cp1140");
+        fIANA2JavaMap.put("IBM01141",    "Cp1141");
+        fIANA2JavaMap.put("CP01141",    "Cp1141");
+        fIANA2JavaMap.put("CCSID01141",    "Cp1141");
+        fIANA2JavaMap.put("IBM01142",    "Cp1142");
+        fIANA2JavaMap.put("CP01142",    "Cp1142");
+        fIANA2JavaMap.put("CCSID01142",    "Cp1142");
+        fIANA2JavaMap.put("IBM01143",    "Cp1143");
+        fIANA2JavaMap.put("CP01143",    "Cp1143");
+        fIANA2JavaMap.put("CCSID01143",    "Cp1143");
+        fIANA2JavaMap.put("IBM01144",    "Cp1144");
+        fIANA2JavaMap.put("CP01144",    "Cp1144");
+        fIANA2JavaMap.put("CCSID01144",    "Cp1144");
+        fIANA2JavaMap.put("IBM01145",    "Cp1145");
+        fIANA2JavaMap.put("CP01145",    "Cp1145");
+        fIANA2JavaMap.put("CCSID01145",    "Cp1145");
+        fIANA2JavaMap.put("IBM01146",    "Cp1146");
+        fIANA2JavaMap.put("CP01146",    "Cp1146");
+        fIANA2JavaMap.put("CCSID01146",    "Cp1146");
+        fIANA2JavaMap.put("IBM01147",    "Cp1147");
+        fIANA2JavaMap.put("CP01147",    "Cp1147");
+        fIANA2JavaMap.put("CCSID01147",    "Cp1147");
+        fIANA2JavaMap.put("IBM01148",    "Cp1148");
+        fIANA2JavaMap.put("CP01148",    "Cp1148");
+        fIANA2JavaMap.put("CCSID01148",    "Cp1148");
+        fIANA2JavaMap.put("IBM01149",    "Cp1149");
+        fIANA2JavaMap.put("CP01149",    "Cp1149");
+        fIANA2JavaMap.put("CCSID01149",    "Cp1149");
+        fIANA2JavaMap.put("EUC-JP",          "EUCJIS");
+        fIANA2JavaMap.put("CSEUCPKDFMTJAPANESE",          "EUCJIS");
+        fIANA2JavaMap.put("EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE",          "EUCJIS");
+        fIANA2JavaMap.put("EUC-KR",          "KSC5601");
+        fIANA2JavaMap.put("CSEUCKR",          "KSC5601");
+        fIANA2JavaMap.put("KS_C_5601-1987",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("ISO-IR-149",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KS_C_5601-1989",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KSC_5601",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KOREAN",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("CSKSC56011987",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("GB2312",          "GB2312");
+        fIANA2JavaMap.put("CSGB2312",          "GB2312");
+        fIANA2JavaMap.put("ISO-2022-JP",     "JIS");
+        fIANA2JavaMap.put("CSISO2022JP",     "JIS");
+        fIANA2JavaMap.put("ISO-2022-KR",     "ISO2022KR");
+        fIANA2JavaMap.put("CSISO2022KR",     "ISO2022KR");
+        fIANA2JavaMap.put("ISO-2022-CN",     "ISO2022CN");
+
+        fIANA2JavaMap.put("X0201",  "JIS0201");
+        fIANA2JavaMap.put("CSISO13JISC6220JP", "JIS0201");
+        fIANA2JavaMap.put("X0208",  "JIS0208");
+        fIANA2JavaMap.put("ISO-IR-87",  "JIS0208");
+        fIANA2JavaMap.put("X0208dbiJIS_X0208-1983",  "JIS0208");
+        fIANA2JavaMap.put("CSISO87JISX0208",  "JIS0208");
+        fIANA2JavaMap.put("X0212",  "JIS0212");
+        fIANA2JavaMap.put("ISO-IR-159",  "JIS0212");
+        fIANA2JavaMap.put("CSISO159JISX02121990",  "JIS0212");
+        fIANA2JavaMap.put("GB18030",       "GB18030");
+        fIANA2JavaMap.put("GBK",       "GBK");
+        fIANA2JavaMap.put("CP936",       "GBK");
+        fIANA2JavaMap.put("MS936",       "GBK");
+        fIANA2JavaMap.put("WINDOWS-936",       "GBK");
+        fIANA2JavaMap.put("SHIFT_JIS",       "SJIS");
+        fIANA2JavaMap.put("CSSHIFTJIS",       "SJIS");
+        fIANA2JavaMap.put("MS_KANJI",       "SJIS");
+        fIANA2JavaMap.put("WINDOWS-31J",       "MS932");
+        fIANA2JavaMap.put("CSWINDOWS31J",       "MS932");
+
+	    // Add support for Cp1252 and its friends
+        fIANA2JavaMap.put("WINDOWS-1250",   "Cp1250");
+        fIANA2JavaMap.put("WINDOWS-1251",   "Cp1251");
+        fIANA2JavaMap.put("WINDOWS-1252",   "Cp1252");
+        fIANA2JavaMap.put("WINDOWS-1253",   "Cp1253");
+        fIANA2JavaMap.put("WINDOWS-1254",   "Cp1254");
+        fIANA2JavaMap.put("WINDOWS-1255",   "Cp1255");
+        fIANA2JavaMap.put("WINDOWS-1256",   "Cp1256");
+        fIANA2JavaMap.put("WINDOWS-1257",   "Cp1257");
+        fIANA2JavaMap.put("WINDOWS-1258",   "Cp1258");
+        fIANA2JavaMap.put("TIS-620",   "TIS620");
+
+        fIANA2JavaMap.put("ISO-8859-1",      "ISO8859_1"); 
+        fIANA2JavaMap.put("ISO-IR-100",      "ISO8859_1");
+        fIANA2JavaMap.put("ISO_8859-1",      "ISO8859_1");
+        fIANA2JavaMap.put("LATIN1",      "ISO8859_1");
+        fIANA2JavaMap.put("CSISOLATIN1",      "ISO8859_1");
+        fIANA2JavaMap.put("L1",      "ISO8859_1");
+        fIANA2JavaMap.put("IBM819",      "ISO8859_1");
+        fIANA2JavaMap.put("CP819",      "ISO8859_1");
+
+        fIANA2JavaMap.put("ISO-8859-2",      "ISO8859_2"); 
+        fIANA2JavaMap.put("ISO-IR-101",      "ISO8859_2");
+        fIANA2JavaMap.put("ISO_8859-2",      "ISO8859_2");
+        fIANA2JavaMap.put("LATIN2",      "ISO8859_2");
+        fIANA2JavaMap.put("CSISOLATIN2",      "ISO8859_2");
+        fIANA2JavaMap.put("L2",      "ISO8859_2");
+
+        fIANA2JavaMap.put("ISO-8859-3",      "ISO8859_3"); 
+        fIANA2JavaMap.put("ISO-IR-109",      "ISO8859_3");
+        fIANA2JavaMap.put("ISO_8859-3",      "ISO8859_3");
+        fIANA2JavaMap.put("LATIN3",      "ISO8859_3");
+        fIANA2JavaMap.put("CSISOLATIN3",      "ISO8859_3");
+        fIANA2JavaMap.put("L3",      "ISO8859_3");
+
+        fIANA2JavaMap.put("ISO-8859-4",      "ISO8859_4"); 
+        fIANA2JavaMap.put("ISO-IR-110",      "ISO8859_4");
+        fIANA2JavaMap.put("ISO_8859-4",      "ISO8859_4");
+        fIANA2JavaMap.put("LATIN4",      "ISO8859_4");
+        fIANA2JavaMap.put("CSISOLATIN4",      "ISO8859_4");
+        fIANA2JavaMap.put("L4",      "ISO8859_4");
+
+        fIANA2JavaMap.put("ISO-8859-5",      "ISO8859_5"); 
+        fIANA2JavaMap.put("ISO-IR-144",      "ISO8859_5");
+        fIANA2JavaMap.put("ISO_8859-5",      "ISO8859_5");
+        fIANA2JavaMap.put("CYRILLIC",      "ISO8859_5");
+        fIANA2JavaMap.put("CSISOLATINCYRILLIC",      "ISO8859_5");
+
+        fIANA2JavaMap.put("ISO-8859-6",      "ISO8859_6"); 
+        fIANA2JavaMap.put("ISO-IR-127",      "ISO8859_6");
+        fIANA2JavaMap.put("ISO_8859-6",      "ISO8859_6");
+        fIANA2JavaMap.put("ECMA-114",      "ISO8859_6");
+        fIANA2JavaMap.put("ASMO-708",      "ISO8859_6");
+        fIANA2JavaMap.put("ARABIC",      "ISO8859_6");
+        fIANA2JavaMap.put("CSISOLATINARABIC",      "ISO8859_6");
+
+        fIANA2JavaMap.put("ISO-8859-7",      "ISO8859_7"); 
+        fIANA2JavaMap.put("ISO-IR-126",      "ISO8859_7");
+        fIANA2JavaMap.put("ISO_8859-7",      "ISO8859_7");
+        fIANA2JavaMap.put("ELOT_928",      "ISO8859_7");
+        fIANA2JavaMap.put("ECMA-118",      "ISO8859_7");
+        fIANA2JavaMap.put("GREEK",      "ISO8859_7");
+        fIANA2JavaMap.put("CSISOLATINGREEK",      "ISO8859_7");
+        fIANA2JavaMap.put("GREEK8",      "ISO8859_7");
+
+        fIANA2JavaMap.put("ISO-8859-8",      "ISO8859_8"); 
+        fIANA2JavaMap.put("ISO-8859-8-I",      "ISO8859_8"); // added since this encoding only differs w.r.t. presentation 
+        fIANA2JavaMap.put("ISO-IR-138",      "ISO8859_8");
+        fIANA2JavaMap.put("ISO_8859-8",      "ISO8859_8");
+        fIANA2JavaMap.put("HEBREW",      "ISO8859_8");
+        fIANA2JavaMap.put("CSISOLATINHEBREW",      "ISO8859_8");
+
+        fIANA2JavaMap.put("ISO-8859-9",      "ISO8859_9"); 
+        fIANA2JavaMap.put("ISO-IR-148",      "ISO8859_9");
+        fIANA2JavaMap.put("ISO_8859-9",      "ISO8859_9");
+        fIANA2JavaMap.put("LATIN5",      "ISO8859_9");
+        fIANA2JavaMap.put("CSISOLATIN5",      "ISO8859_9");
+        fIANA2JavaMap.put("L5",      "ISO8859_9");
+
+        fIANA2JavaMap.put("ISO-8859-13",      "ISO8859_13"); 
+        
+        fIANA2JavaMap.put("ISO-8859-15",      "ISO8859_15_FDIS"); 
+        fIANA2JavaMap.put("ISO_8859-15",      "ISO8859_15_FDIS");
+        fIANA2JavaMap.put("LATIN-9",          "ISO8859_15_FDIS"); 
+
+        fIANA2JavaMap.put("KOI8-R",          "KOI8_R");
+        fIANA2JavaMap.put("CSKOI8R",          "KOI8_R");
+        fIANA2JavaMap.put("US-ASCII",        "ASCII"); 
+        fIANA2JavaMap.put("ISO-IR-6",        "ASCII");
+        fIANA2JavaMap.put("ANSI_X3.4-1968",        "ASCII");
+        fIANA2JavaMap.put("ANSI_X3.4-1986",        "ASCII");
+        fIANA2JavaMap.put("ISO_646.IRV:1991",        "ASCII");
+        fIANA2JavaMap.put("ASCII",        "ASCII");
+        fIANA2JavaMap.put("CSASCII",        "ASCII");
+        fIANA2JavaMap.put("ISO646-US",        "ASCII");
+        fIANA2JavaMap.put("US",        "ASCII");
+        fIANA2JavaMap.put("IBM367",        "ASCII");
+        fIANA2JavaMap.put("CP367",        "ASCII");
+        fIANA2JavaMap.put("UTF-8",           "UTF8");
+        fIANA2JavaMap.put("UTF-16",           "UTF-16");
+        fIANA2JavaMap.put("UTF-16BE",           "UnicodeBig");
+        fIANA2JavaMap.put("UTF-16LE",           "UnicodeLittle");
+
+        // support for 1047, as proposed to be added to the 
+        // IANA registry in 
+        // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0049.html
+        fIANA2JavaMap.put("IBM-1047",    "Cp1047");
+        fIANA2JavaMap.put("IBM1047",    "Cp1047");
+        fIANA2JavaMap.put("CP1047",    "Cp1047");
+
+        // Adding new aliases as proposed in
+        // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0058.html
+        fIANA2JavaMap.put("IBM-37",    "CP037");
+        fIANA2JavaMap.put("IBM-273",    "CP273");
+        fIANA2JavaMap.put("IBM-277",    "CP277");
+        fIANA2JavaMap.put("IBM-278",    "CP278");
+        fIANA2JavaMap.put("IBM-280",    "CP280");
+        fIANA2JavaMap.put("IBM-284",    "CP284");
+        fIANA2JavaMap.put("IBM-285",    "CP285");
+        fIANA2JavaMap.put("IBM-290",    "CP290");
+        fIANA2JavaMap.put("IBM-297",    "CP297");
+        fIANA2JavaMap.put("IBM-420",    "CP420");
+        fIANA2JavaMap.put("IBM-424",    "CP424");
+        fIANA2JavaMap.put("IBM-437",    "CP437");
+        fIANA2JavaMap.put("IBM-500",    "CP500");
+        fIANA2JavaMap.put("IBM-775",    "CP775");
+        fIANA2JavaMap.put("IBM-850",    "CP850");
+        fIANA2JavaMap.put("IBM-852",    "CP852");
+        fIANA2JavaMap.put("IBM-855",    "CP855");
+        fIANA2JavaMap.put("IBM-857",    "CP857");
+        fIANA2JavaMap.put("IBM-858",    "CP858");
+        fIANA2JavaMap.put("IBM-860",    "CP860");
+        fIANA2JavaMap.put("IBM-861",    "CP861");
+        fIANA2JavaMap.put("IBM-862",    "CP862");
+        fIANA2JavaMap.put("IBM-863",    "CP863");
+        fIANA2JavaMap.put("IBM-864",    "CP864");
+        fIANA2JavaMap.put("IBM-865",    "CP865");
+        fIANA2JavaMap.put("IBM-866",    "CP866");
+        fIANA2JavaMap.put("IBM-868",    "CP868");
+        fIANA2JavaMap.put("IBM-869",    "CP869");
+        fIANA2JavaMap.put("IBM-870",    "CP870");
+        fIANA2JavaMap.put("IBM-871",    "CP871");
+        fIANA2JavaMap.put("IBM-918",    "CP918");
+        fIANA2JavaMap.put("IBM-924",    "CP924");
+        fIANA2JavaMap.put("IBM-1026",    "CP1026");
+        fIANA2JavaMap.put("IBM-1140",    "Cp1140");
+        fIANA2JavaMap.put("IBM-1141",    "Cp1141");
+        fIANA2JavaMap.put("IBM-1142",    "Cp1142");
+        fIANA2JavaMap.put("IBM-1143",    "Cp1143");
+        fIANA2JavaMap.put("IBM-1144",    "Cp1144");
+        fIANA2JavaMap.put("IBM-1145",    "Cp1145");
+        fIANA2JavaMap.put("IBM-1146",    "Cp1146");
+        fIANA2JavaMap.put("IBM-1147",    "Cp1147");
+        fIANA2JavaMap.put("IBM-1148",    "Cp1148");
+        fIANA2JavaMap.put("IBM-1149",    "Cp1149");
+        fIANA2JavaMap.put("IBM-819",      "ISO8859_1");
+        fIANA2JavaMap.put("IBM-367",        "ASCII");
+
+        // REVISIT:
+        //   j:CNS11643 -> EUC-TW?
+        //   ISO-2022-CN? ISO-2022-CN-EXT?
+                                                
+        // add Java to IANA encoding mappings
+        //fJava2IANAMap.put("8859_1",    "US-ASCII"); // ?
+        fJava2IANAMap.put("ISO8859_1",    "ISO-8859-1");
+        fJava2IANAMap.put("ISO8859_2",    "ISO-8859-2");
+        fJava2IANAMap.put("ISO8859_3",    "ISO-8859-3");
+        fJava2IANAMap.put("ISO8859_4",    "ISO-8859-4");
+        fJava2IANAMap.put("ISO8859_5",    "ISO-8859-5");
+        fJava2IANAMap.put("ISO8859_6",    "ISO-8859-6");
+        fJava2IANAMap.put("ISO8859_7",    "ISO-8859-7");
+        fJava2IANAMap.put("ISO8859_8",    "ISO-8859-8");
+        fJava2IANAMap.put("ISO8859_9",    "ISO-8859-9");
+        fJava2IANAMap.put("ISO8859_13",    "ISO-8859-13");
+        fJava2IANAMap.put("ISO8859_15",    "ISO-8859-15");
+        fJava2IANAMap.put("ISO8859_15_FDIS",    "ISO-8859-15");
+        fJava2IANAMap.put("Big5",      "BIG5");
+        fJava2IANAMap.put("CP037",     "EBCDIC-CP-US");
+        fJava2IANAMap.put("CP273",     "IBM273");
+        fJava2IANAMap.put("CP277",     "EBCDIC-CP-DK");
+        fJava2IANAMap.put("CP278",     "EBCDIC-CP-FI");
+        fJava2IANAMap.put("CP280",     "EBCDIC-CP-IT");
+        fJava2IANAMap.put("CP284",     "EBCDIC-CP-ES");
+        fJava2IANAMap.put("CP285",     "EBCDIC-CP-GB");
+        fJava2IANAMap.put("CP290",     "EBCDIC-JP-KANA");
+        fJava2IANAMap.put("CP297",     "EBCDIC-CP-FR");
+        fJava2IANAMap.put("CP420",     "EBCDIC-CP-AR1");
+        fJava2IANAMap.put("CP424",     "EBCDIC-CP-HE");
+        fJava2IANAMap.put("CP437",     "IBM437");
+        fJava2IANAMap.put("CP500",     "EBCDIC-CP-CH");
+        fJava2IANAMap.put("CP775",     "IBM775");
+        fJava2IANAMap.put("CP850",     "IBM850");
+        fJava2IANAMap.put("CP852",     "IBM852");
+        fJava2IANAMap.put("CP855",     "IBM855");
+        fJava2IANAMap.put("CP857",     "IBM857");
+        fJava2IANAMap.put("CP858",     "IBM00858");
+        fJava2IANAMap.put("CP860",     "IBM860");
+        fJava2IANAMap.put("CP861",     "IBM861");
+        fJava2IANAMap.put("CP862",     "IBM862");
+        fJava2IANAMap.put("CP863",     "IBM863");
+        fJava2IANAMap.put("CP864",     "IBM864");
+        fJava2IANAMap.put("CP865",     "IBM865");
+        fJava2IANAMap.put("CP866",     "IBM866");
+        fJava2IANAMap.put("CP868",     "IBM868");
+        fJava2IANAMap.put("CP869",     "IBM869");
+        fJava2IANAMap.put("CP870",     "EBCDIC-CP-ROECE");
+        fJava2IANAMap.put("CP871",     "EBCDIC-CP-IS");
+        fJava2IANAMap.put("CP918",     "EBCDIC-CP-AR2");
+        fJava2IANAMap.put("CP924",     "IBM00924");
+        fJava2IANAMap.put("CP1026",     "IBM1026");
+        fJava2IANAMap.put("Cp01140",     "IBM01140");
+        fJava2IANAMap.put("Cp01141",     "IBM01141");
+        fJava2IANAMap.put("Cp01142",     "IBM01142");
+        fJava2IANAMap.put("Cp01143",     "IBM01143");
+        fJava2IANAMap.put("Cp01144",     "IBM01144");
+        fJava2IANAMap.put("Cp01145",     "IBM01145");
+        fJava2IANAMap.put("Cp01146",     "IBM01146");
+        fJava2IANAMap.put("Cp01147",     "IBM01147");
+        fJava2IANAMap.put("Cp01148",     "IBM01148");
+        fJava2IANAMap.put("Cp01149",     "IBM01149");
+        fJava2IANAMap.put("EUCJIS",    "EUC-JP");
+        fJava2IANAMap.put("KS_C_5601-1987",          "KS_C_5601-1987");
+        fJava2IANAMap.put("GB2312",    "GB2312");
+        fJava2IANAMap.put("ISO2022KR", "ISO-2022-KR");
+        fJava2IANAMap.put("ISO2022CN", "ISO-2022-CN");
+        fJava2IANAMap.put("JIS",       "ISO-2022-JP");
+        fJava2IANAMap.put("KOI8_R",    "KOI8-R");
+        fJava2IANAMap.put("KSC5601",   "EUC-KR");
+        fJava2IANAMap.put("GB18030",      "GB18030");
+        fJava2IANAMap.put("GBK",       "GBK");
+        fJava2IANAMap.put("SJIS",      "SHIFT_JIS");
+        fJava2IANAMap.put("MS932",      "WINDOWS-31J");
+        fJava2IANAMap.put("UTF8",      "UTF-8");
+        fJava2IANAMap.put("Unicode",   "UTF-16");
+        fJava2IANAMap.put("UnicodeBig",   "UTF-16BE");
+        fJava2IANAMap.put("UnicodeLittle",   "UTF-16LE");
+        fJava2IANAMap.put("JIS0201",  "X0201");
+        fJava2IANAMap.put("JIS0208",  "X0208");
+        fJava2IANAMap.put("JIS0212",  "ISO-IR-159");
+
+        // proposed addition (see above for details):
+        fJava2IANAMap.put("CP1047",    "IBM1047");
+
+    } // <clinit>()
+
+    //
+    // Constructors
+    //
+
+    /** Default constructor. */
+    public EncodingMap() {}
+
+    //
+    // Public static methods
+    //
+
+    /**
+     * Adds an IANA to Java encoding name mapping.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     * @param javaEncoding The Java encoding name.
+     */
+    public static void putIANA2JavaMapping(String ianaEncoding, 
+                                           String javaEncoding) {
+        fIANA2JavaMap.put(ianaEncoding, javaEncoding);
+    } // putIANA2JavaMapping(String,String)
+
+    /**
+     * Returns the Java encoding name for the specified IANA encoding name.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static String getIANA2JavaMapping(String ianaEncoding) {
+        return (String)fIANA2JavaMap.get(ianaEncoding);
+    } // getIANA2JavaMapping(String):String
+
+    /**
+     * Removes an IANA to Java encoding name mapping.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static String removeIANA2JavaMapping(String ianaEncoding) {
+        return (String)fIANA2JavaMap.remove(ianaEncoding);
+    } // removeIANA2JavaMapping(String):String
+
+    /**
+     * Adds a Java to IANA encoding name mapping.
+     * 
+     * @param javaEncoding The Java encoding name.
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static void putJava2IANAMapping(String javaEncoding, 
+                                           String ianaEncoding) {
+        fJava2IANAMap.put(javaEncoding, ianaEncoding);
+    } // putJava2IANAMapping(String,String)
+
+    /**
+     * Returns the IANA encoding name for the specified Java encoding name.
+     * 
+     * @param javaEncoding The Java encoding name.
+     */
+    public static String getJava2IANAMapping(String javaEncoding) {
+        return (String)fJava2IANAMap.get(javaEncoding);
+    } // getJava2IANAMapping(String):String
+
+    /**
+     * Removes a Java to IANA encoding name mapping.
+     * 
+     * @param javaEncoding The Java encoding name.
+     */
+    public static String removeJava2IANAMapping(String javaEncoding) {
+        return (String)fJava2IANAMap.remove(javaEncoding);
+    } // removeJava2IANAMapping
+
+} // class EncodingMap
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java
new file mode 100644
index 0000000..e73f9e4
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java
@@ -0,0 +1,235 @@
+/*
+ * 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.jasper.xmlparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * XML parsing utilities for processing web application deployment
+ * descriptor and tag library descriptor files.  FIXME - make these
+ * use a separate class loader for the parser to be used.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision$ $Date$
+ */
+
+public class ParserUtils {
+
+    /**
+     * An error handler for use when parsing XML documents.
+     */
+    static ErrorHandler errorHandler = new MyErrorHandler();
+
+    /**
+     * An entity resolver for use when parsing XML documents.
+     */
+    static EntityResolver entityResolver = new MyEntityResolver();
+
+    // Turn off for JSP 2.0 until switch over to using xschema.
+    public static boolean validating = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Parse the specified XML document, and return a <code>TreeNode</code>
+     * that corresponds to the root node of the document tree.
+     *
+     * @param uri URI of the XML document being parsed
+     * @param is Input source containing the deployment descriptor
+     *
+     * @exception JasperException if an input/output error occurs
+     * @exception JasperException if a parsing error occurs
+     */
+    public TreeNode parseXMLDocument(String uri, InputSource is)
+        throws JasperException {
+
+        Document document = null;
+
+        // Perform an XML parse of this document, via JAXP
+        try {
+            DocumentBuilderFactory factory =
+                DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.setValidating(validating);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setEntityResolver(entityResolver);
+            builder.setErrorHandler(errorHandler);
+            document = builder.parse(is);
+	} catch (ParserConfigurationException ex) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", uri), ex);
+	} catch (SAXParseException ex) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml.line",
+				      uri,
+				      Integer.toString(ex.getLineNumber()),
+				      Integer.toString(ex.getColumnNumber())),
+		 ex);
+	} catch (SAXException sx) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", uri), sx);
+        } catch (IOException io) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", uri), io);
+	}
+
+        // Convert the resulting document to a graph of TreeNodes
+        return (convert(null, document.getDocumentElement()));
+    }
+
+
+    /**
+     * Parse the specified XML document, and return a <code>TreeNode</code>
+     * that corresponds to the root node of the document tree.
+     *
+     * @param uri URI of the XML document being parsed
+     * @param is Input stream containing the deployment descriptor
+     *
+     * @exception JasperException if an input/output error occurs
+     * @exception JasperException if a parsing error occurs
+     */
+    public TreeNode parseXMLDocument(String uri, InputStream is)
+            throws JasperException {
+
+        return (parseXMLDocument(uri, new InputSource(is)));
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Create and return a TreeNode that corresponds to the specified Node,
+     * including processing all of the attributes and children nodes.
+     *
+     * @param parent The parent TreeNode (if any) for the new TreeNode
+     * @param node The XML document Node to be converted
+     */
+    protected TreeNode convert(TreeNode parent, Node node) {
+
+        // Construct a new TreeNode for this node
+        TreeNode treeNode = new TreeNode(node.getNodeName(), parent);
+
+        // Convert all attributes of this node
+        NamedNodeMap attributes = node.getAttributes();
+        if (attributes != null) {
+            int n = attributes.getLength();
+            for (int i = 0; i < n; i++) {
+                Node attribute = attributes.item(i);
+                treeNode.addAttribute(attribute.getNodeName(),
+                                      attribute.getNodeValue());
+            }
+        }
+
+        // Create and attach all children of this node
+        NodeList children = node.getChildNodes();
+        if (children != null) {
+            int n = children.getLength();
+            for (int i = 0; i < n; i++) {
+                Node child = children.item(i);
+                if (child instanceof Comment)
+                    continue;
+                if (child instanceof Text) {
+                    String body = ((Text) child).getData();
+                    if (body != null) {
+                        body = body.trim();
+                        if (body.length() > 0)
+                            treeNode.setBody(body);
+                    }
+                } else {
+                    TreeNode treeChild = convert(treeNode, child);
+                }
+            }
+        }
+        
+        // Return the completed TreeNode graph
+        return (treeNode);
+    }
+}
+
+
+// ------------------------------------------------------------ Private Classes
+
+class MyEntityResolver implements EntityResolver {
+
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException {
+        for (int i = 0; i < Constants.CACHED_DTD_PUBLIC_IDS.length; i++) {
+            String cachedDtdPublicId = Constants.CACHED_DTD_PUBLIC_IDS[i];
+            if (cachedDtdPublicId.equals(publicId)) {
+                String resourcePath = Constants.CACHED_DTD_RESOURCE_PATHS[i];
+                InputStream input = this.getClass().getResourceAsStream(
+                        resourcePath);
+                if (input == null) {
+                    throw new SAXException(Localizer.getMessage(
+                            "jsp.error.internal.filenotfound", resourcePath));
+                }
+                InputSource isrc = new InputSource(input);
+                return isrc;
+            }
+        }
+        Log log = LogFactory.getLog(MyEntityResolver.class);
+        if (log.isDebugEnabled())
+            log.debug("Resolve entity failed" + publicId + " " + systemId);
+        log.error(Localizer.getMessage("jsp.error.parse.xml.invalidPublicId",
+                publicId));
+        return null;
+    }
+}
+
+class MyErrorHandler implements ErrorHandler {
+
+    public void warning(SAXParseException ex) throws SAXException {
+        Log log = LogFactory.getLog(MyErrorHandler.class);
+        if (log.isDebugEnabled())
+            log.debug("ParserUtils: warning ", ex);
+        // We ignore warnings
+    }
+
+    public void error(SAXParseException ex) throws SAXException {
+        throw ex;
+    }
+
+    public void fatalError(SAXParseException ex) throws SAXException {
+        throw ex;
+    }
+}
\ No newline at end of file
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java
new file mode 100644
index 0000000..ad77e29
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * This class is a symbol table implementation that guarantees that
+ * strings used as identifiers are unique references. Multiple calls
+ * to <code>addSymbol</code> will always return the same string
+ * reference.
+ * <p>
+ * The symbol table performs the same task as <code>String.intern()</code>
+ * with the following differences:
+ * <ul>
+ *  <li>
+ *   A new string object does not need to be created in order to
+ *   retrieve a unique reference. Symbols can be added by using
+ *   a series of characters in a character array.
+ *  </li>
+ *  <li>
+ *   Users of the symbol table can provide their own symbol hashing
+ *   implementation. For example, a simple string hashing algorithm
+ *   may fail to produce a balanced set of hashcodes for symbols
+ *   that are <em>mostly</em> unique. Strings with similar leading
+ *   characters are especially prone to this poor hashing behavior.
+ *  </li>
+ * </ul>
+ *
+ * @author Andy Clark
+ * @version $Id$
+ */
+public class SymbolTable {
+
+    //
+    // Constants
+    //
+
+    /** Default table size. */
+    protected static final int TABLE_SIZE = 101;
+
+    //
+    // Data
+    //
+
+    /** Buckets. */
+    protected Entry[] fBuckets = null;
+
+    // actual table size
+    protected int fTableSize;
+
+    //
+    // Constructors
+    //
+
+    /** Constructs a symbol table with a default number of buckets. */
+    public SymbolTable() {
+        this(TABLE_SIZE);
+    }
+
+    /** Constructs a symbol table with a specified number of buckets. */
+    public SymbolTable(int tableSize) {
+        fTableSize = tableSize;
+        fBuckets = new Entry[fTableSize];
+    }
+
+    //
+    // Public methods
+    //
+
+    /**
+     * Adds the specified symbol to the symbol table and returns a
+     * reference to the unique symbol. If the symbol already exists,
+     * the previous symbol reference is returned instead, in order
+     * guarantee that symbol references remain unique.
+     *
+     * @param symbol The new symbol.
+     */
+    public String addSymbol(String symbol) {
+
+        // search for identical symbol
+        int bucket = hash(symbol) % fTableSize;
+        int length = symbol.length();
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (symbol.charAt(i) != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return entry.symbol;
+            }
+        }
+
+        // create new entry
+        Entry entry = new Entry(symbol, fBuckets[bucket]);
+        fBuckets[bucket] = entry;
+        return entry.symbol;
+
+    } // addSymbol(String):String
+
+    /**
+     * Adds the specified symbol to the symbol table and returns a
+     * reference to the unique symbol. If the symbol already exists,
+     * the previous symbol reference is returned instead, in order
+     * guarantee that symbol references remain unique.
+     *
+     * @param buffer The buffer containing the new symbol.
+     * @param offset The offset into the buffer of the new symbol.
+     * @param length The length of the new symbol in the buffer.
+     */
+    public String addSymbol(char[] buffer, int offset, int length) {
+
+        // search for identical symbol
+        int bucket = hash(buffer, offset, length) % fTableSize;
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (buffer[offset + i] != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return entry.symbol;
+            }
+        }
+
+        // add new entry
+        Entry entry = new Entry(buffer, offset, length, fBuckets[bucket]);
+        fBuckets[bucket] = entry;
+        return entry.symbol;
+
+    } // addSymbol(char[],int,int):String
+
+    /**
+     * Returns a hashcode value for the specified symbol. The value
+     * returned by this method must be identical to the value returned
+     * by the <code>hash(char[],int,int)</code> method when called
+     * with the character array that comprises the symbol string.
+     *
+     * @param symbol The symbol to hash.
+     */
+    public int hash(String symbol) {
+
+        int code = 0;
+        int length = symbol.length();
+        for (int i = 0; i < length; i++) {
+            code = code * 37 + symbol.charAt(i);
+        }
+        return code & 0x7FFFFFF;
+
+    } // hash(String):int
+
+    /**
+     * Returns a hashcode value for the specified symbol information.
+     * The value returned by this method must be identical to the value
+     * returned by the <code>hash(String)</code> method when called
+     * with the string object created from the symbol information.
+     *
+     * @param buffer The character buffer containing the symbol.
+     * @param offset The offset into the character buffer of the start
+     *               of the symbol.
+     * @param length The length of the symbol.
+     */
+    public int hash(char[] buffer, int offset, int length) {
+
+        int code = 0;
+        for (int i = 0; i < length; i++) {
+            code = code * 37 + buffer[offset + i];
+        }
+        return code & 0x7FFFFFF;
+
+    } // hash(char[],int,int):int
+
+    /**
+     * Returns true if the symbol table already contains the specified
+     * symbol.
+     *
+     * @param symbol The symbol to look for.
+     */
+    public boolean containsSymbol(String symbol) {
+
+        // search for identical symbol
+        int bucket = hash(symbol) % fTableSize;
+        int length = symbol.length();
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (symbol.charAt(i) != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return true;
+            }
+        }
+
+        return false;
+
+    } // containsSymbol(String):boolean
+
+    /**
+     * Returns true if the symbol table already contains the specified
+     * symbol.
+     *
+     * @param buffer The buffer containing the symbol to look for.
+     * @param offset The offset into the buffer.
+     * @param length The length of the symbol in the buffer.
+     */
+    public boolean containsSymbol(char[] buffer, int offset, int length) {
+
+        // search for identical symbol
+        int bucket = hash(buffer, offset, length) % fTableSize;
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (buffer[offset + i] != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return true;
+            }
+        }
+
+        return false;
+
+    } // containsSymbol(char[],int,int):boolean
+
+    //
+    // Classes
+    //
+
+    /**
+     * This class is a symbol table entry. Each entry acts as a node
+     * in a linked list.
+     */
+    protected static final class Entry {
+
+        //
+        // Data
+        //
+
+        /** Symbol. */
+        public String symbol;
+
+        /**
+         * Symbol characters. This information is duplicated here for
+         * comparison performance.
+         */
+        public char[] characters;
+
+        /** The next entry. */
+        public Entry next;
+
+        //
+        // Constructors
+        //
+
+        /**
+         * Constructs a new entry from the specified symbol and next entry
+         * reference.
+         */
+        public Entry(String symbol, Entry next) {
+            this.symbol = symbol.intern();
+            characters = new char[symbol.length()];
+            symbol.getChars(0, characters.length, characters, 0);
+            this.next = next;
+        }
+
+        /**
+         * Constructs a new entry from the specified symbol information and
+         * next entry reference.
+         */
+        public Entry(char[] ch, int offset, int length, Entry next) {
+            characters = new char[length];
+            System.arraycopy(ch, offset, characters, 0, length);
+            symbol = new String(characters).intern();
+            this.next = next;
+        }
+
+    } // class Entry
+
+} // class SymbolTable
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java
new file mode 100644
index 0000000..4ccf97b
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java
@@ -0,0 +1,360 @@
+/*
+ * 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.jasper.xmlparser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+
+
+/**
+ * Simplified implementation of a Node from a Document Object Model (DOM)
+ * parse of an XML document.  This class is used to represent a DOM tree
+ * so that the XML parser's implementation of <code>org.w3c.dom</code> need
+ * not be visible to the remainder of Jasper.
+ * <p>
+ * <strong>WARNING</strong> - Construction of a new tree, or modifications
+ * to an existing one, are not thread-safe and such accesses must be
+ * synchronized.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision$ $Date$
+ */
+
+public class TreeNode {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new node with no parent.
+     *
+     * @param name The name of this node
+     */
+    public TreeNode(String name) {
+
+        this(name, null);
+
+    }
+
+
+    /**
+     * Construct a new node with the specified parent.
+     *
+     * @param name The name of this node
+     * @param parent The node that is the parent of this node
+     */
+    public TreeNode(String name, TreeNode parent) {
+
+        super();
+        this.name = name;
+        this.parent = parent;
+        if (this.parent != null)
+            this.parent.addChild(this);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attributes of this node, keyed by attribute name,
+     * Instantiated only if required.
+     */
+    protected HashMap attributes = null;
+
+
+    /**
+     * The body text associated with this node (if any).
+     */
+    protected String body = null;
+
+
+    /**
+     * The children of this node, instantiated only if required.
+     */
+    protected ArrayList children = null;
+
+
+    /**
+     * The name of this node.
+     */
+    protected String name = null;
+
+
+    /**
+     * The parent node of this node.
+     */
+    protected TreeNode parent = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an attribute to this node, replacing any existing attribute
+     * with the same name.
+     *
+     * @param name The attribute name to add
+     * @param value The new attribute value
+     */
+    public void addAttribute(String name, String value) {
+
+        if (attributes == null)
+            attributes = new HashMap();
+        attributes.put(name, value);
+
+    }
+
+
+    /**
+     * Add a new child node to this node.
+     *
+     * @param node The new child node
+     */
+    public void addChild(TreeNode node) {
+
+        if (children == null)
+            children = new ArrayList();
+        children.add(node);
+
+    }
+
+
+    /**
+     * Return the value of the specified node attribute if it exists, or
+     * <code>null</code> otherwise.
+     *
+     * @param name Name of the requested attribute
+     */
+    public String findAttribute(String name) {
+
+        if (attributes == null)
+            return (null);
+        else
+            return ((String) attributes.get(name));
+
+    }
+
+
+    /**
+     * Return an Iterator of the attribute names of this node.  If there are
+     * no attributes, an empty Iterator is returned.
+     */
+    public Iterator findAttributes() {
+
+        if (attributes == null)
+            return (Collections.EMPTY_LIST.iterator());
+        else
+            return (attributes.keySet().iterator());
+
+    }
+
+
+    /**
+     * Return the first child node of this node with the specified name,
+     * if there is one; otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired child element
+     */
+    public TreeNode findChild(String name) {
+
+        if (children == null)
+            return (null);
+        Iterator items = children.iterator();
+        while (items.hasNext()) {
+            TreeNode item = (TreeNode) items.next();
+            if (name.equals(item.getName()))
+                return (item);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Return an Iterator of all children of this node.  If there are no
+     * children, an empty Iterator is returned.
+     */
+    public Iterator findChildren() {
+
+        if (children == null)
+            return (Collections.EMPTY_LIST.iterator());
+        else
+            return (children.iterator());
+
+    }
+
+
+    /**
+     * Return an Iterator over all children of this node that have the
+     * specified name.  If there are no such children, an empty Iterator
+     * is returned.
+     *
+     * @param name Name used to select children
+     */
+    public Iterator findChildren(String name) {
+
+        if (children == null)
+            return (Collections.EMPTY_LIST.iterator());
+
+        ArrayList results = new ArrayList();
+        Iterator items = children.iterator();
+        while (items.hasNext()) {
+            TreeNode item = (TreeNode) items.next();
+            if (name.equals(item.getName()))
+                results.add(item);
+        }
+        return (results.iterator());
+
+    }
+
+
+    /**
+     * Return the body text associated with this node (if any).
+     */
+    public String getBody() {
+
+        return (this.body);
+
+    }
+
+
+    /**
+     * Return the name of this node.
+     */
+    public String getName() {
+
+        return (this.name);
+
+    }
+
+
+    /**
+     * Remove any existing value for the specified attribute name.
+     *
+     * @param name The attribute name to remove
+     */
+    public void removeAttribute(String name) {
+
+        if (attributes != null)
+            attributes.remove(name);
+
+    }
+
+
+    /**
+     * Remove a child node from this node, if it is one.
+     *
+     * @param node The child node to remove
+     */
+    public void removeNode(TreeNode node) {
+
+        if (children != null)
+            children.remove(node);
+
+    }
+
+
+    /**
+     * Set the body text associated with this node (if any).
+     *
+     * @param body The body text (if any)
+     */
+    public void setBody(String body) {
+
+        this.body = body;
+
+    }
+
+
+    /**
+     * Return a String representation of this TreeNode.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer();
+        toString(sb, 0, this);
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Append to the specified StringBuffer a character representation of
+     * this node, with the specified amount of indentation.
+     *
+     * @param sb The StringBuffer to append to
+     * @param indent Number of characters of indentation
+     * @param node The TreeNode to be printed
+     */
+    protected void toString(StringBuffer sb, int indent,
+                            TreeNode node) {
+
+        int indent2 = indent + 2;
+
+        // Reconstruct an opening node
+        for (int i = 0; i < indent; i++)
+            sb.append(' ');
+        sb.append('<');
+        sb.append(node.getName());
+        Iterator names = node.findAttributes();
+        while (names.hasNext()) {
+            sb.append(' ');
+            String name = (String) names.next();
+            sb.append(name);
+            sb.append("=\"");
+            String value = node.findAttribute(name);
+            sb.append(value);
+            sb.append("\"");
+        }
+        sb.append(">\n");
+
+        // Reconstruct the body text of this node (if any)
+        String body = node.getBody();
+        if ((body != null) && (body.length() > 0)) {
+            for (int i = 0; i < indent2; i++)
+                sb.append(' ');
+            sb.append(body);
+            sb.append("\n");
+        }
+
+        // Reconstruct child nodes with extra indentation
+        Iterator children = node.findChildren();
+        while (children.hasNext()) {
+            TreeNode child = (TreeNode) children.next();
+            toString(sb, indent2, child);
+        }
+
+        // Reconstruct a closing node marker
+        for (int i = 0; i < indent; i++)
+            sb.append(' ');
+        sb.append("</");
+        sb.append(node.getName());
+        sb.append(">\n");
+
+    }
+
+
+}
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java
new file mode 100644
index 0000000..8f3de57
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java
@@ -0,0 +1,301 @@
+/*
+ * 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.jasper.xmlparser;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.Reader;
+
+/** 
+ * Reader for UCS-2 and UCS-4 encodings.
+ * (i.e., encodings from ISO-10646-UCS-(2|4)).
+ *
+ * @author Neil Graham, IBM
+ *
+ * @version $Id$
+ */
+public class UCSReader extends Reader {
+
+    private org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UCSReader.class );
+    
+    //
+    // Constants
+    //
+
+    /** Default byte buffer size (8192, larger than that of ASCIIReader
+     * since it's reasonable to surmise that the average UCS-4-encoded
+     * file should be 4 times as large as the average ASCII-encoded file). 
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    public static final short UCS2LE = 1;
+    public static final short UCS2BE = 2;
+    public static final short UCS4LE = 4;
+    public static final short UCS4BE = 8;
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    // what kind of data we're dealing with
+    protected short fEncoding;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * using the default buffer size.  The Endian-ness and whether this is
+     * UCS-2 or UCS-4 needs also to be known in advance.
+     *
+     * @param inputStream The input stream.
+     * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE.
+     */
+    public UCSReader(InputStream inputStream, short encoding) {
+        this(inputStream, DEFAULT_BUFFER_SIZE, encoding);
+    } // <init>(InputStream, short)
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * and buffer size.  The Endian-ness and whether this is
+     * UCS-2 or UCS-4 needs also to be known in advance.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE.
+     */
+    public UCSReader(InputStream inputStream, int size, short encoding) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+        fEncoding = encoding;
+    } // <init>(InputStream,int,short)
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 127
+     *             (<tt>0x00-0x7f</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read() throws IOException { 
+        int b0 = fInputStream.read() & 0xff;
+        if (b0 == 0xff)
+            return -1;
+        int b1 = fInputStream.read() & 0xff;
+        if (b1 == 0xff)
+            return -1;
+        if(fEncoding >=4) {
+            int b2 = fInputStream.read() & 0xff;
+            if (b2 == 0xff)
+                return -1;
+            int b3 = fInputStream.read() & 0xff;
+            if (b3 == 0xff)
+                return -1;
+            if (log.isDebugEnabled())
+                log.debug("b0 is " + (b0 & 0xff) + " b1 " + (b1 & 0xff) + " b2 " + (b2 & 0xff) + " b3 " + (b3 & 0xff));
+            if (fEncoding == UCS4BE)
+                return (b0<<24)+(b1<<16)+(b2<<8)+b3;
+            else
+                return (b3<<24)+(b2<<16)+(b1<<8)+b0;
+        } else { // UCS-2
+            if (fEncoding == UCS2BE)
+                return (b0<<8)+b1;
+            else
+                return (b1<<8)+b0;
+        }
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read(char ch[], int offset, int length) throws IOException {
+        int byteLength = length << ((fEncoding >= 4)?2:1);
+        if (byteLength > fBuffer.length) {
+            byteLength = fBuffer.length;
+        }
+        int count = fInputStream.read(fBuffer, 0, byteLength);
+        if(count == -1) return -1;
+        // try and make count be a multiple of the number of bytes we're looking for
+        if(fEncoding >= 4) { // BigEndian
+            // this looks ugly, but it avoids an if at any rate...
+            int numToRead = (4 - (count & 3) & 3);
+            for(int i=0; i<numToRead; i++) {
+                int charRead = fInputStream.read();
+                if(charRead == -1) { // end of input; something likely went wrong!A  Pad buffer with nulls.
+                    for (int j = i;j<numToRead; j++)
+                        fBuffer[count+j] = 0;
+                    break;
+                } else {
+                    fBuffer[count+i] = (byte)charRead; 
+                }
+            }
+            count += numToRead;
+        } else {
+            int numToRead = count & 1;
+            if(numToRead != 0) {
+                count++;
+                int charRead = fInputStream.read();
+                if(charRead == -1) { // end of input; something likely went wrong!A  Pad buffer with nulls.
+                    fBuffer[count] = 0;
+                } else {
+                    fBuffer[count] = (byte)charRead;
+                }
+            }
+        }
+
+        // now count is a multiple of the right number of bytes
+        int numChars = count >> ((fEncoding >= 4)?2:1);
+        int curPos = 0;
+        for (int i = 0; i < numChars; i++) {
+            int b0 = fBuffer[curPos++] & 0xff;
+            int b1 = fBuffer[curPos++] & 0xff;
+            if(fEncoding >=4) {
+                int b2 = fBuffer[curPos++] & 0xff;
+                int b3 = fBuffer[curPos++] & 0xff;
+                if (fEncoding == UCS4BE)
+                    ch[offset+i] = (char)((b0<<24)+(b1<<16)+(b2<<8)+b3);
+                else
+                    ch[offset+i] = (char)((b3<<24)+(b2<<16)+(b1<<8)+b0);
+            } else { // UCS-2
+                if (fEncoding == UCS2BE)
+                    ch[offset+i] = (char)((b0<<8)+b1);
+                else
+                    ch[offset+i] = (char)((b1<<8)+b0);
+            }
+        }
+        return numChars;
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public long skip(long n) throws IOException {
+        // charWidth will represent the number of bits to move
+        // n leftward to get num of bytes to skip, and then move the result rightward
+        // to get num of chars effectively skipped.
+        // The trick with &'ing, as with elsewhere in this dcode, is
+        // intended to avoid an expensive use of / that might not be optimized
+        // away.
+        int charWidth = (fEncoding >=4)?2:1;
+        long bytesSkipped = fInputStream.skip(n<<charWidth);
+        if((bytesSkipped & (charWidth | 1)) == 0) return bytesSkipped >> charWidth;
+        return (bytesSkipped >> charWidth) + 1;
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public boolean ready() throws IOException {
+	return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    public boolean markSupported() {
+	return fInputStream.markSupported();
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    public void mark(int readAheadLimit) throws IOException {
+	fInputStream.mark(readAheadLimit);
+    } // mark(int)
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    public void reset() throws IOException {
+        fInputStream.reset();
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+     public void close() throws IOException {
+         fInputStream.close();
+     } // close()
+
+} // class UCSReader
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java
new file mode 100644
index 0000000..c562e4c
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java
@@ -0,0 +1,635 @@
+/*
+ * 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.jasper.xmlparser;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.UTFDataFormatException;
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * @author Andy Clark, IBM
+ *
+ * @version $Id$
+ */
+public class UTF8Reader
+    extends Reader {
+
+    private org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UTF8Reader.class );
+    
+    //
+    // Constants
+    //
+
+    /** Default byte buffer size (2048). */
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+
+    // debugging
+
+    /** Debug read. */
+    private static final boolean DEBUG_READ = false;
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    /** Offset into buffer. */
+    protected int fOffset;
+
+    /** Surrogate character. */
+    private int fSurrogate = -1;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs a UTF-8 reader from the specified input stream, 
+     * buffer size and MessageFormatter.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     */
+    public UTF8Reader(InputStream inputStream, int size) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+    }
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 16383
+     *             (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read() throws IOException {
+
+        // decode character
+        int c = fSurrogate;
+        if (fSurrogate == -1) {
+            // NOTE: We use the index into the buffer if there are remaining
+            //       bytes from the last block read. -Ac
+            int index = 0;
+
+            // get first byte
+            int b0 = index == fOffset 
+                   ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+            if (b0 == -1) {
+                return -1;
+            }
+
+            // UTF-8:   [0xxx xxxx]
+            // Unicode: [0000 0000] [0xxx xxxx]
+            if (b0 < 0x80) {
+                c = (char)b0;
+            }
+
+            // UTF-8:   [110y yyyy] [10xx xxxx]
+            // Unicode: [0000 0yyy] [yyxx xxxx]
+            else if ((b0 & 0xE0) == 0xC0) {
+                int b1 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 2);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 2, b1);
+                }
+                c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
+            }
+
+            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
+            // Unicode: [zzzz yyyy] [yyxx xxxx]
+            else if ((b0 & 0xF0) == 0xE0) {
+                int b1 = index == fOffset
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 3);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 3, b1);
+                }
+                int b2 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b2 == -1) {
+                    expectedByte(3, 3);
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    invalidByte(3, 3, b2);
+                }
+                c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
+                    (b2 & 0x003F);
+            }
+
+            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
+            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
+            //          [1101 11yy] [yyxx xxxx] (low surrogate)
+            //          * uuuuu = wwww + 1
+            else if ((b0 & 0xF8) == 0xF0) {
+                int b1 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 4);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 3, b1);
+                }
+                int b2 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b2 == -1) {
+                    expectedByte(3, 4);
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    invalidByte(3, 3, b2);
+                }
+                int b3 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b3 == -1) {
+                    expectedByte(4, 4);
+                }
+                if ((b3 & 0xC0) != 0x80) {
+                    invalidByte(4, 4, b3);
+                }
+                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
+                if (uuuuu > 0x10) {
+                    invalidSurrogate(uuuuu);
+                }
+                int wwww = uuuuu - 1;
+                int hs = 0xD800 | 
+                         ((wwww << 6) & 0x03C0) | ((b1 << 2) & 0x003C) | 
+                         ((b2 >> 4) & 0x0003);
+                int ls = 0xDC00 | ((b2 << 6) & 0x03C0) | (b3 & 0x003F);
+                c = hs;
+                fSurrogate = ls;
+            }
+
+            // error
+            else {
+                invalidByte(1, 1, b0);
+            }
+        }
+
+        // use surrogate
+        else {
+            fSurrogate = -1;
+        }
+
+        // return character
+        if (DEBUG_READ) {
+            if (log.isDebugEnabled())
+                log.debug("read(): 0x"+Integer.toHexString(c));
+        }
+        return c;
+
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public int read(char ch[], int offset, int length) throws IOException {
+
+        // handle surrogate
+        int out = offset;
+        if (fSurrogate != -1) {
+            ch[offset + 1] = (char)fSurrogate;
+            fSurrogate = -1;
+            length--;
+            out++;
+        }
+
+        // read bytes
+        int count = 0;
+        if (fOffset == 0) {
+            // adjust length to read
+            if (length > fBuffer.length) {
+                length = fBuffer.length;
+            }
+
+            // perform read operation
+            count = fInputStream.read(fBuffer, 0, length);
+            if (count == -1) {
+                return -1;
+            }
+            count += out - offset;
+        }
+
+        // skip read; last character was in error
+        // NOTE: Having an offset value other than zero means that there was
+        //       an error in the last character read. In this case, we have
+        //       skipped the read so we don't consume any bytes past the 
+        //       error. By signalling the error on the next block read we
+        //       allow the method to return the most valid characters that
+        //       it can on the previous block read. -Ac
+        else {
+            count = fOffset;
+            fOffset = 0;
+        }
+
+        // convert bytes to characters
+        final int total = count;
+        for (int in = 0; in < total; in++) {
+            int b0 = fBuffer[in] & 0x00FF;
+
+            // UTF-8:   [0xxx xxxx]
+            // Unicode: [0000 0000] [0xxx xxxx]
+            if (b0 < 0x80) {
+                ch[out++] = (char)b0;
+                continue;
+            }
+
+            // UTF-8:   [110y yyyy] [10xx xxxx]
+            // Unicode: [0000 0yyy] [yyxx xxxx]
+            if ((b0 & 0xE0) == 0xC0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 2);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 2, b1);
+                }
+                int c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
+                ch[out++] = (char)c;
+                count -= 1;
+                continue;
+            }
+
+            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
+            // Unicode: [zzzz yyyy] [yyxx xxxx]
+            if ((b0 & 0xF0) == 0xE0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 3);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 3, b1);
+                }
+                int b2 = -1;
+                if (++in < total) { 
+                    b2 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b2 = fInputStream.read();
+                    if (b2 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fOffset = 2;
+                            return out - offset;
+                        }
+                        expectedByte(3, 3);
+                    }
+                    count++;
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fOffset = 3;
+                        return out - offset;
+                    }
+                    invalidByte(3, 3, b2);
+                }
+                int c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
+                        (b2 & 0x003F);
+                ch[out++] = (char)c;
+                count -= 2;
+                continue;
+            }
+
+            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
+            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
+            //          [1101 11yy] [yyxx xxxx] (low surrogate)
+            //          * uuuuu = wwww + 1
+            if ((b0 & 0xF8) == 0xF0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 4);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 4, b1);
+                }
+                int b2 = -1;
+                if (++in < total) { 
+                    b2 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b2 = fInputStream.read();
+                    if (b2 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fOffset = 2;
+                            return out - offset;
+                        }
+                        expectedByte(3, 4);
+                    }
+                    count++;
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fOffset = 3;
+                        return out - offset;
+                    }
+                    invalidByte(3, 4, b2);
+                }
+                int b3 = -1;
+                if (++in < total) { 
+                    b3 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b3 = fInputStream.read();
+                    if (b3 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fBuffer[2] = (byte)b2;
+                            fOffset = 3;
+                            return out - offset;
+                        }
+                        expectedByte(4, 4);
+                    }
+                    count++;
+                }
+                if ((b3 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fBuffer[3] = (byte)b3;
+                        fOffset = 4;
+                        return out - offset;
+                    }
+                    invalidByte(4, 4, b2);
+                }
+
+                // decode bytes into surrogate characters
+                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
+                if (uuuuu > 0x10) {
+                    invalidSurrogate(uuuuu);
+                }
+                int wwww = uuuuu - 1;
+                int zzzz = b1 & 0x000F;
+                int yyyyyy = b2 & 0x003F;
+                int xxxxxx = b3 & 0x003F;
+                int hs = 0xD800 | ((wwww << 6) & 0x03C0) | (zzzz << 2) | (yyyyyy >> 4);
+                int ls = 0xDC00 | ((yyyyyy << 6) & 0x03C0) | xxxxxx;
+
+                // set characters
+                ch[out++] = (char)hs;
+                ch[out++] = (char)ls;
+                count -= 2;
+                continue;
+            }
+
+            // error
+            if (out > offset) {
+                fBuffer[0] = (byte)b0;
+                fOffset = 1;
+                return out - offset;
+            }
+            invalidByte(1, 1, b0);
+        }
+
+        // return number of characters converted
+        if (DEBUG_READ) {
+            if (log.isDebugEnabled())
+                log.debug("read(char[],"+offset+','+length+"): count="+count);
+        }
+        return count;
+
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public long skip(long n) throws IOException {
+
+        long remaining = n;
+        final char[] ch = new char[fBuffer.length];
+        do {
+            int length = ch.length < remaining ? ch.length : (int)remaining;
+            int count = read(ch, 0, length);
+            if (count > 0) {
+                remaining -= count;
+            }
+            else {
+                break;
+            }
+        } while (remaining > 0);
+
+        long skipped = n - remaining;
+        return skipped;
+
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public boolean ready() throws IOException {
+	    return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    public boolean markSupported() {
+	    return false;
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    public void mark(int readAheadLimit) throws IOException {
+	throw new IOException(
+                Localizer.getMessage("jsp.error.xml.operationNotSupported",
+				     "mark()", "UTF-8"));
+    }
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    public void reset() throws IOException {
+        fOffset = 0;
+        fSurrogate = -1;
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    public void close() throws IOException {
+        fInputStream.close();
+    } // close()
+
+    //
+    // Private methods
+    //
+
+    /** Throws an exception for expected byte. */
+    private void expectedByte(int position, int count)
+        throws UTFDataFormatException {
+
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.expectedByte",
+				     Integer.toString(position),
+				     Integer.toString(count)));
+
+    } // expectedByte(int,int,int)
+
+    /** Throws an exception for invalid byte. */
+    private void invalidByte(int position, int count, int c) 
+        throws UTFDataFormatException {
+
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.invalidByte",
+				     Integer.toString(position),
+				     Integer.toString(count)));
+    } // invalidByte(int,int,int,int)
+
+    /** Throws an exception for invalid surrogate bits. */
+    private void invalidSurrogate(int uuuuu) throws UTFDataFormatException {
+        
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.invalidHighSurrogate",
+				     Integer.toHexString(uuuuu)));
+    } // invalidSurrogate(int)
+
+} // class UTF8Reader
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java
new file mode 100644
index 0000000..4e022ec
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java
@@ -0,0 +1,1031 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.util.Arrays;
+
+/**
+ * This class defines the basic XML character properties. The data
+ * in this class can be used to verify that a character is a valid
+ * XML character or if the character is a space, name start, or name
+ * character.
+ * <p>
+ * A series of convenience methods are supplied to ease the burden
+ * of the developer. Because inlining the checks can improve per
+ * character performance, the tables of character properties are
+ * public. Using the character as an index into the <code>CHARS</code>
+ * array and applying the appropriate mask flag (e.g.
+ * <code>MASK_VALID</code>), yields the same results as calling the
+ * convenience methods. There is one exception: check the comments
+ * for the <code>isValid</code> method for details.
+ *
+ * @author Glenn Marcy, IBM
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ * @author Arnaud  Le Hors, IBM
+ * @author Michael Glavassevich, IBM
+ * @author Rahul Srivastava, Sun Microsystems Inc.
+ *
+ * @version $Id$
+ */
+public class XMLChar {
+
+    //
+    // Constants
+    //
+
+    /** Character flags. */
+    private static final byte[] CHARS = new byte[1 << 16];
+
+    /** Valid character mask. */
+    public static final int MASK_VALID = 0x01;
+
+    /** Space character mask. */
+    public static final int MASK_SPACE = 0x02;
+
+    /** Name start character mask. */
+    public static final int MASK_NAME_START = 0x04;
+
+    /** Name character mask. */
+    public static final int MASK_NAME = 0x08;
+
+    /** Pubid character mask. */
+    public static final int MASK_PUBID = 0x10;
+    
+    /** 
+     * Content character mask. Special characters are those that can
+     * be considered the start of markup, such as '&lt;' and '&amp;'. 
+     * The various newline characters are considered special as well.
+     * All other valid XML characters can be considered content.
+     * <p>
+     * This is an optimization for the inner loop of character scanning.
+     */
+    public static final int MASK_CONTENT = 0x20;
+
+    /** NCName start character mask. */
+    public static final int MASK_NCNAME_START = 0x40;
+
+    /** NCName character mask. */
+    public static final int MASK_NCNAME = 0x80;
+
+    //
+    // Static initialization
+    //
+
+    static {
+        
+        // Initializing the Character Flag Array
+        // Code generated by: XMLCharGenerator.
+        
+        CHARS[9] = 35;
+        CHARS[10] = 19;
+        CHARS[13] = 19;
+        CHARS[32] = 51;
+        CHARS[33] = 49;
+        CHARS[34] = 33;
+        Arrays.fill(CHARS, 35, 38, (byte) 49 ); // Fill 3 of value (byte) 49
+        CHARS[38] = 1;
+        Arrays.fill(CHARS, 39, 45, (byte) 49 ); // Fill 6 of value (byte) 49
+        Arrays.fill(CHARS, 45, 47, (byte) -71 ); // Fill 2 of value (byte) -71
+        CHARS[47] = 49;
+        Arrays.fill(CHARS, 48, 58, (byte) -71 ); // Fill 10 of value (byte) -71
+        CHARS[58] = 61;
+        CHARS[59] = 49;
+        CHARS[60] = 1;
+        CHARS[61] = 49;
+        CHARS[62] = 33;
+        Arrays.fill(CHARS, 63, 65, (byte) 49 ); // Fill 2 of value (byte) 49
+        Arrays.fill(CHARS, 65, 91, (byte) -3 ); // Fill 26 of value (byte) -3
+        Arrays.fill(CHARS, 91, 93, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[93] = 1;
+        CHARS[94] = 33;
+        CHARS[95] = -3;
+        CHARS[96] = 33;
+        Arrays.fill(CHARS, 97, 123, (byte) -3 ); // Fill 26 of value (byte) -3
+        Arrays.fill(CHARS, 123, 183, (byte) 33 ); // Fill 60 of value (byte) 33
+        CHARS[183] = -87;
+        Arrays.fill(CHARS, 184, 192, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 192, 215, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[215] = 33;
+        Arrays.fill(CHARS, 216, 247, (byte) -19 ); // Fill 31 of value (byte) -19
+        CHARS[247] = 33;
+        Arrays.fill(CHARS, 248, 306, (byte) -19 ); // Fill 58 of value (byte) -19
+        Arrays.fill(CHARS, 306, 308, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 308, 319, (byte) -19 ); // Fill 11 of value (byte) -19
+        Arrays.fill(CHARS, 319, 321, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 321, 329, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[329] = 33;
+        Arrays.fill(CHARS, 330, 383, (byte) -19 ); // Fill 53 of value (byte) -19
+        CHARS[383] = 33;
+        Arrays.fill(CHARS, 384, 452, (byte) -19 ); // Fill 68 of value (byte) -19
+        Arrays.fill(CHARS, 452, 461, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 461, 497, (byte) -19 ); // Fill 36 of value (byte) -19
+        Arrays.fill(CHARS, 497, 500, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 500, 502, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 502, 506, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 506, 536, (byte) -19 ); // Fill 30 of value (byte) -19
+        Arrays.fill(CHARS, 536, 592, (byte) 33 ); // Fill 56 of value (byte) 33
+        Arrays.fill(CHARS, 592, 681, (byte) -19 ); // Fill 89 of value (byte) -19
+        Arrays.fill(CHARS, 681, 699, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 699, 706, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 706, 720, (byte) 33 ); // Fill 14 of value (byte) 33
+        Arrays.fill(CHARS, 720, 722, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 722, 768, (byte) 33 ); // Fill 46 of value (byte) 33
+        Arrays.fill(CHARS, 768, 838, (byte) -87 ); // Fill 70 of value (byte) -87
+        Arrays.fill(CHARS, 838, 864, (byte) 33 ); // Fill 26 of value (byte) 33
+        Arrays.fill(CHARS, 864, 866, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 866, 902, (byte) 33 ); // Fill 36 of value (byte) 33
+        CHARS[902] = -19;
+        CHARS[903] = -87;
+        Arrays.fill(CHARS, 904, 907, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[907] = 33;
+        CHARS[908] = -19;
+        CHARS[909] = 33;
+        Arrays.fill(CHARS, 910, 930, (byte) -19 ); // Fill 20 of value (byte) -19
+        CHARS[930] = 33;
+        Arrays.fill(CHARS, 931, 975, (byte) -19 ); // Fill 44 of value (byte) -19
+        CHARS[975] = 33;
+        Arrays.fill(CHARS, 976, 983, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 983, 986, (byte) 33 ); // Fill 3 of value (byte) 33
+        CHARS[986] = -19;
+        CHARS[987] = 33;
+        CHARS[988] = -19;
+        CHARS[989] = 33;
+        CHARS[990] = -19;
+        CHARS[991] = 33;
+        CHARS[992] = -19;
+        CHARS[993] = 33;
+        Arrays.fill(CHARS, 994, 1012, (byte) -19 ); // Fill 18 of value (byte) -19
+        Arrays.fill(CHARS, 1012, 1025, (byte) 33 ); // Fill 13 of value (byte) 33
+        Arrays.fill(CHARS, 1025, 1037, (byte) -19 ); // Fill 12 of value (byte) -19
+        CHARS[1037] = 33;
+        Arrays.fill(CHARS, 1038, 1104, (byte) -19 ); // Fill 66 of value (byte) -19
+        CHARS[1104] = 33;
+        Arrays.fill(CHARS, 1105, 1117, (byte) -19 ); // Fill 12 of value (byte) -19
+        CHARS[1117] = 33;
+        Arrays.fill(CHARS, 1118, 1154, (byte) -19 ); // Fill 36 of value (byte) -19
+        CHARS[1154] = 33;
+        Arrays.fill(CHARS, 1155, 1159, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 1159, 1168, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 1168, 1221, (byte) -19 ); // Fill 53 of value (byte) -19
+        Arrays.fill(CHARS, 1221, 1223, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1223, 1225, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1225, 1227, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1227, 1229, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1229, 1232, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 1232, 1260, (byte) -19 ); // Fill 28 of value (byte) -19
+        Arrays.fill(CHARS, 1260, 1262, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1262, 1270, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 1270, 1272, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1272, 1274, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1274, 1329, (byte) 33 ); // Fill 55 of value (byte) 33
+        Arrays.fill(CHARS, 1329, 1367, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 1367, 1369, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[1369] = -19;
+        Arrays.fill(CHARS, 1370, 1377, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 1377, 1415, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 1415, 1425, (byte) 33 ); // Fill 10 of value (byte) 33
+        Arrays.fill(CHARS, 1425, 1442, (byte) -87 ); // Fill 17 of value (byte) -87
+        CHARS[1442] = 33;
+        Arrays.fill(CHARS, 1443, 1466, (byte) -87 ); // Fill 23 of value (byte) -87
+        CHARS[1466] = 33;
+        Arrays.fill(CHARS, 1467, 1470, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[1470] = 33;
+        CHARS[1471] = -87;
+        CHARS[1472] = 33;
+        Arrays.fill(CHARS, 1473, 1475, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[1475] = 33;
+        CHARS[1476] = -87;
+        Arrays.fill(CHARS, 1477, 1488, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 1488, 1515, (byte) -19 ); // Fill 27 of value (byte) -19
+        Arrays.fill(CHARS, 1515, 1520, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 1520, 1523, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 1523, 1569, (byte) 33 ); // Fill 46 of value (byte) 33
+        Arrays.fill(CHARS, 1569, 1595, (byte) -19 ); // Fill 26 of value (byte) -19
+        Arrays.fill(CHARS, 1595, 1600, (byte) 33 ); // Fill 5 of value (byte) 33
+        CHARS[1600] = -87;
+        Arrays.fill(CHARS, 1601, 1611, (byte) -19 ); // Fill 10 of value (byte) -19
+        Arrays.fill(CHARS, 1611, 1619, (byte) -87 ); // Fill 8 of value (byte) -87
+        Arrays.fill(CHARS, 1619, 1632, (byte) 33 ); // Fill 13 of value (byte) 33
+        Arrays.fill(CHARS, 1632, 1642, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 1642, 1648, (byte) 33 ); // Fill 6 of value (byte) 33
+        CHARS[1648] = -87;
+        Arrays.fill(CHARS, 1649, 1720, (byte) -19 ); // Fill 71 of value (byte) -19
+        Arrays.fill(CHARS, 1720, 1722, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1722, 1727, (byte) -19 ); // Fill 5 of value (byte) -19
+        CHARS[1727] = 33;
+        Arrays.fill(CHARS, 1728, 1743, (byte) -19 ); // Fill 15 of value (byte) -19
+        CHARS[1743] = 33;
+        Arrays.fill(CHARS, 1744, 1748, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[1748] = 33;
+        CHARS[1749] = -19;
+        Arrays.fill(CHARS, 1750, 1765, (byte) -87 ); // Fill 15 of value (byte) -87
+        Arrays.fill(CHARS, 1765, 1767, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1767, 1769, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[1769] = 33;
+        Arrays.fill(CHARS, 1770, 1774, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 1774, 1776, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1776, 1786, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 1786, 2305, (byte) 33 ); // Fill 519 of value (byte) 33
+        Arrays.fill(CHARS, 2305, 2308, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2308] = 33;
+        Arrays.fill(CHARS, 2309, 2362, (byte) -19 ); // Fill 53 of value (byte) -19
+        Arrays.fill(CHARS, 2362, 2364, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2364] = -87;
+        CHARS[2365] = -19;
+        Arrays.fill(CHARS, 2366, 2382, (byte) -87 ); // Fill 16 of value (byte) -87
+        Arrays.fill(CHARS, 2382, 2385, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2385, 2389, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 2389, 2392, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2392, 2402, (byte) -19 ); // Fill 10 of value (byte) -19
+        Arrays.fill(CHARS, 2402, 2404, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2404, 2406, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2406, 2416, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2416, 2433, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 2433, 2436, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2436] = 33;
+        Arrays.fill(CHARS, 2437, 2445, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 2445, 2447, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2447, 2449, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2449, 2451, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2451, 2473, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2473] = 33;
+        Arrays.fill(CHARS, 2474, 2481, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2481] = 33;
+        CHARS[2482] = -19;
+        Arrays.fill(CHARS, 2483, 2486, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2486, 2490, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2490, 2492, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2492] = -87;
+        CHARS[2493] = 33;
+        Arrays.fill(CHARS, 2494, 2501, (byte) -87 ); // Fill 7 of value (byte) -87
+        Arrays.fill(CHARS, 2501, 2503, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2503, 2505, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2505, 2507, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2507, 2510, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2510, 2519, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[2519] = -87;
+        Arrays.fill(CHARS, 2520, 2524, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2524, 2526, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2526] = 33;
+        Arrays.fill(CHARS, 2527, 2530, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2530, 2532, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2532, 2534, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2534, 2544, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2544, 2546, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2546, 2562, (byte) 33 ); // Fill 16 of value (byte) 33
+        CHARS[2562] = -87;
+        Arrays.fill(CHARS, 2563, 2565, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2565, 2571, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 2571, 2575, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2575, 2577, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2577, 2579, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2579, 2601, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2601] = 33;
+        Arrays.fill(CHARS, 2602, 2609, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2609] = 33;
+        Arrays.fill(CHARS, 2610, 2612, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2612] = 33;
+        Arrays.fill(CHARS, 2613, 2615, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2615] = 33;
+        Arrays.fill(CHARS, 2616, 2618, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2618, 2620, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2620] = -87;
+        CHARS[2621] = 33;
+        Arrays.fill(CHARS, 2622, 2627, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 2627, 2631, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2631, 2633, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2633, 2635, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2635, 2638, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2638, 2649, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 2649, 2653, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[2653] = 33;
+        CHARS[2654] = -19;
+        Arrays.fill(CHARS, 2655, 2662, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 2662, 2674, (byte) -87 ); // Fill 12 of value (byte) -87
+        Arrays.fill(CHARS, 2674, 2677, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2677, 2689, (byte) 33 ); // Fill 12 of value (byte) 33
+        Arrays.fill(CHARS, 2689, 2692, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2692] = 33;
+        Arrays.fill(CHARS, 2693, 2700, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2700] = 33;
+        CHARS[2701] = -19;
+        CHARS[2702] = 33;
+        Arrays.fill(CHARS, 2703, 2706, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[2706] = 33;
+        Arrays.fill(CHARS, 2707, 2729, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2729] = 33;
+        Arrays.fill(CHARS, 2730, 2737, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2737] = 33;
+        Arrays.fill(CHARS, 2738, 2740, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2740] = 33;
+        Arrays.fill(CHARS, 2741, 2746, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 2746, 2748, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2748] = -87;
+        CHARS[2749] = -19;
+        Arrays.fill(CHARS, 2750, 2758, (byte) -87 ); // Fill 8 of value (byte) -87
+        CHARS[2758] = 33;
+        Arrays.fill(CHARS, 2759, 2762, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2762] = 33;
+        Arrays.fill(CHARS, 2763, 2766, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2766, 2784, (byte) 33 ); // Fill 18 of value (byte) 33
+        CHARS[2784] = -19;
+        Arrays.fill(CHARS, 2785, 2790, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 2790, 2800, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2800, 2817, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 2817, 2820, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2820] = 33;
+        Arrays.fill(CHARS, 2821, 2829, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 2829, 2831, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2831, 2833, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2833, 2835, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2835, 2857, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2857] = 33;
+        Arrays.fill(CHARS, 2858, 2865, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2865] = 33;
+        Arrays.fill(CHARS, 2866, 2868, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2868, 2870, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2870, 2874, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2874, 2876, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2876] = -87;
+        CHARS[2877] = -19;
+        Arrays.fill(CHARS, 2878, 2884, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 2884, 2887, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2887, 2889, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2889, 2891, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2891, 2894, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2894, 2902, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 2902, 2904, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2904, 2908, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2908, 2910, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2910] = 33;
+        Arrays.fill(CHARS, 2911, 2914, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2914, 2918, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2918, 2928, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2928, 2946, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 2946, 2948, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[2948] = 33;
+        Arrays.fill(CHARS, 2949, 2955, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 2955, 2958, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2958, 2961, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[2961] = 33;
+        Arrays.fill(CHARS, 2962, 2966, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2966, 2969, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2969, 2971, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2971] = 33;
+        CHARS[2972] = -19;
+        CHARS[2973] = 33;
+        Arrays.fill(CHARS, 2974, 2976, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2976, 2979, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2979, 2981, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2981, 2984, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2984, 2987, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2987, 2990, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2990, 2998, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[2998] = 33;
+        Arrays.fill(CHARS, 2999, 3002, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 3002, 3006, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3006, 3011, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 3011, 3014, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 3014, 3017, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3017] = 33;
+        Arrays.fill(CHARS, 3018, 3022, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3022, 3031, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[3031] = -87;
+        Arrays.fill(CHARS, 3032, 3047, (byte) 33 ); // Fill 15 of value (byte) 33
+        Arrays.fill(CHARS, 3047, 3056, (byte) -87 ); // Fill 9 of value (byte) -87
+        Arrays.fill(CHARS, 3056, 3073, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 3073, 3076, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3076] = 33;
+        Arrays.fill(CHARS, 3077, 3085, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3085] = 33;
+        Arrays.fill(CHARS, 3086, 3089, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3089] = 33;
+        Arrays.fill(CHARS, 3090, 3113, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3113] = 33;
+        Arrays.fill(CHARS, 3114, 3124, (byte) -19 ); // Fill 10 of value (byte) -19
+        CHARS[3124] = 33;
+        Arrays.fill(CHARS, 3125, 3130, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 3130, 3134, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3134, 3141, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[3141] = 33;
+        Arrays.fill(CHARS, 3142, 3145, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3145] = 33;
+        Arrays.fill(CHARS, 3146, 3150, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3150, 3157, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3157, 3159, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3159, 3168, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 3168, 3170, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3170, 3174, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3174, 3184, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3184, 3202, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 3202, 3204, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3204] = 33;
+        Arrays.fill(CHARS, 3205, 3213, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3213] = 33;
+        Arrays.fill(CHARS, 3214, 3217, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3217] = 33;
+        Arrays.fill(CHARS, 3218, 3241, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3241] = 33;
+        Arrays.fill(CHARS, 3242, 3252, (byte) -19 ); // Fill 10 of value (byte) -19
+        CHARS[3252] = 33;
+        Arrays.fill(CHARS, 3253, 3258, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 3258, 3262, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3262, 3269, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[3269] = 33;
+        Arrays.fill(CHARS, 3270, 3273, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3273] = 33;
+        Arrays.fill(CHARS, 3274, 3278, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3278, 3285, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3285, 3287, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3287, 3294, (byte) 33 ); // Fill 7 of value (byte) 33
+        CHARS[3294] = -19;
+        CHARS[3295] = 33;
+        Arrays.fill(CHARS, 3296, 3298, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3298, 3302, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3302, 3312, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3312, 3330, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 3330, 3332, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3332] = 33;
+        Arrays.fill(CHARS, 3333, 3341, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3341] = 33;
+        Arrays.fill(CHARS, 3342, 3345, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3345] = 33;
+        Arrays.fill(CHARS, 3346, 3369, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3369] = 33;
+        Arrays.fill(CHARS, 3370, 3386, (byte) -19 ); // Fill 16 of value (byte) -19
+        Arrays.fill(CHARS, 3386, 3390, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3390, 3396, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3396, 3398, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3398, 3401, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3401] = 33;
+        Arrays.fill(CHARS, 3402, 3406, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3406, 3415, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[3415] = -87;
+        Arrays.fill(CHARS, 3416, 3424, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 3424, 3426, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3426, 3430, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3430, 3440, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3440, 3585, (byte) 33 ); // Fill 145 of value (byte) 33
+        Arrays.fill(CHARS, 3585, 3631, (byte) -19 ); // Fill 46 of value (byte) -19
+        CHARS[3631] = 33;
+        CHARS[3632] = -19;
+        CHARS[3633] = -87;
+        Arrays.fill(CHARS, 3634, 3636, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3636, 3643, (byte) -87 ); // Fill 7 of value (byte) -87
+        Arrays.fill(CHARS, 3643, 3648, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 3648, 3654, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 3654, 3663, (byte) -87 ); // Fill 9 of value (byte) -87
+        CHARS[3663] = 33;
+        Arrays.fill(CHARS, 3664, 3674, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3674, 3713, (byte) 33 ); // Fill 39 of value (byte) 33
+        Arrays.fill(CHARS, 3713, 3715, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3715] = 33;
+        CHARS[3716] = -19;
+        Arrays.fill(CHARS, 3717, 3719, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3719, 3721, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3721] = 33;
+        CHARS[3722] = -19;
+        Arrays.fill(CHARS, 3723, 3725, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[3725] = -19;
+        Arrays.fill(CHARS, 3726, 3732, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 3732, 3736, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[3736] = 33;
+        Arrays.fill(CHARS, 3737, 3744, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[3744] = 33;
+        Arrays.fill(CHARS, 3745, 3748, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3748] = 33;
+        CHARS[3749] = -19;
+        CHARS[3750] = 33;
+        CHARS[3751] = -19;
+        Arrays.fill(CHARS, 3752, 3754, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3754, 3756, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3756] = 33;
+        Arrays.fill(CHARS, 3757, 3759, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3759] = 33;
+        CHARS[3760] = -19;
+        CHARS[3761] = -87;
+        Arrays.fill(CHARS, 3762, 3764, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3764, 3770, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[3770] = 33;
+        Arrays.fill(CHARS, 3771, 3773, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3773] = -19;
+        Arrays.fill(CHARS, 3774, 3776, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3776, 3781, (byte) -19 ); // Fill 5 of value (byte) -19
+        CHARS[3781] = 33;
+        CHARS[3782] = -87;
+        CHARS[3783] = 33;
+        Arrays.fill(CHARS, 3784, 3790, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3790, 3792, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3792, 3802, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3802, 3864, (byte) 33 ); // Fill 62 of value (byte) 33
+        Arrays.fill(CHARS, 3864, 3866, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3866, 3872, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 3872, 3882, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3882, 3893, (byte) 33 ); // Fill 11 of value (byte) 33
+        CHARS[3893] = -87;
+        CHARS[3894] = 33;
+        CHARS[3895] = -87;
+        CHARS[3896] = 33;
+        CHARS[3897] = -87;
+        Arrays.fill(CHARS, 3898, 3902, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3902, 3904, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3904, 3912, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3912] = 33;
+        Arrays.fill(CHARS, 3913, 3946, (byte) -19 ); // Fill 33 of value (byte) -19
+        Arrays.fill(CHARS, 3946, 3953, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3953, 3973, (byte) -87 ); // Fill 20 of value (byte) -87
+        CHARS[3973] = 33;
+        Arrays.fill(CHARS, 3974, 3980, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3980, 3984, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3984, 3990, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[3990] = 33;
+        CHARS[3991] = -87;
+        CHARS[3992] = 33;
+        Arrays.fill(CHARS, 3993, 4014, (byte) -87 ); // Fill 21 of value (byte) -87
+        Arrays.fill(CHARS, 4014, 4017, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4017, 4024, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[4024] = 33;
+        CHARS[4025] = -87;
+        Arrays.fill(CHARS, 4026, 4256, (byte) 33 ); // Fill 230 of value (byte) 33
+        Arrays.fill(CHARS, 4256, 4294, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 4294, 4304, (byte) 33 ); // Fill 10 of value (byte) 33
+        Arrays.fill(CHARS, 4304, 4343, (byte) -19 ); // Fill 39 of value (byte) -19
+        Arrays.fill(CHARS, 4343, 4352, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[4352] = -19;
+        CHARS[4353] = 33;
+        Arrays.fill(CHARS, 4354, 4356, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4356] = 33;
+        Arrays.fill(CHARS, 4357, 4360, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[4360] = 33;
+        CHARS[4361] = -19;
+        CHARS[4362] = 33;
+        Arrays.fill(CHARS, 4363, 4365, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4365] = 33;
+        Arrays.fill(CHARS, 4366, 4371, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 4371, 4412, (byte) 33 ); // Fill 41 of value (byte) 33
+        CHARS[4412] = -19;
+        CHARS[4413] = 33;
+        CHARS[4414] = -19;
+        CHARS[4415] = 33;
+        CHARS[4416] = -19;
+        Arrays.fill(CHARS, 4417, 4428, (byte) 33 ); // Fill 11 of value (byte) 33
+        CHARS[4428] = -19;
+        CHARS[4429] = 33;
+        CHARS[4430] = -19;
+        CHARS[4431] = 33;
+        CHARS[4432] = -19;
+        Arrays.fill(CHARS, 4433, 4436, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4436, 4438, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4438, 4441, (byte) 33 ); // Fill 3 of value (byte) 33
+        CHARS[4441] = -19;
+        Arrays.fill(CHARS, 4442, 4447, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 4447, 4450, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[4450] = 33;
+        CHARS[4451] = -19;
+        CHARS[4452] = 33;
+        CHARS[4453] = -19;
+        CHARS[4454] = 33;
+        CHARS[4455] = -19;
+        CHARS[4456] = 33;
+        CHARS[4457] = -19;
+        Arrays.fill(CHARS, 4458, 4461, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4461, 4463, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4463, 4466, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4466, 4468, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4468] = 33;
+        CHARS[4469] = -19;
+        Arrays.fill(CHARS, 4470, 4510, (byte) 33 ); // Fill 40 of value (byte) 33
+        CHARS[4510] = -19;
+        Arrays.fill(CHARS, 4511, 4520, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[4520] = -19;
+        Arrays.fill(CHARS, 4521, 4523, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[4523] = -19;
+        Arrays.fill(CHARS, 4524, 4526, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 4526, 4528, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4528, 4535, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 4535, 4537, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4537] = 33;
+        CHARS[4538] = -19;
+        CHARS[4539] = 33;
+        Arrays.fill(CHARS, 4540, 4547, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 4547, 4587, (byte) 33 ); // Fill 40 of value (byte) 33
+        CHARS[4587] = -19;
+        Arrays.fill(CHARS, 4588, 4592, (byte) 33 ); // Fill 4 of value (byte) 33
+        CHARS[4592] = -19;
+        Arrays.fill(CHARS, 4593, 4601, (byte) 33 ); // Fill 8 of value (byte) 33
+        CHARS[4601] = -19;
+        Arrays.fill(CHARS, 4602, 7680, (byte) 33 ); // Fill 3078 of value (byte) 33
+        Arrays.fill(CHARS, 7680, 7836, (byte) -19 ); // Fill 156 of value (byte) -19
+        Arrays.fill(CHARS, 7836, 7840, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 7840, 7930, (byte) -19 ); // Fill 90 of value (byte) -19
+        Arrays.fill(CHARS, 7930, 7936, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 7936, 7958, (byte) -19 ); // Fill 22 of value (byte) -19
+        Arrays.fill(CHARS, 7958, 7960, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 7960, 7966, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 7966, 7968, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 7968, 8006, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 8006, 8008, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8008, 8014, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 8014, 8016, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8016, 8024, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[8024] = 33;
+        CHARS[8025] = -19;
+        CHARS[8026] = 33;
+        CHARS[8027] = -19;
+        CHARS[8028] = 33;
+        CHARS[8029] = -19;
+        CHARS[8030] = 33;
+        Arrays.fill(CHARS, 8031, 8062, (byte) -19 ); // Fill 31 of value (byte) -19
+        Arrays.fill(CHARS, 8062, 8064, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8064, 8117, (byte) -19 ); // Fill 53 of value (byte) -19
+        CHARS[8117] = 33;
+        Arrays.fill(CHARS, 8118, 8125, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[8125] = 33;
+        CHARS[8126] = -19;
+        Arrays.fill(CHARS, 8127, 8130, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8130, 8133, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[8133] = 33;
+        Arrays.fill(CHARS, 8134, 8141, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 8141, 8144, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8144, 8148, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 8148, 8150, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8150, 8156, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 8156, 8160, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 8160, 8173, (byte) -19 ); // Fill 13 of value (byte) -19
+        Arrays.fill(CHARS, 8173, 8178, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 8178, 8181, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[8181] = 33;
+        Arrays.fill(CHARS, 8182, 8189, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 8189, 8400, (byte) 33 ); // Fill 211 of value (byte) 33
+        Arrays.fill(CHARS, 8400, 8413, (byte) -87 ); // Fill 13 of value (byte) -87
+        Arrays.fill(CHARS, 8413, 8417, (byte) 33 ); // Fill 4 of value (byte) 33
+        CHARS[8417] = -87;
+        Arrays.fill(CHARS, 8418, 8486, (byte) 33 ); // Fill 68 of value (byte) 33
+        CHARS[8486] = -19;
+        Arrays.fill(CHARS, 8487, 8490, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8490, 8492, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 8492, 8494, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[8494] = -19;
+        Arrays.fill(CHARS, 8495, 8576, (byte) 33 ); // Fill 81 of value (byte) 33
+        Arrays.fill(CHARS, 8576, 8579, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 8579, 12293, (byte) 33 ); // Fill 3714 of value (byte) 33
+        CHARS[12293] = -87;
+        CHARS[12294] = 33;
+        CHARS[12295] = -19;
+        Arrays.fill(CHARS, 12296, 12321, (byte) 33 ); // Fill 25 of value (byte) 33
+        Arrays.fill(CHARS, 12321, 12330, (byte) -19 ); // Fill 9 of value (byte) -19
+        Arrays.fill(CHARS, 12330, 12336, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[12336] = 33;
+        Arrays.fill(CHARS, 12337, 12342, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 12342, 12353, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 12353, 12437, (byte) -19 ); // Fill 84 of value (byte) -19
+        Arrays.fill(CHARS, 12437, 12441, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 12441, 12443, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 12443, 12445, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 12445, 12447, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 12447, 12449, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 12449, 12539, (byte) -19 ); // Fill 90 of value (byte) -19
+        CHARS[12539] = 33;
+        Arrays.fill(CHARS, 12540, 12543, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 12543, 12549, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 12549, 12589, (byte) -19 ); // Fill 40 of value (byte) -19
+        Arrays.fill(CHARS, 12589, 19968, (byte) 33 ); // Fill 7379 of value (byte) 33
+        Arrays.fill(CHARS, 19968, 40870, (byte) -19 ); // Fill 20902 of value (byte) -19
+        Arrays.fill(CHARS, 40870, 44032, (byte) 33 ); // Fill 3162 of value (byte) 33
+        Arrays.fill(CHARS, 44032, 55204, (byte) -19 ); // Fill 11172 of value (byte) -19
+        Arrays.fill(CHARS, 55204, 55296, (byte) 33 ); // Fill 92 of value (byte) 33
+        Arrays.fill(CHARS, 57344, 65534, (byte) 33 ); // Fill 8190 of value (byte) 33
+
+    } // <clinit>()
+
+    //
+    // Public static methods
+    //
+
+    /**
+     * Returns true if the specified character is a supplemental character.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isSupplemental(int c) {
+        return (c >= 0x10000 && c <= 0x10FFFF);
+    }
+
+    /**
+     * Returns true the supplemental character corresponding to the given
+     * surrogates.
+     *
+     * @param h The high surrogate.
+     * @param l The low surrogate.
+     */
+    public static int supplemental(char h, char l) {
+        return (h - 0xD800) * 0x400 + (l - 0xDC00) + 0x10000;
+    }
+
+    /**
+     * Returns the high surrogate of a supplemental character
+     *
+     * @param c The supplemental character to "split".
+     */
+    public static char highSurrogate(int c) {
+        return (char) (((c - 0x00010000) >> 10) + 0xD800);
+    }
+
+    /**
+     * Returns the low surrogate of a supplemental character
+     *
+     * @param c The supplemental character to "split".
+     */
+    public static char lowSurrogate(int c) {
+        return (char) (((c - 0x00010000) & 0x3FF) + 0xDC00);
+    }
+
+    /**
+     * Returns whether the given character is a high surrogate
+     *
+     * @param c The character to check.
+     */
+    public static boolean isHighSurrogate(int c) {
+        return (0xD800 <= c && c <= 0xDBFF);
+    }
+
+    /**
+     * Returns whether the given character is a low surrogate
+     *
+     * @param c The character to check.
+     */
+    public static boolean isLowSurrogate(int c) {
+        return (0xDC00 <= c && c <= 0xDFFF);
+    }
+
+
+    /**
+     * Returns true if the specified character is valid. This method
+     * also checks the surrogate character range from 0x10000 to 0x10FFFF.
+     * <p>
+     * If the program chooses to apply the mask directly to the
+     * <code>CHARS</code> array, then they are responsible for checking
+     * the surrogate character range.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isValid(int c) {
+        return (c < 0x10000 && (CHARS[c] & MASK_VALID) != 0) ||
+               (0x10000 <= c && c <= 0x10FFFF);
+    } // isValid(int):boolean
+
+    /**
+     * Returns true if the specified character is invalid.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isInvalid(int c) {
+        return !isValid(c);
+    } // isInvalid(int):boolean
+
+    /**
+     * Returns true if the specified character can be considered content.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isContent(int c) {
+        return (c < 0x10000 && (CHARS[c] & MASK_CONTENT) != 0) ||
+               (0x10000 <= c && c <= 0x10FFFF);
+    } // isContent(int):boolean
+
+    /**
+     * Returns true if the specified character can be considered markup.
+     * Markup characters include '&lt;', '&amp;', and '%'.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isMarkup(int c) {
+        return c == '<' || c == '&' || c == '%';
+    } // isMarkup(int):boolean
+
+    /**
+     * Returns true if the specified character is a space character
+     * as defined by production [3] in the XML 1.0 specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isSpace(int c) {
+        return c <= 0x20 && (CHARS[c] & MASK_SPACE) != 0;
+    } // isSpace(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid name start
+     * character as defined by production [5] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNameStart(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NAME_START) != 0;
+    } // isNameStart(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid name
+     * character as defined by production [4] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isName(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NAME) != 0;
+    } // isName(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid NCName start
+     * character as defined by production [4] in Namespaces in XML
+     * recommendation.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNCNameStart(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NCNAME_START) != 0;
+    } // isNCNameStart(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid NCName
+     * character as defined by production [5] in Namespaces in XML
+     * recommendation.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNCName(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NCNAME) != 0;
+    } // isNCName(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid Pubid
+     * character as defined by production [13] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isPubid(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_PUBID) != 0;
+    } // isPubid(int):boolean
+
+    /*
+     * [5] Name ::= (Letter | '_' | ':') (NameChar)*
+     */
+    /**
+     * Check to see if a string is a valid Name according to [5]
+     * in the XML 1.0 Recommendation
+     *
+     * @param name string to check
+     * @return true if name is a valid Name
+     */
+    public static boolean isValidName(String name) {
+        if (name.length() == 0)
+            return false;
+        char ch = name.charAt(0);
+        if( isNameStart(ch) == false)
+           return false;
+        for (int i = 1; i < name.length(); i++ ) {
+           ch = name.charAt(i);
+           if( isName( ch ) == false ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidName(String):boolean
+    
+
+    /*
+     * from the namespace rec
+     * [4] NCName ::= (Letter | '_') (NCNameChar)*
+     */
+    /**
+     * Check to see if a string is a valid NCName according to [4]
+     * from the XML Namespaces 1.0 Recommendation
+     *
+     * @param ncName string to check
+     * @return true if name is a valid NCName
+     */
+    public static boolean isValidNCName(String ncName) {
+        if (ncName.length() == 0)
+            return false;
+        char ch = ncName.charAt(0);
+        if( isNCNameStart(ch) == false)
+           return false;
+        for (int i = 1; i < ncName.length(); i++ ) {
+           ch = ncName.charAt(i);
+           if( isNCName( ch ) == false ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidNCName(String):boolean
+
+    /*
+     * [7] Nmtoken ::= (NameChar)+
+     */
+    /**
+     * Check to see if a string is a valid Nmtoken according to [7]
+     * in the XML 1.0 Recommendation
+     *
+     * @param nmtoken string to check
+     * @return true if nmtoken is a valid Nmtoken 
+     */
+    public static boolean isValidNmtoken(String nmtoken) {
+        if (nmtoken.length() == 0)
+            return false;
+        for (int i = 0; i < nmtoken.length(); i++ ) {
+           char ch = nmtoken.charAt(i);
+           if(  ! isName( ch ) ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidName(String):boolean
+
+
+
+
+
+    // encodings
+
+    /**
+     * Returns true if the encoding name is a valid IANA encoding.
+     * This method does not verify that there is a decoder available
+     * for this encoding, only that the characters are valid for an
+     * IANA encoding name.
+     *
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static boolean isValidIANAEncoding(String ianaEncoding) {
+        if (ianaEncoding != null) {
+            int length = ianaEncoding.length();
+            if (length > 0) {
+                char c = ianaEncoding.charAt(0);
+                if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
+                    for (int i = 1; i < length; i++) {
+                        c = ianaEncoding.charAt(i);
+                        if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') &&
+                            (c < '0' || c > '9') && c != '.' && c != '_' &&
+                            c != '-') {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    } // isValidIANAEncoding(String):boolean
+
+    /**
+     * Returns true if the encoding name is a valid Java encoding.
+     * This method does not verify that there is a decoder available
+     * for this encoding, only that the characters are valid for an
+     * Java encoding name.
+     *
+     * @param javaEncoding The Java encoding name.
+     */
+    public static boolean isValidJavaEncoding(String javaEncoding) {
+        if (javaEncoding != null) {
+            int length = javaEncoding.length();
+            if (length > 0) {
+                for (int i = 1; i < length; i++) {
+                    char c = javaEncoding.charAt(i);
+                    if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') &&
+                        (c < '0' || c > '9') && c != '.' && c != '_' &&
+                        c != '-') {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    } // isValidIANAEncoding(String):boolean
+
+
+} // class XMLChar
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java
new file mode 100644
index 0000000..a5c8a93
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java
@@ -0,0 +1,1637 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Locale;
+import java.util.jar.JarFile;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.compiler.ErrorDispatcher;
+import org.apache.jasper.compiler.JspUtil;
+
+public class XMLEncodingDetector {
+    
+    private InputStream stream;
+    private String encoding;
+    private boolean isEncodingSetInProlog;
+    private boolean isBomPresent;
+    private int skip;
+    private Boolean isBigEndian;
+    private Reader reader;
+    
+    // org.apache.xerces.impl.XMLEntityManager fields
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+    public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
+    private boolean fAllowJavaEncodings;
+    private SymbolTable fSymbolTable;
+    private XMLEncodingDetector fCurrentEntity;
+    private int fBufferSize = DEFAULT_BUFFER_SIZE;
+    
+    // org.apache.xerces.impl.XMLEntityManager.ScannedEntity fields
+    private int lineNumber = 1;
+    private int columnNumber = 1;
+    private boolean literal;
+    private char[] ch = new char[DEFAULT_BUFFER_SIZE];
+    private int position;
+    private int count;
+    private boolean mayReadChunks = false;
+    
+    // org.apache.xerces.impl.XMLScanner fields
+    private XMLString fString = new XMLString();    
+    private XMLStringBuffer fStringBuffer = new XMLStringBuffer();
+    private XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();
+    private final static String fVersionSymbol = "version";
+    private final static String fEncodingSymbol = "encoding";
+    private final static String fStandaloneSymbol = "standalone";
+    
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl fields
+    private int fMarkupDepth = 0;
+    private String[] fStrings = new String[3];
+
+    private ErrorDispatcher err;
+
+    /**
+     * Constructor
+     */
+    public XMLEncodingDetector() {
+        fSymbolTable = new SymbolTable();
+        fCurrentEntity = this;
+    }
+
+    /**
+     * Autodetects the encoding of the XML document supplied by the given
+     * input stream.
+     *
+     * Encoding autodetection is done according to the XML 1.0 specification,
+     * Appendix F.1: Detection Without External Encoding Information.
+     *
+     * @return Two-element array, where the first element (of type
+     * java.lang.String) contains the name of the (auto)detected encoding, and
+     * the second element (of type java.lang.Boolean) specifies whether the 
+     * encoding was specified using the 'encoding' attribute of an XML prolog
+     * (TRUE) or autodetected (FALSE).
+     */
+    public static Object[] getEncoding(String fname, JarFile jarFile,
+                                       JspCompilationContext ctxt,
+                                       ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        InputStream inStream = JspUtil.getInputStream(fname, jarFile, ctxt,
+                                                      err);
+        XMLEncodingDetector detector = new XMLEncodingDetector();
+        Object[] ret = detector.getEncoding(inStream, err);
+        inStream.close();
+
+        return ret;
+    }
+
+    private Object[] getEncoding(InputStream in, ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        this.stream = in;
+        this.err=err;
+        createInitialReader();
+        scanXMLDecl();
+	
+        return new Object[] { this.encoding,
+                              Boolean.valueOf(this.isEncodingSetInProlog),
+                              Boolean.valueOf(this.isBomPresent),
+                              Integer.valueOf(this.skip) };
+    }
+    
+    // stub method
+    void endEntity() {
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.startEntity()
+    private void createInitialReader() throws IOException, JasperException {
+
+	// wrap this stream in RewindableInputStream
+	stream = new RewindableInputStream(stream);
+
+	// perform auto-detect of encoding if necessary
+	if (encoding == null) {
+	    // read first four bytes and determine encoding
+	    final byte[] b4 = new byte[4];
+	    int count = 0;
+	    for (; count<4; count++ ) {
+		b4[count] = (byte)stream.read();
+	    }
+	    if (count == 4) {
+		Object [] encodingDesc = getEncodingName(b4, count);
+		encoding = (String)(encodingDesc[0]);
+		isBigEndian = (Boolean)(encodingDesc[1]);
+        
+        if (encodingDesc.length > 3) {
+            isBomPresent = (Boolean)(encodingDesc[2]);
+            skip = (Integer)(encodingDesc[3]);
+        } else {
+            isBomPresent = true;
+            skip = (Integer)(encodingDesc[2]);
+        }
+
+		stream.reset();
+		// Special case UTF-8 files with BOM created by Microsoft
+		// tools. It's more efficient to consume the BOM than make
+		// the reader perform extra checks. -Ac
+		if (count > 2 && encoding.equals("UTF-8")) {
+		    int b0 = b4[0] & 0xFF;
+		    int b1 = b4[1] & 0xFF;
+		    int b2 = b4[2] & 0xFF;
+		    if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+			// ignore first three bytes...
+			stream.skip(3);
+		    }
+		}
+		reader = createReader(stream, encoding, isBigEndian);
+	    } else {
+		reader = createReader(stream, encoding, isBigEndian);
+	    }
+	}
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.createReader
+    /**
+     * Creates a reader capable of reading the given input stream in
+     * the specified encoding.
+     *
+     * @param inputStream  The input stream.
+     * @param encoding     The encoding name that the input stream is
+     *                     encoded using. If the user has specified that
+     *                     Java encoding names are allowed, then the
+     *                     encoding name may be a Java encoding name;
+     *                     otherwise, it is an ianaEncoding name.
+     * @param isBigEndian   For encodings (like uCS-4), whose names cannot
+     *                      specify a byte order, this tells whether the order
+     *                      is bigEndian. null means unknown or not relevant.
+     *
+     * @return Returns a reader.
+     */
+    private Reader createReader(InputStream inputStream, String encoding,
+				Boolean isBigEndian)
+                throws IOException, JasperException {
+
+        // normalize encoding name
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+
+        // try to use an optimized reader
+        String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
+        if (ENCODING.equals("UTF-8")) {
+            return new UTF8Reader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("US-ASCII")) {
+            return new ASCIIReader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("ISO-10646-UCS-4")) {
+            if (isBigEndian != null) {
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS4BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS4LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+			     encoding);
+            }
+        }
+        if (ENCODING.equals("ISO-10646-UCS-2")) {
+            if (isBigEndian != null) { // sould never happen with this encoding...
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS2BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS2LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+			     encoding);
+            }
+        }
+
+        // check for valid name
+        boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
+        boolean validJava = XMLChar.isValidJavaEncoding(encoding);
+        if (!validIANA || (fAllowJavaEncodings && !validJava)) {
+            err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+            // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
+            //       because every byte is a valid ISO Latin 1 character.
+            //       It may not translate correctly but if we failed on
+            //       the encoding anyway, then we're expecting the content
+            //       of the document to be bad. This will just prevent an
+            //       invalid UTF-8 sequence to be detected. This is only
+            //       important when continue-after-fatal-error is turned
+            //       on. -Ac
+            encoding = "ISO-8859-1";
+        }
+
+        // try to use a Java reader
+        String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
+        if (javaEncoding == null) {
+            if (fAllowJavaEncodings) {
+		javaEncoding = encoding;
+            } else {
+                err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+                // see comment above.
+                javaEncoding = "ISO8859_1";
+            }
+        }
+        return new InputStreamReader(inputStream, javaEncoding);
+
+    } // createReader(InputStream,String, Boolean): Reader
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.getEncodingName
+    /**
+     * Returns the IANA encoding name that is auto-detected from
+     * the bytes specified, with the endian-ness of that encoding where
+     * appropriate.
+     *
+     * @param b4    The first four bytes of the input.
+     * @param count The number of bytes actually read.
+     * @return a 2-element array:  the first element, an IANA-encoding string,
+     *  the second element a Boolean which is true iff the document is big
+     *  endian, false if it's little-endian, and null if the distinction isn't
+     *  relevant.
+     */
+    private Object[] getEncodingName(byte[] b4, int count) {
+
+        if (count < 2) {
+            return new Object[]{"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-16, with BOM
+        int b0 = b4[0] & 0xFF;
+        int b1 = b4[1] & 0xFF;
+        if (b0 == 0xFE && b1 == 0xFF) {
+            // UTF-16, big-endian
+            return new Object [] {"UTF-16BE", Boolean.TRUE, Integer.valueOf(2)};
+        }
+        if (b0 == 0xFF && b1 == 0xFE) {
+            // UTF-16, little-endian
+            return new Object [] {"UTF-16LE", Boolean.FALSE, Integer.valueOf(2)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 3) {
+            return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-8 with a BOM
+        int b2 = b4[2] & 0xFF;
+        if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(3)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 4) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(0)};
+        }
+
+        // other encodings
+        int b3 = b4[3] & 0xFF;
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
+            // UCS-4, big endian (1234)
+            return new Object [] {"ISO-10646-UCS-4", new Boolean(true), Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, little endian (4321)
+            return new Object [] {"ISO-10646-UCS-4", new Boolean(false), Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
+            // UCS-4, unusual octet order (2143)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, unusual octect order (3412)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
+            // UTF-16, big-endian, no BOM
+            // (or could turn out to be UCS-2...
+            // REVISIT: What should this be?
+            return new Object [] {"UTF-16BE", new Boolean(true), Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
+            // UTF-16, little-endian, no BOM
+            // (or could turn out to be UCS-2...
+            return new Object [] {"UTF-16LE", new Boolean(false), Integer.valueOf(4)};
+        }
+        if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
+            // EBCDIC
+            // a la xerces1, return CP037 instead of EBCDIC here
+            return new Object [] {"CP037", null, Integer.valueOf(4)};
+        }
+
+        // default encoding
+        return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.isExternal
+    /** Returns true if the current entity being scanned is external. */
+    public boolean isExternal() {
+	return true;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.peekChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is <em>not</em> consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int peekChar() throws IOException {
+	
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+	
+	// peek at character
+	int c = fCurrentEntity.ch[fCurrentEntity.position];
+
+	// return peeked character
+	if (fCurrentEntity.isExternal()) {
+	    return c != '\r' ? c : '\n';
+	}
+	else {
+	    return c;
+	}
+	
+    } // peekChar():int
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanChar() throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// scan character
+	int c = fCurrentEntity.ch[fCurrentEntity.position++];
+	boolean external = false;
+	if (c == '\n' ||
+	    (c == '\r' && (external = fCurrentEntity.isExternal()))) {
+	    fCurrentEntity.lineNumber++;
+	    fCurrentEntity.columnNumber = 1;
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = (char)c;
+		load(1, false);
+	    }
+	    if (c == '\r' && external) {
+		if (fCurrentEntity.ch[fCurrentEntity.position++] != '\n') {
+		    fCurrentEntity.position--;
+		}
+		c = '\n';
+	    }
+	}
+
+	// return character that was scanned
+	fCurrentEntity.columnNumber++;
+	return c;
+	
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanName
+    /**
+     * Returns a string matching the Name production appearing immediately
+     * on the input as a symbol, or null if no Name string is present.
+     * <p>
+     * <strong>Note:</strong> The Name characters are consumed.
+     * <p>
+     * <strong>Note:</strong> The string returned must be a symbol. The
+     * SymbolTable can be used for this purpose.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see SymbolTable
+     * @see XMLChar#isName
+     * @see XMLChar#isNameStart
+     */
+    public String scanName() throws IOException {
+	
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+	
+	// scan name
+	int offset = fCurrentEntity.position;
+	if (XMLChar.isNameStart(fCurrentEntity.ch[offset])) {
+	    if (++fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = fCurrentEntity.ch[offset];
+		offset = 0;
+		if (load(1, false)) {
+		    fCurrentEntity.columnNumber++;
+		    String symbol = fSymbolTable.addSymbol(fCurrentEntity.ch,
+							   0, 1);
+		    return symbol;
+		}
+	    }
+	    while (XMLChar.isName(fCurrentEntity.ch[fCurrentEntity.position])) {
+		if (++fCurrentEntity.position == fCurrentEntity.count) {
+		    int length = fCurrentEntity.position - offset;
+		    if (length == fBufferSize) {
+			// bad luck we have to resize our buffer
+			char[] tmp = new char[fBufferSize * 2];
+			System.arraycopy(fCurrentEntity.ch, offset,
+					 tmp, 0, length);
+			fCurrentEntity.ch = tmp;
+			fBufferSize *= 2;
+		    } else {
+			System.arraycopy(fCurrentEntity.ch, offset,
+					 fCurrentEntity.ch, 0, length);
+		    }
+		    offset = 0;
+		    if (load(length, false)) {
+			break;
+		    }
+		}
+	    }
+	}
+	int length = fCurrentEntity.position - offset;
+	fCurrentEntity.columnNumber += length;
+
+	// return name
+	String symbol = null;
+	if (length > 0) {
+	    symbol = fSymbolTable.addSymbol(fCurrentEntity.ch, offset, length);
+	}
+	return symbol;
+	
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanLiteral
+    /**
+     * Scans a range of attribute value data, setting the fields of the
+     * XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of attribute value data. This method may return
+     * before the quote character due to reaching the end of the input
+     * buffer or any other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param quote   The quote character that signifies the end of the
+     *                attribute value data.
+     * @param content The content structure to fill.
+     *
+     * @return Returns the next character on the input, if known. This
+     *         value may be -1 but this does <em>note</em> designate
+     *         end of file.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanLiteral(int quote, XMLString content)
+	throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	} else if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+	    fCurrentEntity.ch[0] = fCurrentEntity.ch[fCurrentEntity.count - 1];
+	    load(1, false);
+	    fCurrentEntity.position = 0;
+	}
+
+	// normalize newlines
+	int offset = fCurrentEntity.position;
+	int c = fCurrentEntity.ch[offset];
+	int newlines = 0;
+	boolean external = fCurrentEntity.isExternal();
+	if (c == '\n' || (c == '\r' && external)) {
+	    do {
+		c = fCurrentEntity.ch[fCurrentEntity.position++];
+		if (c == '\r' && external) {
+		    newlines++;
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			offset = 0;
+			fCurrentEntity.position = newlines;
+			if (load(newlines, false)) {
+			    break;
+			}
+		    }
+		    if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+			fCurrentEntity.position++;
+			offset++;
+		    }
+		    /*** NEWLINE NORMALIZATION ***/
+		    else {
+			newlines++;
+		    }
+		    /***/
+		}
+		else if (c == '\n') {
+		    newlines++;
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			offset = 0;
+			fCurrentEntity.position = newlines;
+			if (load(newlines, false)) {
+			    break;
+			}
+		    }
+		    /*** NEWLINE NORMALIZATION ***
+			 if (fCurrentEntity.ch[fCurrentEntity.position] == '\r'
+			 && external) {
+			 fCurrentEntity.position++;
+			 offset++;
+			 }
+			 /***/
+		}
+		else {
+		    fCurrentEntity.position--;
+		    break;
+		}
+	    } while (fCurrentEntity.position < fCurrentEntity.count - 1);
+	    for (int i = offset; i < fCurrentEntity.position; i++) {
+		fCurrentEntity.ch[i] = '\n';
+	    }
+	    int length = fCurrentEntity.position - offset;
+	    if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+		content.setValues(fCurrentEntity.ch, offset, length);
+		return -1;
+	    }
+	}
+
+	// scan literal value
+	while (fCurrentEntity.position < fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if ((c == quote &&
+		 (!fCurrentEntity.literal || external))
+		|| c == '%' || !XMLChar.isContent(c)) {
+		fCurrentEntity.position--;
+		break;
+	    }
+	}
+	int length = fCurrentEntity.position - offset;
+	fCurrentEntity.columnNumber += length - newlines;
+	content.setValues(fCurrentEntity.ch, offset, length);
+
+	// return next character
+	if (fCurrentEntity.position != fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position];
+	    // NOTE: We don't want to accidentally signal the
+	    //       end of the literal if we're expanding an
+	    //       entity appearing in the literal. -Ac
+	    if (c == quote && fCurrentEntity.literal) {
+		c = -1;
+	    }
+	}
+	else {
+	    c = -1;
+	}
+	return c;
+
+    }
+
+    /**
+     * Scans a range of character data up to the specified delimiter,
+     * setting the fields of the XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This assumes that the internal buffer is
+     * at least the same size, or bigger, than the length of the delimiter
+     * and that the delimiter contains at least one character.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of character data. This method may return before
+     * the delimiter due to reaching the end of the input buffer or any
+     * other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param delimiter The string that signifies the end of the character
+     *                  data to be scanned.
+     * @param buffer    The data structure to fill.
+     *
+     * @return Returns true if there is more data to scan, false otherwise.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean scanData(String delimiter, XMLStringBuffer buffer)
+	throws IOException {
+
+	boolean done = false;
+	int delimLen = delimiter.length();
+	char charAt0 = delimiter.charAt(0);
+	boolean external = fCurrentEntity.isExternal();
+	do {
+    
+	    // load more characters, if needed
+    
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		load(0, true);
+	    }
+	    else if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+		System.arraycopy(fCurrentEntity.ch, fCurrentEntity.position,
+				 fCurrentEntity.ch, 0, fCurrentEntity.count - fCurrentEntity.position);
+		load(fCurrentEntity.count - fCurrentEntity.position, false);
+		fCurrentEntity.position = 0;
+	    } 
+	    if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+		// something must be wrong with the input: e.g., file ends an
+		// unterminated comment
+		int length = fCurrentEntity.count - fCurrentEntity.position;
+		buffer.append (fCurrentEntity.ch, fCurrentEntity.position,
+			       length); 
+		fCurrentEntity.columnNumber += fCurrentEntity.count;
+		fCurrentEntity.position = fCurrentEntity.count;
+		load(0,true);
+		return false;
+	    }
+    
+	    // normalize newlines
+	    int offset = fCurrentEntity.position;
+	    int c = fCurrentEntity.ch[offset];
+	    int newlines = 0;
+	    if (c == '\n' || (c == '\r' && external)) {
+		do {
+		    c = fCurrentEntity.ch[fCurrentEntity.position++];
+		    if (c == '\r' && external) {
+			newlines++;
+			fCurrentEntity.lineNumber++;
+			fCurrentEntity.columnNumber = 1;
+			if (fCurrentEntity.position == fCurrentEntity.count) {
+			    offset = 0;
+			    fCurrentEntity.position = newlines;
+			    if (load(newlines, false)) {
+				break;
+			    }
+			}
+			if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+			    fCurrentEntity.position++;
+			    offset++;
+			}
+			/*** NEWLINE NORMALIZATION ***/
+			else {
+			    newlines++;
+			}
+		    }
+		    else if (c == '\n') {
+			newlines++;
+			fCurrentEntity.lineNumber++;
+			fCurrentEntity.columnNumber = 1;
+			if (fCurrentEntity.position == fCurrentEntity.count) {
+			    offset = 0;
+			    fCurrentEntity.position = newlines;
+			    fCurrentEntity.count = newlines;
+			    if (load(newlines, false)) {
+				break;
+			    }
+			}
+		    }
+		    else {
+			fCurrentEntity.position--;
+			break;
+		    }
+		} while (fCurrentEntity.position < fCurrentEntity.count - 1);
+		for (int i = offset; i < fCurrentEntity.position; i++) {
+		    fCurrentEntity.ch[i] = '\n';
+		}
+		int length = fCurrentEntity.position - offset;
+		if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+		    buffer.append(fCurrentEntity.ch, offset, length);
+		    return true;
+		}
+	    }
+    
+	    // iterate over buffer looking for delimiter
+	OUTER: while (fCurrentEntity.position < fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if (c == charAt0) {
+		// looks like we just hit the delimiter
+		int delimOffset = fCurrentEntity.position - 1;
+		for (int i = 1; i < delimLen; i++) {
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			fCurrentEntity.position -= i;
+			break OUTER;
+		    }
+		    c = fCurrentEntity.ch[fCurrentEntity.position++];
+		    if (delimiter.charAt(i) != c) {
+			fCurrentEntity.position--;
+			break;
+		    }
+		}
+		if (fCurrentEntity.position == delimOffset + delimLen) {
+		    done = true;
+		    break;
+		}
+	    }
+	    else if (c == '\n' || (external && c == '\r')) {
+		fCurrentEntity.position--;
+		break;
+	    }
+	    else if (XMLChar.isInvalid(c)) {
+		fCurrentEntity.position--;
+		int length = fCurrentEntity.position - offset;
+		fCurrentEntity.columnNumber += length - newlines;
+		buffer.append(fCurrentEntity.ch, offset, length); 
+		return true;
+	    }
+	}
+	    int length = fCurrentEntity.position - offset;
+	    fCurrentEntity.columnNumber += length - newlines;
+	    if (done) {
+		length -= delimLen;
+	    }
+	    buffer.append (fCurrentEntity.ch, offset, length);
+    
+	    // return true if string was skipped
+	} while (!done);
+	return !done;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipChar
+    /**
+     * Skips a character appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed only if it matches
+     * the specified character.
+     *
+     * @param c The character to skip.
+     *
+     * @return Returns true if the character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipChar(int c) throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip character
+	int cc = fCurrentEntity.ch[fCurrentEntity.position];
+	if (cc == c) {
+	    fCurrentEntity.position++;
+	    if (c == '\n') {
+		fCurrentEntity.lineNumber++;
+		fCurrentEntity.columnNumber = 1;
+	    }
+	    else {
+		fCurrentEntity.columnNumber++;
+	    }
+	    return true;
+	} else if (c == '\n' && cc == '\r' && fCurrentEntity.isExternal()) {
+	    // handle newlines
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = (char)cc;
+		load(1, false);
+	    }
+	    fCurrentEntity.position++;
+	    if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+		fCurrentEntity.position++;
+	    }
+	    fCurrentEntity.lineNumber++;
+	    fCurrentEntity.columnNumber = 1;
+	    return true;
+	}
+
+	// character was not skipped
+	return false;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipSpaces
+    /**
+     * Skips space characters appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @return Returns true if at least one space character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see XMLChar#isSpace
+     */
+    public boolean skipSpaces() throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip spaces
+	int c = fCurrentEntity.ch[fCurrentEntity.position];
+	if (XMLChar.isSpace(c)) {
+	    boolean external = fCurrentEntity.isExternal();
+	    do {
+		boolean entityChanged = false;
+		// handle newlines
+		if (c == '\n' || (external && c == '\r')) {
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+			fCurrentEntity.ch[0] = (char)c;
+			entityChanged = load(1, true);
+			if (!entityChanged)
+                                // the load change the position to be 1,
+                                // need to restore it when entity not changed
+			    fCurrentEntity.position = 0;
+		    }
+		    if (c == '\r' && external) {
+			// REVISIT: Does this need to be updated to fix the
+			//          #x0D ^#x0A newline normalization problem? -Ac
+			if (fCurrentEntity.ch[++fCurrentEntity.position] != '\n') {
+			    fCurrentEntity.position--;
+			}
+		    }
+		    /*** NEWLINE NORMALIZATION ***
+			 else {
+			 if (fCurrentEntity.ch[fCurrentEntity.position + 1] == '\r'
+			 && external) {
+			 fCurrentEntity.position++;
+			 }
+			 }
+			 /***/
+		}
+		else {
+		    fCurrentEntity.columnNumber++;
+		}
+		// load more characters, if needed
+		if (!entityChanged)
+		    fCurrentEntity.position++;
+		if (fCurrentEntity.position == fCurrentEntity.count) {
+		    load(0, true);
+		}
+	    } while (XMLChar.isSpace(c = fCurrentEntity.ch[fCurrentEntity.position]));
+	    return true;
+	}
+
+	// no spaces were found
+	return false;
+
+    }
+
+    /**
+     * Skips the specified string appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @param s The string to skip.
+     *
+     * @return Returns true if the string was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipString(String s) throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip string
+	final int length = s.length();
+	for (int i = 0; i < length; i++) {
+	    char c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if (c != s.charAt(i)) {
+		fCurrentEntity.position -= i + 1;
+		return false;
+	    }
+	    if (i < length - 1 && fCurrentEntity.position == fCurrentEntity.count) {
+		System.arraycopy(fCurrentEntity.ch, fCurrentEntity.count - i - 1, fCurrentEntity.ch, 0, i + 1);
+		// REVISIT: Can a string to be skipped cross an
+		//          entity boundary? -Ac
+		if (load(i + 1, false)) {
+		    fCurrentEntity.position -= i + 1;
+		    return false;
+		}
+	    }
+	}
+	fCurrentEntity.columnNumber += length;
+	return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.load
+    /**
+     * Loads a chunk of text.
+     *
+     * @param offset       The offset into the character buffer to
+     *                     read the next batch of characters.
+     * @param changeEntity True if the load should change entities
+     *                     at the end of the entity, otherwise leave
+     *                     the current entity in place and the entity
+     *                     boundary will be signaled by the return
+     *                     value.
+     *
+     * @returns Returns true if the entity changed as a result of this
+     *          load operation.
+     */
+    final boolean load(int offset, boolean changeEntity)
+	throws IOException {
+
+	// read characters
+	int length = fCurrentEntity.mayReadChunks?
+	    (fCurrentEntity.ch.length - offset):
+	    (DEFAULT_XMLDECL_BUFFER_SIZE);
+	int count = fCurrentEntity.reader.read(fCurrentEntity.ch, offset,
+					       length);
+
+	// reset count and position
+	boolean entityChanged = false;
+	if (count != -1) {
+	    if (count != 0) {
+		fCurrentEntity.count = count + offset;
+		fCurrentEntity.position = offset;
+	    }
+	}
+
+	// end of this entity
+	else {
+	    fCurrentEntity.count = offset;
+	    fCurrentEntity.position = offset;
+	    entityChanged = true;
+	    if (changeEntity) {
+		endEntity();
+		if (fCurrentEntity == null) {
+		    throw new EOFException();
+		}
+		// handle the trailing edges
+		if (fCurrentEntity.position == fCurrentEntity.count) {
+		    load(0, false);
+		}
+	    }
+	}
+
+	return entityChanged;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.RewindableInputStream
+    /**
+     * This class wraps the byte inputstreams we're presented with.
+     * We need it because java.io.InputStreams don't provide
+     * functionality to reread processed bytes, and they have a habit
+     * of reading more than one character when you call their read()
+     * methods.  This means that, once we discover the true (declared)
+     * encoding of a document, we can neither backtrack to read the
+     * whole doc again nor start reading where we are with a new
+     * reader.
+     *
+     * This class allows rewinding an inputStream by allowing a mark
+     * to be set, and the stream reset to that position.  <strong>The
+     * class assumes that it needs to read one character per
+     * invocation when it's read() method is inovked, but uses the
+     * underlying InputStream's read(char[], offset length) method--it
+     * won't buffer data read this way!</strong>
+     *
+     * @author Neil Graham, IBM
+     * @author Glenn Marcy, IBM
+     */
+    private final class RewindableInputStream extends InputStream {
+
+        private InputStream fInputStream;
+        private byte[] fData;
+        private int fStartOffset;
+        private int fEndOffset;
+        private int fOffset;
+        private int fLength;
+        private int fMark;
+
+        public RewindableInputStream(InputStream is) {
+            fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
+            fInputStream = is;
+            fStartOffset = 0;
+            fEndOffset = -1;
+            fOffset = 0;
+            fLength = 0;
+            fMark = 0;
+        }
+
+        public void setStartOffset(int offset) {
+            fStartOffset = offset;
+        }
+
+        public void rewind() {
+            fOffset = fStartOffset;
+        }
+
+        public int read() throws IOException {
+            int b = 0;
+            if (fOffset < fLength) {
+                return fData[fOffset++] & 0xff;
+            }
+            if (fOffset == fEndOffset) {
+                return -1;
+            }
+            if (fOffset == fData.length) {
+                byte[] newData = new byte[fOffset << 1];
+                System.arraycopy(fData, 0, newData, 0, fOffset);
+                fData = newData;
+            }
+            b = fInputStream.read();
+            if (b == -1) {
+                fEndOffset = fOffset;
+                return -1;
+            }
+            fData[fLength++] = (byte)b;
+            fOffset++;
+            return b & 0xff;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                // better get some more for the voracious reader...
+                if (fCurrentEntity.mayReadChunks) {
+                    return fInputStream.read(b, off, len);
+                }
+                int returnedVal = read();
+                if (returnedVal == -1) {
+                    fEndOffset = fOffset;
+                    return -1;
+                }
+                b[off] = (byte)returnedVal;
+                return 1;
+            }
+            if (len < bytesLeft) {
+                if (len <= 0) {
+                    return 0;
+                }
+            }
+            else {
+                len = bytesLeft;
+            }
+            if (b != null) {
+                System.arraycopy(fData, fOffset, b, off, len);
+            }
+            fOffset += len;
+            return len;
+        }
+
+        public long skip(long n)
+            throws IOException
+        {
+            int bytesLeft;
+            if (n <= 0) {
+                return 0;
+            }
+            bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return 0;
+                }
+                return fInputStream.skip(n);
+            }
+            if (n <= bytesLeft) {
+                fOffset += n;
+                return n;
+            }
+            fOffset += bytesLeft;
+            if (fOffset == fEndOffset) {
+                return bytesLeft;
+            }
+            n -= bytesLeft;
+	    /*
+	     * In a manner of speaking, when this class isn't permitting more
+	     * than one byte at a time to be read, it is "blocking".  The
+	     * available() method should indicate how much can be read without
+	     * blocking, so while we're in this mode, it should only indicate
+	     * that bytes in its buffer are available; otherwise, the result of
+	     * available() on the underlying InputStream is appropriate.
+	     */
+            return fInputStream.skip(n) + bytesLeft;
+        }
+
+        public int available() throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                return fCurrentEntity.mayReadChunks ? fInputStream.available()
+		    : 0;
+            }
+            return bytesLeft;
+        }
+
+        public void mark(int howMuch) {
+            fMark = fOffset;
+        }
+
+        public void reset() {
+            fOffset = fMark;
+        }
+
+        public boolean markSupported() {
+            return true;
+        }
+
+        public void close() throws IOException {
+            if (fInputStream != null) {
+                fInputStream.close();
+                fInputStream = null;
+            }
+        }
+    } // end of RewindableInputStream class
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentScannerImpl.dispatch
+    private void scanXMLDecl() throws IOException, JasperException {
+
+	if (skipString("<?xml")) {
+	    fMarkupDepth++;
+	    // NOTE: special case where document starts with a PI
+	    //       whose name starts with "xml" (e.g. "xmlfoo")
+	    if (XMLChar.isName(peekChar())) {
+		fStringBuffer.clear();
+		fStringBuffer.append("xml");
+		while (XMLChar.isName(peekChar())) {
+		    fStringBuffer.append((char)scanChar());
+		}
+		String target = fSymbolTable.addSymbol(fStringBuffer.ch,
+						       fStringBuffer.offset,
+						       fStringBuffer.length);
+		scanPIData(target, fString);
+	    }
+
+	    // standard XML declaration
+	    else {
+		scanXMLDeclOrTextDecl(false);
+	    }
+	}
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '&lt;?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '&lt;?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl) 
+        throws IOException, JasperException {
+
+        // scan decl
+        scanXMLDeclOrTextDecl(scanningTextDecl, fStrings);
+        fMarkupDepth--;
+
+        // pseudo-attribute values
+        String encodingPseudoAttr = fStrings[1];
+
+        // set encoding on reader
+        if (encodingPseudoAttr != null) {
+            isEncodingSetInProlog = true;
+	    encoding = encodingPseudoAttr;
+        }
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     * @param pseudoAttributeValues An array of size 3 to return the version,
+     *                         encoding and standalone pseudo attribute values
+     *                         (in that order).
+     *
+     * <strong>Note:</strong> This method uses fString, anything in it
+     * at the time of calling is lost.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl,
+				       String[] pseudoAttributeValues) 
+                throws IOException, JasperException {
+
+        // pseudo-attribute values
+        String version = null;
+        String encoding = null;
+        String standalone = null;
+
+        // scan pseudo-attributes
+        final int STATE_VERSION = 0;
+        final int STATE_ENCODING = 1;
+        final int STATE_STANDALONE = 2;
+        final int STATE_DONE = 3;
+        int state = STATE_VERSION;
+
+        boolean dataFoundForTarget = false;
+        boolean sawSpace = skipSpaces();
+        while (peekChar() != '?') {
+            dataFoundForTarget = true;
+            String name = scanPseudoAttribute(scanningTextDecl, fString);
+            switch (state) {
+                case STATE_VERSION: {
+                    if (name == fVersionSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                       ? "jsp.error.xml.spaceRequiredBeforeVersionInTextDecl"
+                                       : "jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl",
+                                             null);
+                        }
+                        version = fString.toString();
+                        state = STATE_ENCODING;
+                        if (!version.equals("1.0")) {
+                            // REVISIT: XML REC says we should throw an error
+			    // in such cases.
+                            // some may object the throwing of fatalError.
+                            err.jspError("jsp.error.xml.versionNotSupported",
+					 version);
+                        }
+                    } else if (name == fEncodingSymbol) {
+                        if (!scanningTextDecl) {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                    } else {
+                        if (scanningTextDecl) {
+                            err.jspError("jsp.error.xml.encodingDeclRequired");
+                        }
+                        else {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                    }
+                    break;
+                }
+                case STATE_ENCODING: {
+                    if (name == fEncodingSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                        // TODO: check encoding name; set encoding on
+                        //       entity scanner
+                    } else if (!scanningTextDecl && name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+                        err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                case STATE_STANDALONE: {
+                    if (name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+			err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                default: {
+                    err.jspError("jsp.error.xml.noMorePseudoAttributes");
+                }
+            }
+            sawSpace = skipSpaces();
+        }
+        // REVISIT: should we remove this error reporting?
+        if (scanningTextDecl && state != STATE_DONE) {
+            err.jspError("jsp.error.xml.morePseudoAttributes");
+        }
+        
+        // If there is no data in the xml or text decl then we fail to report
+	// error for version or encoding info above.
+        if (scanningTextDecl) {
+            if (!dataFoundForTarget && encoding == null) {
+                err.jspError("jsp.error.xml.encodingDeclRequired");
+            }
+        } else {
+            if (!dataFoundForTarget && version == null) {
+                err.jspError("jsp.error.xml.versionInfoRequired");
+            }
+        }
+
+        // end
+        if (!skipChar('?')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+        }
+        if (!skipChar('>')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+
+        }
+        
+        // fill in return array
+        pseudoAttributeValues[0] = version;
+        pseudoAttributeValues[1] = encoding;
+        pseudoAttributeValues[2] = standalone;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPseudoAttribute
+    /**
+     * Scans a pseudo attribute.
+     *
+     * @param scanningTextDecl True if scanning this pseudo-attribute for a
+     *                         TextDecl; false if scanning XMLDecl. This 
+     *                         flag is needed to report the correct type of
+     *                         error.
+     * @param value            The string to fill in with the attribute 
+     *                         value.
+     *
+     * @return The name of the attribute
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer2, anything in it
+     * at the time of calling is lost.
+     */
+    public String scanPseudoAttribute(boolean scanningTextDecl, 
+                                      XMLString value) 
+                throws IOException, JasperException {
+
+        String name = scanName();
+        if (name == null) {
+            err.jspError("jsp.error.xml.pseudoAttrNameExpected");
+        }
+        skipSpaces();
+        if (!skipChar('=')) {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.eqRequiredInTextDecl"
+                             : "jsp.error.xml.eqRequiredInXMLDecl",
+			     name);
+        }
+        skipSpaces();
+        int quote = peekChar();
+        if (quote != '\'' && quote != '"') {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.quoteRequiredInTextDecl"
+                             : "jsp.error.xml.quoteRequiredInXMLDecl" ,
+			     name);
+        }
+        scanChar();
+        int c = scanLiteral(quote, value);
+        if (c != quote) {
+            fStringBuffer2.clear();
+            do {
+                fStringBuffer2.append(value);
+                if (c != -1) {
+                    if (c == '&' || c == '%' || c == '<' || c == ']') {
+                        fStringBuffer2.append((char)scanChar());
+                    }
+                    else if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer2);
+                    }
+                    else if (XMLChar.isInvalid(c)) {
+                        String key = scanningTextDecl
+                            ? "jsp.error.xml.invalidCharInTextDecl"
+			    : "jsp.error.xml.invalidCharInXMLDecl";
+                        reportFatalError(key, Integer.toString(c, 16));
+                        scanChar();
+                    }
+                }
+                c = scanLiteral(quote, value);
+            } while (c != quote);
+            fStringBuffer2.append(value);
+            value.setValues(fStringBuffer2);
+        }
+        if (!skipChar(quote)) {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.closeQuoteMissingInTextDecl"
+                             : "jsp.error.xml.closeQuoteMissingInXMLDecl",
+			     name);
+        }
+
+        // return
+        return name;
+
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPIData
+    /**
+     * Scans a processing data. This is needed to handle the situation
+     * where a document starts with a processing instruction whose 
+     * target name <em>starts with</em> "xml". (e.g. xmlfoo)
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer, anything in it
+     * at the time of calling is lost.
+     *
+     * @param target The PI target
+     * @param data The string to fill in with the data
+     */
+    private void scanPIData(String target, XMLString data) 
+        throws IOException, JasperException {
+
+        // check target
+        if (target.length() == 3) {
+            char c0 = Character.toLowerCase(target.charAt(0));
+            char c1 = Character.toLowerCase(target.charAt(1));
+            char c2 = Character.toLowerCase(target.charAt(2));
+            if (c0 == 'x' && c1 == 'm' && c2 == 'l') {
+                err.jspError("jsp.error.xml.reservedPITarget");
+            }
+        }
+
+        // spaces
+        if (!skipSpaces()) {
+            if (skipString("?>")) {
+                // we found the end, there is no data
+                data.clear();
+                return;
+            }
+            else {
+                // if there is data there should be some space
+                err.jspError("jsp.error.xml.spaceRequiredInPI");
+            }
+        }
+
+        fStringBuffer.clear();
+        // data
+        if (scanData("?>", fStringBuffer)) {
+            do {
+                int c = peekChar();
+                if (c != -1) {
+                    if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer);
+                    } else if (XMLChar.isInvalid(c)) {
+                        err.jspError("jsp.error.xml.invalidCharInPI",
+				     Integer.toHexString(c));
+                        scanChar();
+                    }
+                }
+            } while (scanData("?>", fStringBuffer));
+        }
+        data.setValues(fStringBuffer);
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanSurrogates
+    /**
+     * Scans surrogates and append them to the specified buffer.
+     * <p>
+     * <strong>Note:</strong> This assumes the current char has already been
+     * identified as a high surrogate.
+     *
+     * @param buf The StringBuffer to append the read surrogates to.
+     * @returns True if it succeeded.
+     */
+    private boolean scanSurrogates(XMLStringBuffer buf)
+        throws IOException, JasperException {
+
+        int high = scanChar();
+        int low = peekChar();
+        if (!XMLChar.isLowSurrogate(low)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+			 Integer.toString(high, 16));
+            return false;
+        }
+        scanChar();
+
+        // convert surrogates to supplemental character
+        int c = XMLChar.supplemental((char)high, (char)low);
+
+        // supplemental character must be a valid XML character
+        if (!XMLChar.isValid(c)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+			 Integer.toString(c, 16)); 
+            return false;
+        }
+
+        // fill in the buffer
+        buf.append((char)high);
+        buf.append((char)low);
+
+        return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.reportFatalError
+    /**
+     * Convenience function used in all XML scanners.
+     */
+    private void reportFatalError(String msgId, String arg)
+                throws JasperException {
+        err.jspError(msgId, arg);
+    }
+
+}
+
+
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java
new file mode 100644
index 0000000..4311cdd
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * This class is used as a structure to pass text contained in the underlying
+ * character buffer of the scanner. The offset and length fields allow the
+ * buffer to be re-used without creating new character arrays.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * should consider the contents read-only and not make any modifications
+ * to the contents of the buffer. The method receiving this structure
+ * should also not modify the offset and length if this structure (or
+ * the values of this structure) are passed to another method.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * are required to copy the information out of the buffer if it is to be
+ * saved for use beyond the scope of the method. The contents of the 
+ * structure are volatile and the contents of the character buffer cannot
+ * be assured once the method that is passed this structure returns.
+ * Therefore, methods passed this structure should not save any reference
+ * to the structure or the character array contained in the structure.
+ *
+ * @author Eric Ye, IBM
+ * @author Andy Clark, IBM
+ *
+ * @version $Id$
+ */
+public class XMLString {
+
+    //
+    // Data
+    //
+
+    /** The character array. */
+    public char[] ch;
+
+    /** The offset into the character array. */
+    public int offset;
+
+    /** The length of characters from the offset. */
+    public int length;
+
+    //
+    // Constructors
+    //
+
+    /** Default constructor. */
+    public XMLString() {
+    } // <init>()
+
+    /**
+     * Constructs an XMLString structure preset with the specified
+     * values.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public XMLString(char[] ch, int offset, int length) {
+        setValues(ch, offset, length);
+    } // <init>(char[],int,int)
+
+    /**
+     * Constructs an XMLString structure with copies of the values in
+     * the given structure.
+     * <p>
+     * <strong>Note:</strong> This does not copy the character array;
+     * only the reference to the array is copied.
+     *
+     * @param string The XMLString to copy.
+     */
+    public XMLString(XMLString string) {
+        setValues(string);
+    } // <init>(XMLString)
+
+    //
+    // Public methods
+    //
+
+    /**
+     * Initializes the contents of the XMLString structure with the
+     * specified values.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public void setValues(char[] ch, int offset, int length) {
+        this.ch = ch;
+        this.offset = offset;
+        this.length = length;
+    } // setValues(char[],int,int)
+
+    /**
+     * Initializes the contents of the XMLString structure with copies
+     * of the given string structure.
+     * <p>
+     * <strong>Note:</strong> This does not copy the character array;
+     * only the reference to the array is copied.
+     * 
+     * @param s
+     */
+    public void setValues(XMLString s) {
+        setValues(s.ch, s.offset, s.length);
+    } // setValues(XMLString)
+
+    /** Resets all of the values to their defaults. */
+    public void clear() {
+        this.ch = null;
+        this.offset = 0;
+        this.length = -1;
+    } // clear()
+
+    /**
+     * Returns true if the contents of this XMLString structure and
+     * the specified array are equal.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public boolean equals(char[] ch, int offset, int length) {
+        if (ch == null) {
+            return false;
+        }
+        if (this.length != length) {
+            return false;
+        }
+
+        for (int i=0; i<length; i++) {
+            if (this.ch[this.offset+i] != ch[offset+i] ) {
+                return false;
+            }
+        }
+        return true;
+    } // equals(char[],int,int):boolean
+
+    /**
+     * Returns true if the contents of this XMLString structure and
+     * the specified string are equal.
+     * 
+     * @param s The string to compare.
+     */
+    public boolean equals(String s) {
+        if (s == null) {
+            return false;
+        }
+        if ( length != s.length() ) {
+            return false;
+        }
+
+        // is this faster than call s.toCharArray first and compare the 
+        // two arrays directly, which will possibly involve creating a
+        // new char array object.
+        for (int i=0; i<length; i++) {
+            if (ch[offset+i] != s.charAt(i)) {
+                return false;
+            }
+        }
+
+        return true;
+    } // equals(String):boolean
+
+    //
+    // Object methods
+    //
+
+    /** Returns a string representation of this object. */
+    public String toString() {
+        return length > 0 ? new String(ch, offset, length) : "";
+    } // toString():String
+
+} // class XMLString
diff --git a/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java
new file mode 100644
index 0000000..69802d0
--- /dev/null
+++ b/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * XMLString is a structure used to pass character arrays. However,
+ * XMLStringBuffer is a buffer in which characters can be appended
+ * and extends XMLString so that it can be passed to methods
+ * expecting an XMLString object. This is a safe operation because
+ * it is assumed that any callee will <strong>not</strong> modify
+ * the contents of the XMLString structure.
+ * <p> 
+ * The contents of the string are managed by the string buffer. As
+ * characters are appended, the string buffer will grow as needed.
+ * <p>
+ * <strong>Note:</strong> Never set the <code>ch</code>, 
+ * <code>offset</code>, and <code>length</code> fields directly.
+ * These fields are managed by the string buffer. In order to reset
+ * the buffer, call <code>clear()</code>.
+ * 
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ *
+ * @version $Id$
+ */
+public class XMLStringBuffer
+    extends XMLString {
+
+    //
+    // Constants
+    //
+
+    /** Default buffer size (32). */
+    public static final int DEFAULT_SIZE = 32;
+
+    //
+    // Constructors
+    //
+
+    /**
+     * 
+     */
+    public XMLStringBuffer() {
+        this(DEFAULT_SIZE);
+    } // <init>()
+
+    /**
+     * 
+     * 
+     * @param size 
+     */
+    public XMLStringBuffer(int size) {
+        ch = new char[size];
+    } // <init>(int)
+
+    /** Constructs a string buffer from a char. */
+    public XMLStringBuffer(char c) {
+        this(1);
+        append(c);
+    } // <init>(char)
+
+    /** Constructs a string buffer from a String. */
+    public XMLStringBuffer(String s) {
+        this(s.length());
+        append(s);
+    } // <init>(String)
+
+    /** Constructs a string buffer from the specified character array. */
+    public XMLStringBuffer(char[] ch, int offset, int length) {
+        this(length);
+        append(ch, offset, length);
+    } // <init>(char[],int,int)
+
+    /** Constructs a string buffer from the specified XMLString. */
+    public XMLStringBuffer(XMLString s) {
+        this(s.length);
+        append(s);
+    } // <init>(XMLString)
+
+    //
+    // Public methods
+    //
+
+    /** Clears the string buffer. */
+    public void clear() {
+        offset = 0;
+        length = 0;
+    }
+
+    /**
+     * append
+     * 
+     * @param c 
+     */
+    public void append(char c) {
+        if (this.length + 1 > this.ch.length) {
+                    int newLength = this.ch.length*2;
+                    if (newLength < this.ch.length + DEFAULT_SIZE)
+                        newLength = this.ch.length + DEFAULT_SIZE;
+                    char[] newch = new char[newLength];
+                    System.arraycopy(this.ch, 0, newch, 0, this.length);
+                    this.ch = newch;
+        }
+        this.ch[this.length] = c;
+        this.length++;
+    } // append(char)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(String s) {
+        int length = s.length();
+        if (this.length + length > this.ch.length) {
+            int newLength = this.ch.length*2;
+            if (newLength < this.length + length + DEFAULT_SIZE)
+                newLength = this.ch.length + length + DEFAULT_SIZE;
+            char[] newch = new char[newLength];            
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        s.getChars(0, length, this.ch, this.length);
+        this.length += length;
+    } // append(String)
+
+    /**
+     * append
+     * 
+     * @param ch 
+     * @param offset 
+     * @param length 
+     */
+    public void append(char[] ch, int offset, int length) {
+        if (this.length + length > this.ch.length) {
+            char[] newch = new char[this.ch.length + length + DEFAULT_SIZE];
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        System.arraycopy(ch, offset, this.ch, this.length, length);
+        this.length += length;
+    } // append(char[],int,int)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(XMLString s) {
+        append(s.ch, s.offset, s.length);
+    } // append(XMLString)
+
+} // class XMLStringBuffer