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><jsp-file></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>
+ * <taskdef classname="org.apache.jasper.JspC" name="jasper2" >
+ * <classpath>
+ * <pathelement location="${java.home}/../lib/tools.jar"/>
+ * <fileset dir="${ENV.CATALINA_HOME}/server/lib">
+ * <include name="*.jar"/>
+ * </fileset>
+ * <fileset dir="${ENV.CATALINA_HOME}/common/lib">
+ * <include name="*.jar"/>
+ * </fileset>
+ * <path refid="myjars"/>
+ * </classpath>
+ * </taskdef>
+ *
+ * <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" />
+ * </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("\"", """));
+ 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("\"", """));
+ 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 =, >, <, ", 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
+ * "page", "request", "session", and
+ * "application"
+ */
+ 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("<");
+ } else if (c == '>') {
+ sb.append(">");
+ } else if (c == '\'') {
+ sb.append("'");
+ } else if (c == '&') {
+ sb.append("&");
+ } else if (c == '"') {
+ sb.append(""");
+ } 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 <jsp:text> element
+ */
+ public static class JspText extends Node {
+
+ public JspText(String qName, Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, TEXT_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a Named Attribute (<jsp:attribute>)
+ */
+ public static class NamedAttribute extends Node {
+
+ // A unique temporary variable name suitable for code generation
+ private String temporaryVariableName;
+
+ // True if this node is to be trimmed, or false otherwise
+ private boolean trim = true;
+
+ 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 (<jsp:body>)
+ */
+ public static class JspBody extends Node {
+
+ private ChildInfo childInfo;
+
+ public JspBody(Mark start, Node parent) {
+ this(JSP_BODY_ACTION, null, null, start, parent);
+ }
+
+ public JspBody(String qName, Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, BODY_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ this.childInfo = new ChildInfo();
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public ChildInfo getChildInfo() {
+ return childInfo;
+ }
+ }
+
+ /**
+ * Represents a template text string
+ */
+ public static class TemplateText extends Node {
+
+ private ArrayList 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 "]]>")
+ * 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 ::= ''' | '"' | '\\' | '\"' | "\'" | '\>' | '\$' |
+ * 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 = "<%@ page";
+ if (isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.directive.istagfile",
+ directive);
+ }
+ parsePageDirective(parent);
+ } else if (reader.matches("include")) {
+ directive = "<%@ 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 = "<%@ taglib";
+ parseTaglibDirective(parent);
+ } else if (reader.matches("tag")) {
+ directive = "<%@ tag";
+ if (!isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+ directive);
+ }
+ parseTagDirective(parent);
+ } else if (reader.matches("attribute")) {
+ directive = "<%@ attribute";
+ if (!isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+ directive);
+ }
+ parseAttributeDirective(parent);
+ } else if (reader.matches("variable")) {
+ directive = "<%@ 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",
+ "<" + 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",
+ "<" + eTag);
+ }
+ parseTagDirective(parent);
+ } else if (reader.matches("attribute")) {
+ eTag = "jsp:directive.attribute";
+ if (!isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+ "<" + eTag);
+ }
+ parseAttributeDirective(parent);
+ } else if (reader.matches("variable")) {
+ eTag = "jsp:directive.variable";
+ if (!isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+ "<" + 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", "<" + eTag);
+ }
+ } else if (!reader.matches("/>")) {
+ err.jspError(start, "jsp.error.unterminated", "<" + 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", "<%--");
+ }
+
+ 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", "<%!");
+ }
+
+ 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",
+ "<jsp:declaration>");
+ }
+ Mark stop;
+ String text;
+ while (true) {
+ start = reader.mark();
+ stop = reader.skipUntil("<");
+ if (stop == null) {
+ err.jspError(start, "jsp.error.unterminated",
+ "<jsp:declaration>");
+ }
+ 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",
+ "<jsp:declaration>");
+ }
+ }
+ }
+
+ /*
+ * 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", "<%=");
+ }
+
+ 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",
+ "<jsp:expression>");
+ }
+ Mark stop;
+ String text;
+ while (true) {
+ start = reader.mark();
+ stop = reader.skipUntil("<");
+ if (stop == null) {
+ err.jspError(start, "jsp.error.unterminated",
+ "<jsp:expression>");
+ }
+ 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",
+ "<jsp:expression>");
+ }
+ }
+ }
+
+ /*
+ * 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", "<%");
+ }
+
+ 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",
+ "<jsp:scriptlet>");
+ }
+ Mark stop;
+ String text;
+ while (true) {
+ start = reader.mark();
+ stop = reader.skipUntil("<");
+ if (stop == null) {
+ err.jspError(start, "jsp.error.unterminated",
+ "<jsp:scriptlet>");
+ }
+ 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",
+ "<jsp:scriptlet>");
+ }
+ }
+ }
+
+ /**
+ * 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", "<" + tag);
+ }
+ } else {
+ err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
+ "<" + tag);
+ }
+ } else {
+ err.jspError(reader.mark(), "jsp.error.unterminated", "<" + 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", "<" + 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", "<"
+ + 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", "<"
+ + 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",
+ "<jsp:invoke");
+ }
+ parseInvoke(parent);
+ } else if (reader.matches(DOBODY_ACTION)) {
+ if (!isTagFile) {
+ err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
+ "<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",
+ "<jsp:text>");
+ }
+ 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",
+ "<jsp:text>");
+ } 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",
+ "<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", "<" + 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", "<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", "<"
+ + 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", "<" + 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",
+ "<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 <{0}> cannot have template data. Template data must be encapsulated within a <jsp:cdata> element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=Validation Error: Element <{0}> cannot have template data. Template data must be encapsulated within a <jsp:text> 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 ( <%!, <jsp:declaration, <%=, <jsp:expression, <%, <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=<jsp:output>: illegal to have multiple occurrences of \"{0}\" with different values (old: {1}, new: {2})
+jsp.error.jspoutput.doctypenamesystem=<jsp:output>: 'doctype-root-element' and 'doctype-system' attributes must appear together
+jsp.error.jspoutput.doctypepulicsystem=<jsp:output>: 'doctype-system' attribute must appear if 'doctype-public' attribute appears
+jsp.error.jspoutput.nonemptybody=<jsp:output> must not have a body
+jsp.error.jspoutput.invalidUse=<jsp:output> 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=\'<\', when appears in the body of <jsp:text>, 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=<jsp:text> 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 <jsp:root>
+jsp.error.unbalanced.endtag=The end tag \"</{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 <{0}> cannot have template data. Template data must be encapsulated within a <jsp:cdata> element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata = Error de Validaci\u00F3n\: El Elemento <{0}> no puede tener datos plantilla. Los datos plantilla deben de estar encapsulados dentro de un elemento <jsp\:text>. [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 (<%\!, <jsp\:declaration, <%\=, <jsp\:expression, <%, <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 = <jsp\:output>\: ilegal tener ocurrencias m\u00FAltiples de "{0}" con diferentes valores (viejo\: {1}, nuevo\: {2})
+jsp.error.jspoutput.doctypenamesystem = <jsp\:output>\: atributos 'doctype-root-element' y 'doctype-system' deben de aparecer juntos
+jsp.error.jspoutput.doctypepulicsystem = <jsp\:output>\: atributo 'doctype-system' debe de aparecer si aparece atributo 'doctype-public'
+jsp.error.jspoutput.nonemptybody = <jsp\:output> no debe de tener un cuerpo
+jsp.error.jspoutput.invalidUse = <jsp\:output> 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 = '<', cuando aparece en el cuerpo de <jsp\:text>, 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 = <jsp\:text> 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 = <jsp\:root> anidado
+jsp.error.unbalanced.endtag = El tgag final "</{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 <{0}> ne peut avoir de données template. Les données Template doivent être encapsulées à l''intérieur d''un élément <jsp:cdata>. [JSP1.2 PFD section 5.1.9]\nDonnée Template en erreur: {1}
+jspx.error.templateDataNotInJspCdata=Erreur de validation: l''élément <{0}> ne peut avoir de données template. Les données Template doivent être encapsulées à l''intérieur d''un élément <jsp:text>. [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 <{0}> cannot have template data. Template data must be encapsulated within a <jsp:cdata> element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=\u8a3c\u660e\u30a8\u30e9\u30fc: \u8981\u7d20<{0}>\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<jsp:text>\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 ( <%!\u3001<jsp:declaration\u3001<%=\u3001<jsp:expression\u3001<%\u3001<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=<jsp:output>: \"{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=<jsp:output>: '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=<jsp:output>: '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=<jsp:output> \u30dc\u30c7\u30a3\u3092\u6301\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.invalidUse=<jsp:output> \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=\'<\'\u304c<jsp:text>\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=<jsp:text> \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 <jsp:root> \u3067\u3059
+jsp.error.unbalanced.endtag=\u7d42\u4e86\u30bf\u30b0 \"</{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("<");
+ } else if (c == '>') {
+ sb.append(">");
+ } else if (c == '\'') {
+ sb.append("'"); // '
+ } else if (c == '&') {
+ sb.append("&");
+ } else if (c == '"') {
+ sb.append("""); // "
+ } 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("<");
+ break;
+ case '>':
+ result.append(">");
+ break;
+ case '&':
+ result.append("&");
+ break;
+ case '"':
+ result.append(""");
+ 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['&'] = "&".toCharArray();
+ specialCharactersRepresentation['<'] = "<".toCharArray();
+ specialCharactersRepresentation['>'] = ">".toCharArray();
+ specialCharactersRepresentation['"'] = """.toCharArray();
+ specialCharactersRepresentation['\''] = "'".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):
+ *
+ * & -> &
+ * < -> <
+ * > -> >
+ * " -> "
+ * ' -> '
+ *
+ * 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 <import>: " +
+ "Target servlet called getWriter(), then getOutputStream()");
+ isWriterUsed = true;
+ return new PrintWriter(sw);
+ }
+
+ public ServletOutputStream getOutputStream() {
+ if (isWriterUsed)
+ throw new IllegalStateException("Unexpected internal error during <import>: " +
+ "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 <import>\");");
+ 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 <import> 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 <import> 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" +
+ "(\"<param> outside <import> or <urlEncode>\");");
+ 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 <set> 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 <set>:\"+" + 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 '<' and '&'.
+ * 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 '<', '&', 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 ::= '<?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.
+ */
+ 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