"));
+ }
+ }
+
+ initClassPath();
+
+ if (context instanceof org.apache.struts2.jasper.servlet.JspCServletContext) {
+ return;
+ }
+
+ if (System.getSecurityManager() != null) {
+ 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) {
+ if (appBase.endsWith(File.separator)) {
+ appBase = appBase.substring(0, appBase.length() - 1);
+ }
+ String directory =
+ appBase.substring(appBase.lastIndexOf(File.separator));
+ threadName = threadName + "[" + directory + "]";
+ threadStart();
+ }
+ }
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * This web applications ServletContext
+ */
+ private ServletContext context;
+ private Options options;
+ private URLClassLoader parentClassLoader;
+ private PermissionCollection permissionCollection;
+ private CodeSource codeSource;
+ private String classpath;
+
+ /**
+ * Maps JSP pages to their JspServletWrapper's
+ */
+ private Map jsps = Collections.synchronizedMap(new HashMap());
+
+
+ /**
+ * The background thread.
+ */
+ private Thread thread = null;
+
+
+ /**
+ * The background thread completion semaphore.
+ */
+ private boolean threadDone = false;
+
+
+ /**
+ * Name to register for the background thread.
+ */
+ private String threadName = "JspRuntimeContext";
+
+ // ------------------------------------------------------ Public Methods
+
+ /**
+ * Add a new JspServletWrapper.
+ *
+ * @param jspUri JSP URI
+ * @param jsw Servlet wrapper for JSP
+ */
+ public void addWrapper(String jspUri, JspServletWrapper jsw) {
+ jsps.remove(jspUri);
+ jsps.put(jspUri, jsw);
+ }
+
+ /**
+ * Get an already existing JspServletWrapper.
+ *
+ * @param jspUri JSP URI
+ * @return JspServletWrapper for JSP
+ */
+ public JspServletWrapper getWrapper(String jspUri) {
+ return (JspServletWrapper) 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() {
+ threadStop();
+
+ 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;
+ }
+
+
+ // -------------------------------------------------------- Private Methods
+
+ /**
+ * Method used by background thread to check the JSP dependencies
+ * registered with this class for JSP's.
+ */
+ private void checkCompile() {
+ 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;
+ }
+
+ /**
+ * 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.struts2.jasper.runtime.HttpJspBase
+ permissionCollection.add(new RuntimePermission(
+ "accessClassInPackage.org.apache.struts2.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);
+ }
+ }
+ }
+
+
+ // -------------------------------------------------------- Thread Support
+
+ /**
+ * Start the background thread that will periodically check for
+ * changes to compile time included files in a JSP.
+ *
+ * @throws IllegalStateException if we should not be starting
+ * a background thread now
+ */
+ protected void threadStart() {
+
+ // Has the background thread already been started?
+ if (thread != null) {
+ return;
+ }
+
+ // Start the background thread
+ threadDone = false;
+ thread = new Thread(this, threadName);
+ thread.setDaemon(true);
+ thread.start();
+
+ }
+
+
+ /**
+ * Stop the background thread that is periodically checking for
+ * changes to compile time included files in a JSP.
+ */
+ protected void threadStop() {
+
+ if (thread == null) {
+ return;
+ }
+
+ threadDone = true;
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ ;
+ }
+
+ thread = null;
+
+ }
+
+ /**
+ * Sleep for the duration specified by the checkInterval
+ * property.
+ */
+ protected void threadSleep() {
+
+ try {
+ Thread.sleep(options.getCheckInterval() * 1000L);
+ } catch (InterruptedException e) {
+ ;
+ }
+
+ }
+
+
+ // ------------------------------------------------------ Background Thread
+
+
+ /**
+ * The background thread that checks for changes to files
+ * included by a JSP and flags that a recompile is required.
+ */
+ public void run() {
+
+ // Loop until the termination semaphore is set
+ while (!threadDone) {
+
+ // Wait for our check interval
+ threadSleep();
+
+ // Check for included files which are newer than the
+ // JSP which uses them.
+ try {
+ checkCompile();
+ } catch (Throwable t) {
+ log.error("Exception checking if recompile needed: ", t);
+ }
+ }
+
+ }
+
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java
new file mode 100644
index 000000000..aa6ea6b0d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java
@@ -0,0 +1,1118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.commons.el.ExpressionEvaluatorImpl;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ELParseException;
+import javax.servlet.jsp.el.FunctionMapper;
+import java.io.*;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * 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();
+
+ 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.HACK_CHAR, '$'));
+ }
+
+ /**
+ * 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 replace with the
+ * string with .
+ */
+ 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 Class object associated with the class or
+ * interface with the given string name.
+ *
+ * The Class object is determined by passing the given string
+ * name to the Class.forName() method, unless the given string
+ * name represents a primitive type, in which case it is converted to a
+ * Class 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.struts2.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
+ */
+ public static void validateExpressions(Mark where,
+ String expressions,
+ Class expectedType,
+ FunctionMapper functionMapper,
+ ErrorDispatcher err)
+ throws JasperException {
+
+ try {
+ JspUtil.expressionEvaluator.parseExpression(expressions,
+ expectedType, null);
+ }
+ 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)
+ */
+ public static void resetTemporaryVariableName() {
+ tempSequenceNumber = 0;
+ }
+
+ /**
+ * Generates a new temporary variable name.
+ * (not thread-safe)
+ */
+ public static String nextTemporaryVariableName() {
+ return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+ }
+
+ public static String coerceToPrimitiveBoolean(String s,
+ boolean isNamedAttribute) {
+ if (isNamedAttribute) {
+ return "org.apache.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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.struts2.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
+ */
+ public static String getTagHandlerClassName(String path,
+ 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 = "org.apache.jsp.tag.meta.";
+ begin = index + META_INF_TAGS.length();
+ } else {
+ err.jspError("jsp.error.tagfile.illegalPath", path);
+ }
+ }
+
+ className += makeJavaPackage(path.substring(begin));
+
+ return className;
+ }
+
+ /**
+ * 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 {
+
+ InputStreamReader reader = null;
+ InputStream in = getInputStream(fname, jarFile, ctxt, err);
+
+ try {
+ reader = new InputStreamReader(in, encoding);
+ } catch (UnsupportedEncodingException ex) {
+ err.jspError("jsp.error.unsupported.encoding", encoding);
+ }
+
+ return reader;
+ }
+
+ /**
+ * Class.getName() return arrays in the form "[[[", where et,
+ * the element type can be one of ZBCDFIJS or L;
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java
new file mode 100644
index 000000000..fefa11a8a
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Mark.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Mark.java
new file mode 100644
index 000000000..c94adb775
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JspCompilationContext;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Stack;
+
+/**
+ * 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
+ * @throws 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 other
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java
new file mode 100644
index 000000000..61f66541b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Node.java
@@ -0,0 +1,2368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.xml.sax.Attributes;
+
+import javax.servlet.jsp.tagext.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * 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 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.
+ *
+ * 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;
+
+ /*
+ * 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;
+ }
+
+ /**
+ * @return The enclosing root to this Root. Usually represents the
+ * page that includes this one.
+ */
+ public Root getParentRoot() {
+ return parentRoot;
+ }
+ }
+
+ /**
+ * Represents the root of a Jsp document (XML syntax)
+ */
+ public static class JspRoot extends Node {
+
+ public JspRoot(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+ Mark start, Node parent) {
+ super(qName, ROOT_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a page directive
+ */
+ public static class PageDirective extends Node {
+
+ private 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 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 tag file action
+ */
+ public static class DoBodyAction extends Node {
+
+ public DoBodyAction(Attributes attrs, Mark start, Node parent) {
+ this(JSP_DOBODY_ACTION, attrs, null, null, start, parent);
+ }
+
+ public DoBodyAction(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, DOBODY_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a Jsp comment
+ * Comments are kept for completeness.
+ */
+ public static class Comment extends Node {
+
+ public Comment(String text, Mark start, Node parent) {
+ super(null, null, text, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents an expression, declaration, or scriptlet
+ */
+ public static abstract class ScriptingElement extends Node {
+
+ public ScriptingElement(String qName, String localName, String text,
+ Mark start, Node parent) {
+ super(qName, localName, text, start, parent);
+ }
+
+ public ScriptingElement(String qName, String localName,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start,
+ Node parent) {
+ super(qName, localName, null, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ /**
+ * When this node was created from a JSP page in JSP syntax, its text
+ * was stored as a String in the "text" field, whereas when this node
+ * was created from a JSP document, its text was stored as one or more
+ * TemplateText nodes in its body. This method handles either case.
+ *
+ * @return The text string
+ */
+ public String getText() {
+ String ret = text;
+ if ((ret == null) && (body != null)) {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < body.size(); i++) {
+ buf.append(body.getNode(i).getText());
+ }
+ ret = buf.toString();
+ }
+ return ret;
+ }
+
+ /**
+ * For the same reason as above, the source line information in the
+ * contained TemplateText node should be used.
+ */
+ public Mark getStart() {
+ if (text == null && body != null && body.size() > 0) {
+ return body.getNode(0).getStart();
+ } else {
+ return super.getStart();
+ }
+ }
+ }
+
+ /**
+ * Represents a declaration
+ */
+ public static class Declaration extends ScriptingElement {
+
+ public Declaration(String text, Mark start, Node parent) {
+ super(JSP_DECLARATION_ACTION, DECLARATION_ACTION, text, start,
+ parent);
+ }
+
+ public Declaration(String qName, Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start,
+ Node parent) {
+ super(qName, DECLARATION_ACTION, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents an expression. Expressions in attributes are embedded
+ * in the attribute string and not here.
+ */
+ public static class Expression extends ScriptingElement {
+
+ public Expression(String text, Mark start, Node parent) {
+ super(JSP_EXPRESSION_ACTION, EXPRESSION_ACTION, text, start,
+ parent);
+ }
+
+ public Expression(String qName, Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start,
+ Node parent) {
+ super(qName, EXPRESSION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a scriptlet
+ */
+ public static class Scriptlet extends ScriptingElement {
+
+ public Scriptlet(String text, Mark start, Node parent) {
+ super(JSP_SCRIPTLET_ACTION, SCRIPTLET_ACTION, text, start, parent);
+ }
+
+ public Scriptlet(String qName, Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start,
+ Node parent) {
+ super(qName, SCRIPTLET_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents an EL expression. Expressions in attributes are embedded
+ * in the attribute string and not here.
+ */
+ public static class ELExpression extends Node {
+
+ private ELNode.Nodes el;
+
+ public ELExpression(String text, Mark start, Node parent) {
+ super(null, null, text, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setEL(ELNode.Nodes el) {
+ this.el = el;
+ }
+
+ public ELNode.Nodes getEL() {
+ return el;
+ }
+ }
+
+ /**
+ * Represents a param action
+ */
+ public static class ParamAction extends Node {
+
+ JspAttribute value;
+
+ public ParamAction(Attributes attrs, Mark start, Node parent) {
+ this(JSP_PARAM_ACTION, attrs, null, null, start, parent);
+ }
+
+ public ParamAction(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, PARAM_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setValue(JspAttribute value) {
+ this.value = value;
+ }
+
+ public JspAttribute getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * Represents a params action
+ */
+ public static class ParamsAction extends Node {
+
+ public ParamsAction(Mark start, Node parent) {
+ this(JSP_PARAMS_ACTION, null, null, start, parent);
+ }
+
+ public ParamsAction(String qName,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs,
+ Mark start, Node parent) {
+ super(qName, PARAMS_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a fallback action
+ */
+ public static class FallBackAction extends Node {
+
+ public FallBackAction(Mark start, Node parent) {
+ this(JSP_FALLBACK_ACTION, null, null, start, parent);
+ }
+
+ public FallBackAction(String qName,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start,
+ Node parent) {
+ super(qName, FALLBACK_ACTION, null, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents an include action
+ */
+ public static class IncludeAction extends Node {
+
+ private JspAttribute page;
+
+ public IncludeAction(Attributes attrs, Mark start, Node parent) {
+ this(JSP_INCLUDE_ACTION, attrs, null, null, start, parent);
+ }
+
+ public IncludeAction(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, INCLUDE_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setPage(JspAttribute page) {
+ this.page = page;
+ }
+
+ public JspAttribute getPage() {
+ return page;
+ }
+ }
+
+ /**
+ * Represents a forward action
+ */
+ public static class ForwardAction extends Node {
+
+ private JspAttribute page;
+
+ public ForwardAction(Attributes attrs, Mark start, Node parent) {
+ this(JSP_FORWARD_ACTION, attrs, null, null, start, parent);
+ }
+
+ public ForwardAction(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, FORWARD_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setPage(JspAttribute page) {
+ this.page = page;
+ }
+
+ public JspAttribute getPage() {
+ return page;
+ }
+ }
+
+ /**
+ * Represents a getProperty action
+ */
+ public static class GetProperty extends Node {
+
+ public GetProperty(Attributes attrs, Mark start, Node parent) {
+ this(JSP_GET_PROPERTY_ACTION, attrs, null, null, start, parent);
+ }
+
+ public GetProperty(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, GET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+ }
+
+ /**
+ * Represents a setProperty action
+ */
+ public static class SetProperty extends Node {
+
+ private JspAttribute value;
+
+ public SetProperty(Attributes attrs, Mark start, Node parent) {
+ this(JSP_SET_PROPERTY_ACTION, attrs, null, null, start, parent);
+ }
+
+ public SetProperty(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs, Mark start, Node parent) {
+ super(qName, SET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setValue(JspAttribute value) {
+ this.value = value;
+ }
+
+ public JspAttribute getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * Represents a useBean action
+ */
+ public static class UseBean extends Node {
+
+ JspAttribute beanName;
+
+ public UseBean(Attributes attrs, Mark start, Node parent) {
+ this(JSP_USE_BEAN_ACTION, attrs, null, null, start, parent);
+ }
+
+ public UseBean(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+ Mark start, Node parent) {
+ super(qName, USE_BEAN_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setBeanName(JspAttribute beanName) {
+ this.beanName = beanName;
+ }
+
+ public JspAttribute getBeanName() {
+ return beanName;
+ }
+ }
+
+ /**
+ * Represents a plugin action
+ */
+ public static class PlugIn extends Node {
+
+ private JspAttribute width;
+ private JspAttribute height;
+
+ public PlugIn(Attributes attrs, Mark start, Node parent) {
+ this(JSP_PLUGIN_ACTION, attrs, null, null, start, parent);
+ }
+
+ public PlugIn(String qName, Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+ Mark start, Node parent) {
+ super(qName, PLUGIN_ACTION, attrs, nonTaglibXmlnsAttrs,
+ taglibAttrs, start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setHeight(JspAttribute height) {
+ this.height = height;
+ }
+
+ public void setWidth(JspAttribute width) {
+ this.width = width;
+ }
+
+ public JspAttribute getHeight() {
+ return height;
+ }
+
+ public JspAttribute getWidth() {
+ return width;
+ }
+ }
+
+ /**
+ * Represents an uninterpreted tag, from a Jsp document
+ */
+ public static class UninterpretedTag extends Node {
+
+ private JspAttribute[] jspAttrs;
+
+ public UninterpretedTag(String qName, String localName,
+ Attributes attrs,
+ Attributes nonTaglibXmlnsAttrs,
+ Attributes taglibAttrs,
+ Mark start, Node parent) {
+ super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+ start, parent);
+ }
+
+ public void accept(Visitor v) throws JasperException {
+ v.visit(this);
+ }
+
+ public void setJspAttributes(JspAttribute[] jspAttrs) {
+ this.jspAttrs = jspAttrs;
+ }
+
+ public JspAttribute[] getJspAttributes() {
+ return jspAttrs;
+ }
+ }
+
+ /**
+ * Represents a .
+ */
+ 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 .
+ */
+ 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 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);
+ }
+
+ /*
+ * 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.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 JspAttribute[] getJspAttributes() {
+ return jspAttrs;
+ }
+
+ public ChildInfo getChildInfo() {
+ return childInfo;
+ }
+
+ public void setTagData(TagData tagData) {
+ this.tagData = tagData;
+ this.varInfos = tagInfo.getVariableInfo(tagData);
+ if (this.varInfos == null) {
+ this.varInfos = ZERO_VARIABLE_INFO;
+ }
+ }
+
+ public TagData getTagData() {
+ return tagData;
+ }
+
+ public void setTagHandlerPoolName(String s) {
+ tagHandlerPoolName = s;
+ }
+
+ public String getTagHandlerPoolName() {
+ return tagHandlerPoolName;
+ }
+
+ public TagInfo getTagInfo() {
+ return tagInfo;
+ }
+
+ public TagFileInfo getTagFileInfo() {
+ return tagFileInfo;
+ }
+
+ /*
+ * @return true if this custom action is supported by a tag file,
+ * false otherwise
+ */
+ public boolean isTagFile() {
+ return tagFileInfo != null;
+ }
+
+ public Class getTagHandlerClass() {
+ return tagHandlerClass;
+ }
+
+ public void setTagHandlerClass(Class hc) {
+ tagHandlerClass = hc;
+ }
+
+ public boolean implementsIterationTag() {
+ return implementsIterationTag;
+ }
+
+ public boolean implementsBodyTag() {
+ return implementsBodyTag;
+ }
+
+ public boolean implementsTryCatchFinally() {
+ return implementsTryCatchFinally;
+ }
+
+ public boolean implementsSimpleTag() {
+ return implementsSimpleTag;
+ }
+
+ public boolean implementsDynamicAttributes() {
+ return implementsDynamicAttributes;
+ }
+
+ public TagVariableInfo[] getTagVariableInfos() {
+ return tagInfo.getTagVariableInfos();
+ }
+
+ public VariableInfo[] getVariableInfos() {
+ return varInfos;
+ }
+
+ public void setCustomTagParent(Node.CustomTag n) {
+ this.customTagParent = n;
+ }
+
+ public Node.CustomTag getCustomTagParent() {
+ return this.customTagParent;
+ }
+
+ public void setNumCount(Integer count) {
+ this.numCount = count;
+ }
+
+ public Integer getNumCount() {
+ return this.numCount;
+ }
+
+ public void setScriptingVars(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:
+ *
+ *
+ * -- nesting level 0
+ *
+ *
+ * -- nesting level 1
+ * -- nesting level 2
+ *
+ *
+ * -- nesting level 1
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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 = JspUtil.nextTemporaryVariableName();
+ }
+ return temporaryVariableName;
+ }
+
+ /*
+ * Get the attribute value from this named 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
+ // 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 ELNode.Nodes el;
+
+ // If true, this JspAttribute represents a
+ private boolean namedAttribute;
+ // The node in the parse tree for the NamedAttribute
+ private NamedAttribute namedAttributeNode;
+
+ JspAttribute(String qName, String uri, String localName, String value,
+ boolean expr, ELNode.Nodes el, boolean dyn) {
+ this.qName = qName;
+ this.uri = uri;
+ this.localName = localName;
+ this.value = value;
+ this.namedAttributeNode = null;
+ this.expression = expr;
+ this.el = el;
+ this.dynamic = dyn;
+ this.namedAttribute = false;
+ }
+
+ /**
+ * Use this constructor if the JspAttribute represents a
+ * named attribute. In this case, we have to store the nodes of
+ * the body of the attribute.
+ */
+ JspAttribute(NamedAttribute na, boolean dyn) {
+ this.qName = na.getName();
+ this.localName = na.getLocalName();
+ this.value = null;
+ this.namedAttributeNode = na;
+ this.expression = false;
+ this.el = null;
+ this.dynamic = dyn;
+ this.namedAttribute = true;
+ }
+
+ /**
+ * @return The name of the attribute
+ */
+ public String getName() {
+ return qName;
+ }
+
+ /**
+ * @return The local name of the attribute
+ */
+ public String getLocalName() {
+ return localName;
+ }
+
+ /**
+ * @return The namespace of the attribute, or null if in the default
+ * namespace
+ */
+ public String getURI() {
+ return uri;
+ }
+
+ /**
+ * Only makes sense if namedAttribute is false.
+ *
+ * @return the value for the attribute, or the expression string
+ * (stripped of "<%=", "%>", "%=", or "%"
+ * but containing "${" and "}" for EL expressions)
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * 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 false for string literals or rtexprvalues that should
+ * not be interpreted or reevaluated
+ */
+ public boolean isELInterpreterInput() {
+ return el != null;
+ }
+
+ /**
+ * @return true if the value is a string literal known at translation
+ * time.
+ */
+ public boolean isLiteral() {
+ return !expression && (el != null) && !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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java
new file mode 100644
index 000000000..7c13bce8b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageDataImpl.java
@@ -0,0 +1,712 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+import javax.servlet.jsp.tagext.PageData;
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayWriter;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ListIterator;
+
+/**
+ * An implementation of javax.servlet.jsp.tagext.PageData 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 = "\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("\n");
+ }
+ }
+}
+
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java
new file mode 100644
index 000000000..6d0f0887c
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/PageInfo.java
@@ -0,0 +1,625 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import java.util.*;
+
+/**
+ * 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;
+ 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;
+ }
+
+ 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);
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java
new file mode 100644
index 000000000..c972d06eb
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Parser.java
@@ -0,0 +1,1913 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+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 java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 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.struts2.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)
+ 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);
+
+ 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 - '"')*
+ * ('"' | )
+ * 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 == '>') {
+ buf.append(ch);
+ i += 2;
+ } else if (ch == '$') {
+ // Replace "\$" with some special char. XXX hack!
+ buf.append(Constants.HACK_CHAR);
+ 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,
+ prefix,
+ uri,
+ location,
+ err);
+ if (ctxt.getOptions().isCaching()) {
+ ctxt.getOptions().getCache().put(uri, impl);
+ }
+ }
+ 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,
+ 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 ) )
+ * |
+ *
+ * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList
+ * S? ( '/>' | ( '>' S? ETag ) )
+ * | ( 'include' IncludeDirectiveAttrList
+ * S? ( '/>' | ( '>' S? ETag ) )
+ * | ( 'attribute' AttributeDirectiveAttrList
+ * S? ( '/>' | ( '>' S? ETag ) )
+ * | ( 'variable' VariableDirectiveAttrList
+ * S? ( '/>' | ( '>' S? ETag ) )
+ * )
+ * |
+ */
+ 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
+ * |
+ * CDSect ::= CDStart CData CDEnd
+ * CDStart ::= '' 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 )
+ * |
+ */
+ 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) 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", "${");
+ if (currentChar == '"')
+ doubleQuoted = !doubleQuoted;
+ if (currentChar == '\'')
+ singleQuoted = !singleQuoted;
+ } while (currentChar != '}' || (singleQuoted || doubleQuoted));
+
+ new Node.ELExpression(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 )
+ * |
+ */
+ 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 ::= '' S? ( ' )
+ * S? ETag
+ * )
+ * | ( '>' S? Param* ETag )
+ *
+ * EmptyBody ::= '/>'
+ * | ( '>' ETag )
+ * | ( '>' S? '' Param* ' '
+ */
+ 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? ' ")) {
+ // Done
+ } else if (reader.matches(">")) {
+ if (reader.matchesETag(tag)) {
+ // Done
+ } else if (reader.matchesOptionalSpacesFollowedBy(
+ "' ETag )
+ * | ( '>' S? '' 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? ( ' )
+ * S? ETag
+ * )
+ */
+ private boolean parseJspAttributeAndBody(Node parent, String tag,
+ String bodyType)
+ throws JasperException {
+ boolean result = false;
+
+ if (reader.matchesOptionalSpacesFollowedBy(" elements:
+ parseNamedAttributes(parent);
+
+ result = true;
+ }
+
+ if (reader.matchesOptionalSpacesFollowedBy(" but something other than
+ // or the end tag, translation error.
+ err.jspError(reader.mark(), "jsp.error.jspbody.required",
+ "<" + tag);
+ }
+
+ return result;
+ }
+
+ /*
+ * Params ::= `>' S?
+ * ( ( `'
+ * ( ( S? Param+ S? ` ' )
+ * |
+ * )
+ * )
+ * | Param+
+ * )
+ * ''
+ */
+ private void parseJspParams(Node parent) throws JasperException {
+ Node jspParamsNode = new Node.ParamsAction(start, parent);
+ parseOptionalBody(jspParamsNode, "jsp:params",
+ JAVAX_BODY_CONTENT_PARAM);
+ }
+
+ /*
+ * Fallback ::= '/>'
+ * | ( `>' S? `'
+ * ( ( S?
+ * ( Char* - ( Char* ` ' ) )
+ * ` ' S?
+ * )
+ * |
+ * )
+ * `'
+ * )
+ * | ( '>'
+ * ( Char* - ( Char* '' ) )
+ * ''
+ * )
+ */
+ 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? ( ' )
+ * S? ETag
+ * )
+ * | ( '>' S? PluginTags ETag )
+ *
+ * EmptyBody ::= '/>'
+ * | ( '>' ETag )
+ * | ( '>' S? '
+ *
+ * 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 "${" 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 == '$') {
+ if (!reader.hasMoreInput()) {
+ ttext.write('$');
+ break;
+ }
+ ch = reader.nextChar();
+ if (ch == '{') {
+ reader.pushChar();
+ reader.pushChar();
+ break;
+ }
+ ttext.write('$');
+ reader.pushChar();
+ continue;
+ } else if (ch == '\\') {
+ if (!reader.hasMoreInput()) {
+ ttext.write('\\');
+ break;
+ }
+ // Look for \% or \$
+ // Only recognize \$ if isELIgnored is false, but since it can
+ // be set in a page directive, it cannot be determined yet.
+ char next = (char) reader.peekChar();
+ if (next == '%') {
+ ch = reader.nextChar();
+ } else if (next == '$') {
+ // Skip the $ and use a hack to flag this sequence
+ reader.nextChar();
+ ch = Constants.HACK_CHAR;
+ }
+ }
+ ttext.write(ch);
+ }
+ new Node.TemplateText(ttext.toString(), start, parent);
+ }
+
+ /*
+ * XMLTemplateText ::= ( S? '/>' )
+ * | ( S? '>'
+ * ( ( Char* - ( Char* ( '<' | '${' ) ) )
+ * ( '${' ELExpressionBody )?
+ * CDSect?
+ * )* ETag
+ * )
+ * |
+ */
+ 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 ");
+ 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 != '$') {
+ ttext.write('\\');
+ }
+ ttext.write(ch);
+ } else if (ch == '$') {
+ if (!reader.hasMoreInput()) {
+ ttext.write('$');
+ break;
+ }
+ ch = reader.nextChar();
+ if (ch != '{') {
+ ttext.write('$');
+ 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);
+
+ 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 )
+ * | ( ' 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(" )
+ * | ( ' )
+ * | ( '<%=' )
+ * | ( ' )
+ * | ( '<%' )
+ * | ( ' )
+ * | ( ' )
+ * | ( ' )
+ * | ( '<%=' )
+ * | ( ' )
+ * | ( '<%' )
+ * | ( ' )
+ * | ( ' )
+ * | ( '${' )
+ * | ( ' )
+ * | 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(" ")) {
+ 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(" ")) {
+ 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(" 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java
new file mode 100644
index 000000000..231b110c1
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java
@@ -0,0 +1,580 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.xmlparser.XMLEncodingDetector;
+import org.xml.sax.Attributes;
+
+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;
+
+/**
+ * Controller for the parsing of a JSP page.
+ *
+ * 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 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());
+ }
+
+ /**
+ * 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.
+ */
+ public Node.Nodes parseTagFileDirectives(String inFileName)
+ throws FileNotFoundException, JasperException, IOException {
+ boolean isTagFileSave = isTagFile;
+ boolean directiveOnlySave = directiveOnly;
+ isTagFile = true;
+ directiveOnly = true;
+ Node.Nodes page = doParse(inFileName, null,
+ (URL) ctxt.getTagFileJarUrls().get(inFileName));
+ 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;
+ 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
+ compiler.getPageInfo().addDependant(absFileName);
+ }
+
+ if (isXml && isEncodingSpecifiedInProlog) {
+ /*
+ * 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);
+ } else {
+ // Standard syntax
+ InputStreamReader inStreamReader = null;
+ try {
+ inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
+ jarFile, ctxt, err);
+ JspReader jspReader = new JspReader(ctxt, absFileName,
+ sourceEnc, inStreamReader,
+ err);
+ parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
+ directiveOnly, jarFileUrl,
+ sourceEnc, jspConfigPageEnc,
+ isDefaultPageEncoding);
+ } 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
+ * element.
+ *
+ * @param absFileName The URI to match
+ *
+ * @return The value of the 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 specified in a , 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
+ 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 (!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 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.,
+ * ) 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)) {
+ isXml = true;
+ if (revert) sourceEnc = "UTF-8";
+ return;
+ } else {
+ 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.
+ */
+ 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 %>, or .
+ */
+ 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('\\', '/');
+ 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
+ * element whose prefix is bound to the JSP namespace, as in:
+ *
+ *
+ * ...
+ *
+ *
+ * @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 {
+
+ // :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 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java
new file mode 100644
index 000000000..17ec3ef41
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java
new file mode 100644
index 000000000..420f6a4c3
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import java.io.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java
new file mode 100644
index 000000000..53e76f43b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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 default is true, this
+ * stratum is made the default stratum, overriding any previously
+ * set default.
+ *
+ * @param stratum the SmapStratum object to add
+ * @param defaultStratum if true , 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 smap 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 false , 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java
new file mode 100644
index 000000000..7cb1ad0aa
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.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.struts2.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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.
+ * LineInfo 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 InputStartLine )
+ * @param inputFileName the filepath (or name) from which the input comes
+ * (yields SMAP LineFileID ) Use unqualified names
+ * carefully, and only when they uniquely identify a file.
+ * @param inputLineCount the number of lines in the input to map
+ * (SMAP LineFileCount )
+ * @param outputStartLine starting line in the output file
+ * (SMAP OutputStartLine )
+ * @param outputLineIncrement number of output lines to map to each
+ * input line (SMAP OutputLineIncrement ). Given the
+ * fact that the name starts with "output", I continuously have
+ * the subconscious urge to call this field
+ * OutputLineExcrement .
+ */
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java
new file mode 100644
index 000000000..17d060008
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java
@@ -0,0 +1,722 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 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 Logger log = LoggerFactory.getLogger(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 Logger log = LoggerFactory.getLogger(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: "
+ + " \n"
+ + " ");
+ }
+ }
+
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java
new file mode 100644
index 000000000..a1ff8879f
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.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 = " ";
+
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java
new file mode 100644
index 000000000..25830cd5d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagFileProcessor.java
@@ -0,0 +1,609 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.runtime.JspSourceDependent;
+import org.apache.struts2.jasper.servlet.JspServletWrapper;
+
+import javax.servlet.jsp.tagext.*;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * 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("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("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);
+
+ 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";
+ }
+
+ TagAttributeInfo tagAttributeInfo =
+ new TagAttributeInfo(attrName, required, type, rtexprvalue,
+ fragment);
+ 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, 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.
+ */
+ public static TagInfo parseTagFileDirectives(ParserController pc,
+ String name,
+ String path,
+ TagLibraryInfo tagLibInfo)
+ throws JasperException {
+
+ ErrorDispatcher err = pc.getCompiler().getErrorDispatcher();
+
+ Node.Nodes page = null;
+ try {
+ page = pc.parseTagFileDirectives(path);
+ } 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 {
+
+ JspCompilationContext ctxt = compiler.getCompilationContext();
+ JspRuntimeContext rctxt = ctxt.getRuntimeContext();
+ JspServletWrapper wrapper =
+ (JspServletWrapper) rctxt.getWrapper(tagFilePath);
+
+ synchronized (rctxt) {
+ if (wrapper == null) {
+ wrapper = new JspServletWrapper(ctxt.getServletContext(),
+ ctxt.getOptions(),
+ tagFilePath,
+ tagInfo,
+ ctxt.getRuntimeContext(),
+ (URL) ctxt.getTagFileJarUrls().get(tagFilePath));
+ rctxt.addWrapper(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(),
+ (URL) ctxt.getTagFileJarUrls().get(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();
+ JspCompilationContext ctxt = compiler.getCompilationContext();
+ if (ctxt.getTagFileJarUrls().get(tagFilePath) == null) {
+ // Omit tag file dependency info on jar files for now.
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java
new file mode 100644
index 000000000..a159530fd
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagLibraryInfoImpl.java
@@ -0,0 +1,745 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.xmlparser.ParserUtils;
+import org.apache.struts2.jasper.xmlparser.TreeNode;
+
+import javax.servlet.jsp.tagext.*;
+import java.io.*;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * 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 Logger log = LoggerFactory.getLogger(TagLibraryInfoImpl.class);
+
+ private Hashtable jarEntries;
+ private JspCompilationContext ctxt;
+ 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,
+ String prefix,
+ String uriIn,
+ String[] location,
+ ErrorDispatcher err) throws JasperException {
+ super(prefix, uriIn);
+
+ this.ctxt = ctxt;
+ this.parserController = pc;
+ 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) {
+ }
+ }
+ }
+
+ }
+
+ /*
+ * @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 element
+ ParserUtils pu = new ParserUtils();
+ TreeNode tld = pu.parseXMLDocument(uri, in);
+
+ // Check to see if the root element contains a 'version'
+ // attribute, which was added in JSP 2.0 to replace the
+ // subelement
+ this.jspversion = tld.findAttribute("version");
+
+ // Process each child element of our 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) {
+ //TODO: implement for tld
+ //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 ( 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 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 element: Bugzilla 33538
+ } else if ("tag-extension".equals(tname)) {
+ // Ignore 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
+ // check if jarFileUrl is not null before adding it, or this will
+ // cause a NPE.
+ if (jarFileUrl != null) {
+ ctxt.getTagFileJarUrls().put(path, jarFileUrl);
+ }
+ } else if (!path.startsWith("/WEB-INF/tags")) {
+ err.jspError("jsp.error.tagfile.illegalPath", path);
+ }
+
+ TagInfo tagInfo
+ = TagFileProcessor.parseTagFileDirectives(parserController, name,
+ path, this);
+ return new TagFileInfo(name, path, tagInfo);
+ }
+
+ TagAttributeInfo createAttribute(TreeNode elem, String jspVersion) {
+ String name = null;
+ String type = null;
+ boolean required = false, rtexprvalue = false, reqTime = false,
+ isFragment = 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 ("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) {
+ // 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);
+ }
+
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java
new file mode 100644
index 000000000..e98059f74
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagPluginManager.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.struts2.jasper.xmlparser.ParserUtils;
+import org.apache.struts2.jasper.xmlparser.TreeNode;
+
+import javax.servlet.ServletContext;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * 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 JspUtil.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java
new file mode 100644
index 000000000..bd151d195
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TextOptimizer.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.Options;
+
+/**
+ */
+public class TextOptimizer {
+
+ /**
+ * A visitor to concatenate contiguous template texts.
+ */
+ static class TextCatVisitor extends Node.Visitor {
+
+ private Options options;
+ private 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();
+ }
+
+ 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() && 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java
new file mode 100644
index 000000000..faaea3ed8
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TldLocationsCache.java
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.io.FileUtils;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.xmlparser.ParserUtils;
+import org.apache.struts2.jasper.xmlparser.TreeNode;
+import org.xml.sax.InputSource;
+
+import javax.servlet.ServletContext;
+import java.io.File;
+import java.io.InputStream;
+import java.net.*;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * 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 elements in web.xml:
+ * the uri and location of the tag-library are specified in
+ * the 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
+ * 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 Logger log = LoggerFactory.getLogger(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 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();
+ noTldJars.add("ant.jar");
+ noTldJars.add("catalina.jar");
+ noTldJars.add("catalina-ant.jar");
+ noTldJars.add("catalina-cluster.jar");
+ noTldJars.add("catalina-optional.jar");
+ noTldJars.add("catalina-i18n-fr.jar");
+ noTldJars.add("catalina-i18n-ja.jar");
+ noTldJars.add("catalina-i18n-es.jar");
+ noTldJars.add("commons-dbcp.jar");
+ noTldJars.add("commons-modeler.jar");
+ noTldJars.add("commons-logging-api.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("commons-el.jar");
+ noTldJars.add("jakarta-regexp-1.2.jar");
+ noTldJars.add("jasper-compiler.jar");
+ noTldJars.add("jasper-runtime.jar");
+ noTldJars.add("jmx.jar");
+ noTldJars.add("jmx-tools.jar");
+ noTldJars.add("jsp-api.jar");
+ noTldJars.add("jkshm.jar");
+ noTldJars.add("jkconfig.jar");
+ noTldJars.add("naming-common.jar");
+ noTldJars.add("naming-resources.jar");
+ noTldJars.add("naming-factory.jar");
+ noTldJars.add("naming-java.jar");
+ noTldJars.add("servlet-api.jar");
+ noTldJars.add("servlets-default.jar");
+ noTldJars.add("servlets-invoker.jar");
+ noTldJars.add("servlets-common.jar");
+ noTldJars.add("servlets-webdav.jar");
+ noTldJars.add("tomcat-util.jar");
+ noTldJars.add("tomcat-http11.jar");
+ noTldJars.add("tomcat-jni.jar");
+ noTldJars.add("tomcat-jk.jar");
+ noTldJars.add("tomcat-jk2.jar");
+ noTldJars.add("tomcat-coyote.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");
+ }
+
+ 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 a list of absolute paths of the locations in the cache
+ */
+ public Set getAbsolutePathsOfLocations() {
+ Set paths = new HashSet(mappings.size());
+ for (Object value : mappings.values()) {
+ String[] location = (String[]) value;
+
+ try {
+ File file = FileUtils.toFile(new URL(location[0]));
+ paths.add(file.getAbsolutePath());
+ } catch (Exception e) {
+ //ignore
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * 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) {
+ 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 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 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
+ * 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.,
+ * /common/lib).
+ *
+ * The set of shared JARs to be scanned for TLDs is narrowed down by
+ * the noTldJars 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 jarPath 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 jarPath 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java
new file mode 100644
index 000000000..bedfd6c1d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Validator.java
@@ -0,0 +1,1539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.xml.sax.Attributes;
+
+import javax.servlet.jsp.el.FunctionMapper;
+import javax.servlet.jsp.tagext.*;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+/**
+ * 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")
+ };
+
+ 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;
+ comparePageEncodings(value, n);
+ }
+ }
+
+ // 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;
+ n.getRoot().setPageEncoding(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 void comparePageEncodings(String pageDirEnc,
+ Node.PageDirective pageDir)
+ throws JasperException {
+
+ Node.Root root = pageDir.getRoot();
+ String configEnc = root.getJspConfigPageEncoding();
+
+ /*
+ * 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 && !pageDirEnc.equals(configEnc)
+ && (!pageDirEnc.startsWith("UTF-16")
+ || !configEnc.startsWith("UTF-16"))) {
+ err.jspError(pageDir,
+ "jsp.error.config_pagedir_encoding_mismatch",
+ configEnc, pageDirEnc);
+ }
+
+ /*
+ * 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()) {
+ String pageEnc = root.getPageEncoding();
+ 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 ClassLoader loader;
+
+ 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.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")) {
+ 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("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("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("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("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("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("width", null, null,
+ n.getAttributeValue("width"),
+ java.lang.String.class, n, false);
+ n.setWidth(width);
+
+ Node.JspAttribute height
+ = getJspAttribute("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 {
+ if (!pageInfo.isELIgnored()) {
+ String expressions = "${" + new String(n.getText()) + "}";
+ ELNode.Nodes el = ELParser.parse(expressions);
+ validateFunctions(el, n);
+ JspUtil.validateExpressions(
+ n.getStart(),
+ expressions,
+ java.lang.String.class, // XXX - Should template text
+ // always evaluate to String?
+ getFunctionMapper(el),
+ err);
+ 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(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 ().
+ * 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 tagDataAttrs = new Hashtable(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(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(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, 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:
+ *
+ * , 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:
+ *
+ *
+ *
+ * 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 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();
+
+ for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+ boolean found = false;
+ 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()) {
+ 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);
+ }
+ jspAttrs[i]
+ = getJspAttribute(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 (isExpression(n, attrs.getValue(i))) {
+ err.jspError(n,
+ "jsp.error.attribute.custom.non_rt_with_expr",
+ tldAttrs[j].getName());
+ }
+ jspAttrs[i]
+ = new Node.JspAttribute(attrs.getQName(i),
+ attrs.getURI(i),
+ attrs.getLocalName(i),
+ attrs.getValue(i),
+ false,
+ null,
+ false);
+ }
+ if (isExpression(n, attrs.getValue(i))) {
+ 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(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 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, 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, true);
+ } else {
+ err.jspError(n, "jsp.error.bad_attribute",
+ na.getName(), n.getLocalName());
+ }
+ }
+ }
+ }
+
+ /**
+ * Preprocess attributes that can be expressions. Expression
+ * delimiters are stripped.
+ *
+ * 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(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(
+ qName,
+ uri,
+ localName,
+ value.substring(2, value.length() - 1),
+ true,
+ null,
+ dynamic);
+ } else if (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")) {
+ result = new Node.JspAttribute(
+ 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);
+ if (el.containsEL() && !pageInfo.isELIgnored()) {
+ validateFunctions(el, n);
+ JspUtil.validateExpressions(
+ n.getStart(),
+ value,
+ expectedType,
+ getFunctionMapper(el),
+ this.err);
+
+
+ result = new Node.JspAttribute(qName, uri, localName,
+ value, false, el,
+ dynamic);
+ } else {
+ value = value.replace(Constants.HACK_CHAR, '$');
+ result = new Node.JspAttribute(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,
+ dynamic);
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * Checks to see if the given attribute value represents a runtime or
+ * EL expression.
+ */
+ private boolean isExpression(Node n, String value) {
+ if ((n.getRoot().isXmlSyntax() && value.startsWith("%="))
+ || (!n.getRoot().isXmlSyntax() && value.startsWith("<%="))
+ || (value.indexOf("${") != -1 && !pageInfo.isELIgnored()))
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * 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))) {
+ 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 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 params = new ArrayList();
+ // Signature is of the form
+ // S ( ',' )* )? ')'
+ 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 implements FunctionMapper {
+
+ private HashMap fnmap = new java.util.HashMap();
+
+ public void mapFunction(String fnQName, Method method) {
+ fnmap.put(fnQName, method);
+ }
+
+ public Method resolveFunction(String prefix,
+ String localName) {
+ return (Method) 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("");
+ errMsg.append(Localizer.getMessage(
+ "jsp.error.tei.invalid.attributes", n.getQName()));
+ errMsg.append(" ");
+ for (int i = 0; i < errors.length; i++) {
+ errMsg.append("");
+ if (errors[i].getId() != null) {
+ errMsg.append(errors[i].getId());
+ errMsg.append(": ");
+ }
+ errMsg.append(errors[i].getMessage());
+ errMsg.append("
");
+ }
+
+ err.jspError(n, errMsg.toString());
+ }
+
+ visitBody(n);
+ }
+ }
+
+ public static void validate(Compiler compiler,
+ Node.Nodes page) throws JasperException {
+
+ /*
+ * Visit the page/tag directives first, as they are global to the page
+ * and are position independent.
+ */
+ page.visit(new DirectiveVisitor(compiler));
+
+ // 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("");
+ errMsg.append(Localizer.getMessage("jsp.error.tlv.invalid.page",
+ tli.getShortName(),
+ compiler.getPageInfo().getJspFile()));
+ errMsg.append(" ");
+ for (int i = 0; i < errors.length; i++) {
+ if (errors[i] != null) {
+ errMsg.append("");
+ errMsg.append(errors[i].getId());
+ errMsg.append(": ");
+ errMsg.append(errors[i].getMessage());
+ errMsg.append("
");
+ }
+ }
+ }
+ }
+
+ if (errMsg != null) {
+ errDisp.jspError(errMsg.toString());
+ }
+ }
+}
+
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.java
new file mode 100644
index 000000000..57ef8e717
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPlugin.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java
new file mode 100644
index 000000000..2ddd73fa9
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/tagplugin/TagPluginContext.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.compiler.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 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties
new file mode 100644
index 000000000..f8c38c5e2
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings.properties
@@ -0,0 +1,429 @@
+# 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: LocalStrings.properties 685823 2008-08-14 09:04:26Z markt $
+#
+# 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=\" \
+\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.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.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.engine.info=Jasper JSP 2.0 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
+# 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.sendErrToClient=Warning: Invalid value for the initParam sendErrToClient. 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.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 ] [-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 [--] \n\
+where jsp files is\n\
+\ -webapp A directory containing a web-app, whose JSP pages\n\
+\ will be processed recursively\n\
+or any number of\n\
+\ 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 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 of target package (default org.apache.jsp)\n\
+\ -c 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 The uri directory compilations should be relative to\n\
+\ (default "/")\n\
+\ -uriroot Same as -webapp\n\
+\ -compile Compiles generated servlets\n\
+\ -webinc Creates a partial servlet mappings in the file\n\
+\ -webxml Creates a complete web.xml in the file\n\
+\ -ieplugin Java Plugin classid for Internet Explorer\n\
+\ -classpath 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 Set the encoding charset for Java classes (default UTF-8)\n\
+\ -source Set the -source argument to the compiler (default 1.4)\n\
+\ -target Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=\n\
+\n\
+\n\
+\n\
+\n\
+\n
+jspc.webxml.footer=\n\
+ \n\
+\n
+jspc.webinc.header=\n\
+\n
+jspc.webinc.footer=\n\
+\n
+jspc.webinc.insertEnd=
+jspc.webinc.insertStart=
+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.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.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\" or \"2.0\"
+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}.
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties
new file mode 100644
index 000000000..b101db5d3
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_es.properties
@@ -0,0 +1,423 @@
+# 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: LocalStrings_es.properties 565193 2007-08-12 22:53:10Z markt $
+#
+# Default localized string information
+# Localized para Locale es_ES
+
+jsp.error.bad.servlet.engine=¡Versión incorrecta del motor servlet!
+jsp.error.no.scratch.dir=El motor JSP no tiene configurado un directorio de trabajo.\
+\n Añada \"jsp.initparams=scratchdir=\" \
+\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á 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ón {0} ya que está 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.warning.bad.type=Aviso: tipo incorrecto en archivo .class
+jsp.error.data.file.write=Error mientras escribía el archivo de datos
+jsp.error.page.invalid.buffer=Directiva Page: medida de buffer inválida
+jsp.error.page.conflict.contenttype=Directiva Page: es ilegal tener múltiples 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últiples 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últiples ocurrencias de 'buffer'con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.page.invalid.buffer=Directiva Page: valor incorrecto para buffer
+jsp.error.page.conflict.autoflush=Directiva Page: es ilegal tener múltiples 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últiples 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últiples 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últiples 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últiples ocurrencias de 'errorPage' con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.page.conflict.language=Directiva Page: es ilegal tener múltiples ocurrencias de 'language' con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.tag.conflict.language=Directiva Tag: es ilegal tener múltiples 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.defafterusar.language=Directiva Page: No puede definir language 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últiples ocurrencias de 'extends' con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.page.conflict.iselignored=Directiva Page: es ilegal tener múltiples ocurrencias de 'isELIgnored' con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.tag.conflict.iselignored=Directiva Tag: es ilegal tener múltiples ocurrencias de 'isELIgnored' con valores distintos (viejo: {0}, nuevo: {1})
+jsp.error.page.invalid.iselignored=Directiva Page: valor inválido 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últiples ocurrencias de pageencoding
+jsp.error.tag.conflict.attr=Directiva Tag: es ilegal tener múltiples ocurrencias del atributo \"{0}\" con valores distintos (viejo: {1}, nuevo: {2})
+jsp.error.tag.multi.pageencoding=La directiva Tag no debe de tener múltiples ocurrencias de pageencoding
+jsp.error.page.bad_b_and_a_combo=Directiva Page: Combinación 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álido
+jsp.error.include.exception=No se puede incluir {0}
+jsp.error.stream.closed=Stream cerrado
+jsp.error.invalid.forward=Tag forward no válido
+jsp.error.unknownException=¡Error no caturado!. Deberías de considerar el poner una página de error para avisar de los errores más elegantemente
+jsp.error.invalid.directive=Directiva no válida
+jsp.error.directive.istagfile=La Directiva {0} no puede usarse en archivo de tag
+jsp.error.directive.isnottagfile=La Directiva {0} sólo 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ón {0} no se puede usar en un archivo tag
+jsp.error.action.isnottagfile=La acción {0} sólo 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ísico
+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.atributo={0}: atributo obligatorio {1} perdido
+jsp.engine.info=Motor Jasper JSP 2.0
+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, ¿podría 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ía 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ón 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ón ha tenido lugar mientras se leía el método de lectura de la propiedad ''{0}'' en un bean del tipo ''{1}'':\n{2}
+jsp.error.beans.nomethod=No puedo encontrar un método para leer la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.nomethod.setproperty=No puedo encontrar un método para escribir la propiedad ''{0}'' en un bean del tipo ''{2}''
+jsp.error.beans.noproperty=No puedo encontrar información de la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.setproperty.noindexset=No puedo poner la propiedad indexada
+jsp.error.include.tag=Tag jsp:include no válido
+jsp.error.include.noflush=jsp:include necesita tener \"flush=true\"
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" es la única combinación válida en JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer=Error: Se ha intentado limpiar un buffer que ya había 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és del tag \"params\".
+jsp.error.param.invalidUse=La acción 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ón estándar o de cliente
+jsp.error.jspbody.invalidUse=jsp:body debe de ser el subelemento de una acción estándar 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ódigo no declarado en jsp:plugin
+jsp.error.ise_on_clear=Es ilegal usar clear() cuando el tamaño 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
+# typo ?
+#jsp.error.setproperty.invalidSayntax=setProperty: can't have non-null value when property=*
+jsp.error.setproperty.invalidSyantax=setProperty: No puede haber un valor no nulo cuando se ha especificado property=*
+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és de una valor de cadena literal
+jsp.warning.keepgen=Aviso: valor incorrecto para el initParam keepgen. Se usará el valor por defecto de \"false\"
+jsp.warning.xpoweredBy=Aviso: valor incorrecto para el initParam xpoweredBy. Se usará el valor por defecto de \"false\"
+jsp.warning.enablePooling=Aviso: valor incorrecto para el initParam enablePooling. Se usará el valor por defecto de \"true\"
+jsp.warning.invalidTagPoolSize=Aviso: valor incorrecto para el parámetro init llamado tagPoolSize. Se usará la medida por defecto de {0}
+jsp.warning.mappedFile=Aviso: valor incorrecto para el initParam mappedFile. Se usará el valor por defecto de \"false\"
+jsp.warning.sendErrToClient=Aviso: valor incorrecto para el initParam sendErrToClient. Se usará el valor por defecto de \"false\"
+jsp.warning.classDebugInfo=Aviso: valor incorrecto para el initParam classdebuginfo. Se usará el valor por defecto de \"false\"
+jsp.warning.checkInterval=Aviso: valor incorrecto para el initParam checkInterval. Se usará el valor por defecto de \"300\" segundos
+jsp.warning.development=Aviso: valor incorrecto para el initParam development. Se usará el valor por defecto de \"true\"
+jsp.warning.fork=Aviso: valor incorrecto para el initParam fork. Se usará el valor por defecto de \"true\"
+jsp.warning.reloading=Aviso: valor incorrecto para el initParam reloading. Se usará el valor por defecto de \"true\"
+jsp.warning.dumpSmap=Aviso: valor incorrecto para el initParam dumpSmap. Se usará el valor por defecto de \"false\"
+jsp.warning.genchararray=Aviso: valor incorrecto para el initParam genStrAsCharArray. Se usará el valor por defecto de \"false\"
+jsp.warning.suppressSmap=Aviso: valor incorrecto para el initParam suppressSmap. Se usará el valor por defecto de \"false\"
+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ás 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ás subelementos variable y una clase TagExtraInfo que devuelve una o más VariableInfo
+jsp.error.parse.error.in.TLD=Error de análisis 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ño de buffer <= 0
+jsp.error.file.not.found=Archivo JSP \"{0}\" no encontrado
+jsp.message.copyinguri=Copiando {0} en {1}
+jsp.message.htmlcomment=\nQuitando comentario: \t{0}
+jsp.message.handling_directive=\nResolviendo directiva: {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {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ó {0} en {1}
+jsp.message.adding_jar=Añadiendo 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álido según 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 ] [-keepgenerated]
+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étodo 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ón 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ño cero
+jsp.error.bad_string_char=No puede extraer un char desde un array de tamaño 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á 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á el compilador del Java de Sun.
+jsp.warning.compiler.path.notfound=Trayectoria del compilador especificado {0} no encontrada. Se usará el PATH del sistema.
+jsp.error.jspc.uriroot_not_dir=La opción -uriroot debe de especificar un directorio ya existente
+jsp.error.jspc.missingTarget=Falta target: Debe de especificar -webapp o -uriroot o una o más páginas 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 [--] \n\
+donde son:\n\
+\ -webapp Un directorio conteniendo una web-app. Todas las\n\
+\ páginas jsp serán compiladas recursivamente\n\
+o cualquier número de\n\
+\ Un Archivo para ser interpretado como una página jsp\n\
+y donde incluyen:\n\
+\ -help Muestra este mensaje de ayuda\n\
+\ -v Modo detallado\n\
+\ -d Directorio de salida\n\
+\ -l Muestra el nombre de la página JSP al ocurrir un fallo\n\
+\ -s Muestra el nombre de la página JSP al tener éxito\n\
+\ -p Nombre del package objetivo\n\
+\ (por defecto org.apache.jsp)\n\
+\ -c Nombre de la clase objetivo\n\
+\ (sólo se aplica a la primera página JSP)\n\
+\ -mapped Genera llamadas separadas a write() para cada línea de\n\
+\ HTML en el JSP\n\
+\ -die[#] Genera un código de retorno de error (#) en errores\n\
+\ fatales. (por defecto 1).\n\
+\ -uribase El directorio uri de donde deben de partir las\n\
+\ compilaciones. (por defecto "/")\n\
+\ -uriroot Igual que -webapp\n\
+\ -compile Compila los servlets generados\n\
+\ -webinc Crea unos mapeos parciales de servlet en el archivo\n\
+\ -webxml Crea un web.xml completo en el archivo.\n\
+\ -ieplugin Java Plugin classid para Internet Explorer\n\
+\ -classpath Pasa por alto la propiedad de sistema java.class.path\n\
+\ -xpoweredBy Añade cabecera de respuesta X-Powered-By\n\
+\ -trimSpaces Trim spaces in template text between actions, directives\n\
+\ -javaEncoding Set the encoding charset for Java classes (default UTF-8)\n\
+\ -source Set the -source argument to the compiler (default 1.4)\n\
+\ -target Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=\n\
+\n\
+\n\
+\n\
+\n\
+\n
+jspc.webxml.footer=\n\
+ \n\
+\n
+jspc.webinc.header=\n\
+\n
+jspc.webinc.footer=\n\
+\n
+jspc.webinc.insertEnd=
+jspc.webinc.insertStart=
+jspc.error.jasperException=error-el archivo ''{0}'' ha generado la excepción de sintáxis siguiente: {1}
+jspc.error.generalException=ERROR-el archivo ''{0}'' ha generado la excepción 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ágina 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ón desde TagLibraryValidator para {0} in {1}
+jsp.error.tei.invalid.attributes=Mensajes de error de validación 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ística SAX no soportada: {0}
+jsp.parser.sax.featurenotrecognized=Característica SAX no reconocida: {0}
+jsp.error.no.more.content=Alcanzado fin de contenido mietras se requería más análisis: ¿error de anidamiento de tag?
+jsp.error.parse.xml=Error de análisis XML en archivo {0}
+jsp.error.parse.xml.line=Error de análisis XML en archivo {0}: (línea {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ón no soportada: {0}
+tld.error.variableNotAllowed=Es un error para un tag, que tiene uno o más 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ón
+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ón: 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ón 5.1.9]\nDatos de Plantilla en error: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix=El prefijo taglib {0} está reservado
+jsp.error.invalid.javaEncoding=Codificaciones java incorrectas. Intenté {0} y luego {1}. Ambas fallaron.
+jsp.error.needAlternateJavaEncoding=La codificación java por defecto {0} es incorrecta en tu plataforma java. Se puede especificar una alternativa vía parámetro '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ínea: {0} en el archivo jsp: {1}
+jsp.error.multiple.line.number=\n\nHa tenido lugar un error entre las líneas: {0} y {1} en el archivo jsp: {2}\n\n
+jsp.error.corresponding.servlet=Error de servlet generado:\n
+jsp.error.empty.body.not.allowed=Cuerpo vacío 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ólo puede tener jsp:attribute en su cuerpo.
+jsp.error.no.scriptlets=Los elementos de Scripting (<%!, <jsp:declaration, <%=, <jsp:expression, <%, <jsp:scriptlet ) no están permitidos aquí.
+jsp.error.internal.unexpected_node_type=Error Interno: Encontrado tipo de nodo inesperado
+jsp.error.tld.fn.invalid.signature=Sintáxis incorrecta para firma de función en TLD. Biblioteca de Tag: {0}, Función: {1}
+jsp.error.tld.fn.duplicate.name=Nombre duplicado de función {0} en biblioteca de tag {1}
+jsp.error.tld.fn.invalid.signature.commaexpected=Sintáxis incorrecta para firma de función en TLD. Se esperaba Coma ','. Biblioteca de Tag: {0}, Función: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Sintáxis incorrecta para firma de función en TLD. Se esperaba Paréntesis '('. Biblioteca de Tag: {0}, Función: {1}.
+jsp.error.tld.mandatory.element.missing=Falta o está vacío elemento TLD obligatorio: {0}
+jsp.error.dynamic.attributes.not.implemented=El tag {0} declara que acepta atributos dinámicos 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ímbolo igual
+jsp.error.attribute.noquote=se esperaba símbolo comillas
+jsp.error.attribute.unterminated=el atributo para {0} no está terminado correctamente
+jsp.error.missing.tagInfo=El objeto TagInfo para {0} falta del TLD
+jsp.error.fragmentwithtype=No puede especificar ambos atributos 'fragment' y 'type'. Si está presente 'fragment', 'type' se pone como 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue=No puede especificar ambos atributos 'fragment' y 'rtexprvalue'. Si está 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ólo 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.jspelement.missing.name=Falta atributo obligatorio XML-style \'name\'
+jsp.error.xmlns.redefinition.notimplemented=Error interno: Intento de redefinir xmlns:{0}. La redefinición de espacios de nombre no está implementada.
+jsp.error.could.not.add.taglibraries=No pude añadir una o más bibliotecas.
+jsp.error.duplicate.name.jspattribute=El atributo {0} especificado en la acción standard o custom también 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ón estándar incorrecta
+jsp.error.xml.badStandardAction=Acción estándar 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ún 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ón estándar {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ún el TLD, el tag {0} debe de estar vacío, pero no lo está
+jsp.error.tagfile.nameNotUnique=El valor de {0} y el valor de {1} en la línea {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ínea {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 ámbito de sesión en una página que no participa en una sesión
+jsp.error.useBean.noSession=Es ilegal para useBean usar un ámbito de sesión cuando la página JSP declara (vía directiva page) que no participa en sesiones
+jsp.error.xml.encodingByteOrderUnsupported=El orden de byte dado para encoding \"{0}\" no está soportado
+jsp.error.xml.encodingDeclInvalid=Nombre de codificación \"{0}\" incorrecto.
+jsp.error.xml.encodingDeclRequired=Se necesita la declaración encoding en la declaración de texto
+jsp.error.xml.morePseudoAttributes=se esperan más pseudo-atributos
+jsp.error.xml.noMorePseudoAttributes=no se permiten más pseudo-atributos.
+jsp.error.xml.versionInfoRequired=Se requiere la versión en la declaración XML.
+jsp.error.xml.xmlDeclUnterminated=La declaración XML debe de terminar con \"?>\".
+jsp.error.xml.reservedPITarget=La instrucción de procesamiento que coincide con \"[xX][mM][lL]\" no está permitida.
+jsp.error.xml.spaceRequiredInPI=Se necesita un espacio en blanco entre la instrucción de procesamiento y los datos.
+jsp.error.xml.invalidCharInContent=Un carácter XML incorrecto (Unicode: 0x{0}) se halló 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ón XML.
+jsp.error.xml.sdDeclInvalid=El valor de declaración de documento standalone debe de ser \"yes\" o \"no\", no \"{0}\".
+jsp.error.xml.invalidCharInPI=Se halló un carácter XML incorrecto (Unicode: 0x{0}) en la instrucción de procesamiento
+jsp.error.xml.versionNotSupported=No se soporta la versión XML \"{0}\", sólo 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ón \"{0}\" no está soportada por lector {1}.
+jsp.error.xml.invalidHighSurrogate=Surrogación Alta de bits en secuencia UTF-8 no debe de exceder 0x10, pero se halló 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ón XML.
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl=Se necesita espacio en blanco antes del pseudo-atributo encoding en la declaración text.
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl=Se necesita espacio en blanco antes del pseudo-atributo version en la declaración text.
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl=Se necesita espacio en blanco antes del pseudo-atributo version en la declaración XML.
+jsp.error.xml.eqRequiredInXMLDecl=El carácter '' = '' debe de serguir a \"{0}\" en la declaración XML.
+jsp.error.xml.eqRequiredInTextDecl=El carácter '' = '' debe de serguir a \"{0}\" en la declaración text.
+jsp.error.xml.quoteRequiredInTextDecl=El valor que sigue a \"{0}\" en la declaración text debe de ser una cadena entre comillas.
+jsp.error.xml.quoteRequiredInXMLDecl=El valor que sigue a \"{0}\" en la declaración XML debe de ser un cadena entre comillas.
+jsp.error.xml.invalidCharInTextDecl=Un carácter XML incorrecto (Unicode: 0x{0}) se halló en la declaración text
+jsp.error.xml.invalidCharInXMLDecl=Un carácter XML incorrecto (Unicode: 0x{0}) se halló en la declaración XML
+jsp.error.xml.closeQuoteMissingInTextDecl=Faltan las comillas de cierre en el valor que sigue a \"{0}\" en la declaración text.
+jsp.error.xml.closeQuoteMissingInXMLDecl=Faltan las comillas de cierre en el valor que sigue a \"{0}\" en la declaración XML.
+jsp.error.multiple.jsp=No puedo tener múltiples especificaciones de
+jsp.error.jspoutput.conflict=<jsp:output>: ilegal tener ocurrencias múltiples 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áxis estándar
+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íz 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ón estándar jsp:attribute no puede estar anidada dentro de otra acción estándar jsp:attribute
+jsp.error.nested.jspbody=Una acción estándar jsp:body no puede estar anidada dentro de otra acción estándar 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úmero incorrecto de versión: \"{0}\", debe de ser \"1.2\" o \"2.0\"
+jsp.error.noFunctionPrefix=La función {0} debe de usarse con un prefijo cuando no se especifica un espacio de nombres por defecto
+jsp.error.noFunction=La función {0} no puede ser localizada mediante el prefijo especificado
+jsp.error.noFunctionMethod=El método \"{0}\" para la función \"{1}\" no se pudo hallar en la clase \"{2}\"
+jsp.error.function.classnotfound=La clase {0} especificada en el TLD para la función {1} no se puede hallar: {2}
+jsp.error.signature.classnotfound=La clase {0} especificada en la firma del método en el TLD para la función {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 ámbito en curso.
+jsp.error.nested_jsproot=<jsp:root> anidado
+jsp.error.unbalanced.endtag=El tgag final \"</{0}\" está desequilibrado
+jsp.error.invalid.bean=
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties
new file mode 100644
index 000000000..e19772363
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/resources/LocalStrings_fr.properties
@@ -0,0 +1,320 @@
+# 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: LocalStrings_fr.properties 565193 2007-08-12 22:53:10Z markt $
+#
+# 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=\" \
+\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.sendErrToClient=Attention: Valeur incorrecte pour le initParam sendErrToClient. 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 ] [-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 [--] \n\
+où les fichiers jsp sont n''importe quel nombre de:\n\
+\ Un fichier à évaluer (parser) comme page jsp\n\
+\ -webapp 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 Dossier de sortie\n\
+\ -dd 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 Nom du paquet cible\n\
+\ -c 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 Le répertoire uri de compilations relatif\n\
+\ (Par défaut "/")\n\
+\ -uriroot 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 Création d''association partielle de servlet pour l''option -webapp.\n\
+\ -webxml Création d''un fichier web.xml complet pour l''option -webapp.\n\
+\ -ieplugin Le classid du Plugin Java Plugin pour Internet Explorer\n\
+\ -sax2 Le nom de classe du Driver SAX 2.0 à utiliser\n\
+\ -trimSpaces Trim spaces in template text between actions, directives\n\
+\ -javaEncoding Set the encoding charset for Java classes (default UTF-8)\n\
+\ -source Set the -source argument to the compiler (default 1.4)\n\
+\ -target Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=\n\
+\n\
+\n\
+\n\
+\n\
+\n
+jspc.webxml.footer=\n\
+ \n\
+\n
+jspc.webinc.header=\n\
+\n
+jspc.webinc.footer=\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 ( <%!, \" \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.sendErrToClient=\u8b66\u544a: initParam sendErrToClient\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 [--] \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 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\
+\ 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 \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 \u30bf\u30fc\u30b2\u30c3\u30c8\u30d1\u30c3\u30b1\u30fc\u30b8\u306e\u540d\u524d (\u30c7\u30d5\u30a9\u30eb\u30c8\u306forg.apache.jsp)\n\
+\ -c \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 \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 -webapp\u3068\u540c\u3058\n\
+\ -compile \u751f\u6210\u3057\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30b3\u30f3\u30d1\u30a4\u30eb\n\
+\ -webinc \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 \u30d5\u30a1\u30a4\u30eb\u306b\u5b8c\u5168\u306aweb.xml\u3092\u4f5c\u6210\n\
+\ -ieplugin Internet Explorer\u306eJava Plugin\u306eclassid\n\
+\ -classpath 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 Set the encoding charset for Java classes (default UTF-8)\n\
+\ -source Set the -source argument to the compiler (default 1.4)\n\
+\ -target Set the -target argument to the compiler (default 1.4)\n\
+
+jspc.webxml.header=\n\
+\n\
+\n\
+\n\
+\n\
+\n
+jspc.webxml.footer=\n\
+ \n\
+\n
+jspc.webinc.header=\n\
+\n
+jspc.webinc.footer=\n\
+\n
+jspc.webinc.insertEnd=
+jspc.webinc.insertStart=
+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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java
new file mode 100644
index 000000000..4aa07dc9c
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/BodyContentImpl.java
@@ -0,0 +1,607 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.Constants;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * 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.struts2.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;
+
+ // See comment in setWriter()
+ private int bufferSizeSave;
+
+ /**
+ * 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.
+ *
+ * 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
+ * DiscardableBufferedWriters 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 line.separator , 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 {@link
+ * java.lang.String#valueOf(boolean)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link
+ * #write(int)} method.
+ *
+ * @param b The boolean 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 {@link
+ * #write(int)} method.
+ *
+ * @param c The char 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 {@link
+ * java.lang.String#valueOf(int)} is translated into bytes according
+ * to the platform's default character encoding, and these bytes are
+ * written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param i The int 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 {@link
+ * java.lang.String#valueOf(long)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the
+ * {@link #write(int)} method.
+ *
+ * @param l The long 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 {@link
+ * java.lang.String#valueOf(float)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the
+ * {@link #write(int)} method.
+ *
+ * @param f The float 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
+ * {@link java.lang.String#valueOf(double)} is translated into
+ * bytes according to the platform's default character encoding, and these
+ * bytes are written in exactly the manner of the {@link
+ * #write(int)} method.
+ *
+ * @param d The double 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
+ * {@link #write(int)} method.
+ *
+ * @param s The array of chars to be printed
+ * @throws NullPointerException If s is null
+ * @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 null then the string
+ * "null" 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
+ * {@link #write(int)} method.
+ *
+ * @param s The String 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 {@link
+ * java.lang.String#valueOf(Object)} method is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the
+ * {@link #write(int)} method.
+ *
+ * @param obj The Object 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
+ * line.separator, and is not necessarily a single newline
+ * character ('\n').
+ *
+ * @throws IOException
+ */
+ public void println() throws IOException {
+ newLine();
+ }
+
+ /**
+ * Print a boolean value and then terminate the line. This method behaves
+ * as though it invokes {@link #print(boolean)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link #print(char)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link #print(int)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link #print(long)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link #print(float)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link
+ * #print(double)} and then {@link #println()}.
+ *
+ * @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 {@link #print(char[])} and
+ * then {@link #println()}.
+ *
+ * @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 {@link #print(String)} and then
+ * {@link #println()}.
+ *
+ * @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 {@link #print(Object)} and then
+ * {@link #println()}.
+ *
+ * @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;
+ }
+ }
+
+ /**
+ * @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) {
+ // 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. The implementation of
+ // JspWriter.getBufferSize() returns the value of JspWriter's
+ // 'bufferSize' field, which is inherited by this class.
+ // Therefore, we simply save the current 'bufferSize' (so we can
+ // later restore it should this BodyContentImpl ever be reused by
+ // a call to PageContext.pushBody()) before setting it to 0.
+ if (bufferSize != 0) {
+ bufferSizeSave = bufferSize;
+ bufferSize = 0;
+ }
+ } else {
+ bufferSize = bufferSizeSave;
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java
new file mode 100644
index 000000000..95444dc14
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/HttpJspBase.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.compiler.Localizer;
+
+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 java.io.IOException;
+
+/**
+ * This is the super class of all JSP-generated servlets.
+ *
+ * @author Anil K. Vijendran
+ */
+public abstract class HttpJspBase
+ extends HttpServlet
+ implements HttpJspPage
+
+
+{
+
+ static {
+ if (JspFactory.getDefaultFactory() == null) {
+ JspFactoryImpl factory = new JspFactoryImpl();
+ if (System.getSecurityManager() != null) {
+ String basePackage = "org.apache.struts2.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) {
+ org.apache.commons.logging.LogFactory.getLog(HttpJspBase.class)
+ .error("Jasper JspRuntimeContext preload of class failed: " +
+ ex.getMessage(), ex);
+ }
+ }
+ JspFactory.setDefaultFactory(factory);
+ }
+ }
+
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java
new file mode 100644
index 000000000..49ca0d7d7
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspContextWrapper.java
@@ -0,0 +1,439 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.commons.el.VariableResolverImpl;
+import org.apache.struts2.jasper.compiler.Localizer;
+
+import javax.servlet.*;
+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 java.io.IOException;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * 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
+ */
+public class JspContextWrapper
+ extends PageContext implements VariableResolver {
+
+ // Invoking JSP context
+ private PageContext invokingJspCtxt;
+
+ private transient Hashtable 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 Hashtable originalNestedVars;
+
+ /**
+ * The variable resolver, for evaluating EL expressions.
+ */
+ private VariableResolverImpl variableResolver
+ = new VariableResolverImpl(this);
+
+ 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 Hashtable(16);
+ this.aliases = aliases;
+
+ if (nestedVars != null) {
+ this.originalNestedVars = new Hashtable(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 getAttributeNamesInScope(int scope) {
+ if (scope == PAGE_SCOPE) {
+ return pageAttributes.keys();
+ }
+
+ 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 {
+ include(relativeUrlPath, false); // XXX
+ }
+
+ 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 {
+ return variableResolver.resolveVariable(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;
+ }
+}
+
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java
new file mode 100644
index 000000000..8d68de868
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFactoryImpl.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.runtime;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.util.SimplePool;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspEngineInfo;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.PageContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Implementation of JspFactory.
+ *
+ * @author Anil K. Vijendran
+ */
+public class JspFactoryImpl extends JspFactory {
+
+ // Logger
+ private Logger log = LoggerFactory.getLogger(JspFactoryImpl.class);
+
+ private static final String SPEC_VERSION = "2.0";
+ private static final boolean USE_POOL =
+ Boolean.valueOf(System.getProperty("org.apache.struts2.jasper.runtime.JspFactoryImpl.USE_POOL", "true")).booleanValue();
+
+ private SimplePool pool = new SimplePool(100);
+
+ public PageContext getPageContext(Servlet servlet,
+ ServletRequest request,
+ ServletResponse response,
+ String errorPageURL,
+ boolean needsSession,
+ int bufferSize,
+ boolean autoflush) {
+ if (System.getSecurityManager() != null) {
+ 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 (System.getSecurityManager() != null) {
+ 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) {
+ pc = (PageContext) pool.get();
+ if (pc == null) {
+ pc = new PageContextImpl(this);
+ }
+ } else {
+ pc = new PageContextImpl(this);
+ }
+ 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)) {
+ pool.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;
+ }
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java
new file mode 100644
index 000000000..d354be2fa
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspFragmentHelper.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.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.
+ *
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java
new file mode 100644
index 000000000..081596277
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspRuntimeLibrary.java
@@ -0,0 +1,1032 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.compiler.Localizer;
+
+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 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;
+
+/**
+ * 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 (System.getSecurityManager() != null) {
+ 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 {
+ 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()];
+
+ 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, null);
+ } catch (Exception ex) {
+ throw new JasperException(ex);
+ }
+ return value;
+ }
+ // __end lookupReadMethodMethod
+
+ // handles 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?
+ * @throws IOException if thrown by the included servlet
+ * @throws 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspSourceDependent.java
new file mode 100644
index 000000000..f4195bf6b
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java
new file mode 100644
index 000000000..a876564ff
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/JspWriterImpl.java
@@ -0,0 +1,586 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.compiler.Localizer;
+import org.apache.struts2.jasper.security.SecurityUtil;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * 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
+ * @throws 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.
+ *
+ * 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
+ * DiscardableBufferedWriters 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 line.separator , and is not necessarily a single
+ * newline ('\n') character.
+ *
+ * @throws 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 {@link
+ * java.lang.String#valueOf(boolean)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link
+ * #write(int)} method.
+ *
+ * @param b The boolean 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 {@link
+ * #write(int)} method.
+ *
+ * @param c The char to be printed
+ */
+ public void print(char c) throws IOException {
+ write(String.valueOf(c));
+ }
+
+ /**
+ * Print an integer. The string produced by {@link
+ * java.lang.String#valueOf(int)} is translated into bytes according
+ * to the platform's default character encoding, and these bytes are
+ * written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param i The int to be printed
+ */
+ public void print(int i) throws IOException {
+ write(String.valueOf(i));
+ }
+
+ /**
+ * Print a long integer. The string produced by {@link
+ * java.lang.String#valueOf(long)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param l The long to be printed
+ */
+ public void print(long l) throws IOException {
+ write(String.valueOf(l));
+ }
+
+ /**
+ * Print a floating-point number. The string produced by {@link
+ * java.lang.String#valueOf(float)} is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param f The float 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
+ * {@link java.lang.String#valueOf(double)} is translated into
+ * bytes according to the platform's default character encoding, and these
+ * bytes are written in exactly the manner of the {@link
+ * #write(int)} method.
+ *
+ * @param d The double 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 {@link #write(int)}
+ * method.
+ *
+ * @param s The array of chars to be printed
+ * @throws NullPointerException If s is null
+ */
+ public void print(char s[]) throws IOException {
+ write(s);
+ }
+
+ /**
+ * Print a string. If the argument is null then the string
+ * "null" 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
+ * {@link #write(int)} method.
+ *
+ * @param s The String 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 {@link
+ * java.lang.String#valueOf(Object)} method is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param obj The Object 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
+ * line.separator, and is not necessarily a single newline
+ * character ('\n').
+ *
+ * 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 {@link #print(boolean)} and then
+ * {@link #println()}.
+ */
+ 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 {@link #print(char)} and then {@link
+ * #println()}.
+ */
+ 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 {@link #print(int)} and then {@link
+ * #println()}.
+ */
+ 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 {@link #print(long)} and then
+ * {@link #println()}.
+ */
+ 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 {@link #print(float)} and then
+ * {@link #println()}.
+ */
+ 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 {@link
+ * #print(double)} and then {@link #println()}.
+ */
+ 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 {@link #print(char[])} and then
+ * {@link #println()}.
+ */
+ 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 {@link #print(String)} and then
+ * {@link #println()}.
+ */
+ 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 {@link #print(Object)} and then
+ * {@link #println()}.
+ */
+ public void println(Object x) throws IOException {
+ print(x);
+ println();
+ }
+
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java
new file mode 100644
index 000000000..abaeb3593
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PageContextImpl.java
@@ -0,0 +1,944 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.commons.el.ExpressionEvaluatorImpl;
+import org.apache.commons.el.VariableResolverImpl;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.compiler.Localizer;
+import org.apache.struts2.jasper.security.SecurityUtil;
+
+import javax.servlet.*;
+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 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.Hashtable;
+
+/**
+ * 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
+ */
+public class PageContextImpl extends PageContext implements VariableResolver {
+
+ // The expression evaluator, for evaluating EL expressions.
+ private static ExpressionEvaluatorImpl elExprEval
+ = new ExpressionEvaluatorImpl(false);
+
+ // The variable resolver, for evaluating EL expressions.
+ private VariableResolverImpl variableResolver;
+
+ private BodyContentImpl[] outs;
+ private int depth;
+
+ // per-servlet state
+ private Servlet servlet;
+ private ServletConfig config;
+ private ServletContext context;
+ private String errorPageURL;
+
+ // page-scope attributes
+ private transient Hashtable attributes;
+
+ // per-request state
+ private transient ServletRequest request;
+ private transient ServletResponse response;
+ private transient HttpSession session;
+ private boolean isIncluded;
+
+ // initial output stream
+ private transient JspWriter out;
+ private transient JspWriterImpl baseOut;
+
+ /*
+ * Constructor.
+ */
+ PageContextImpl(JspFactory factory) {
+ this.variableResolver = new VariableResolverImpl(this);
+ this.outs = new BodyContentImpl[0];
+ this.attributes = new Hashtable(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;
+
+
+ // 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) {
+ context.log(
+ "PageContextImpl.release(): Internal error flushing buffer");
+ }
+
+ servlet = null;
+ config = null;
+ context = 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 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 attributes.keys();
+
+ 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.
+ *
+ * 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 this;
+ }
+
+ 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);
+ request.setAttribute(Constants.FORWARD_SEEN, "true");
+ }
+ }
+
+ 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 elExprEval;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * VariableResolver interface
+ */
+ public Object resolveVariable(String pName) throws ELException {
+ return variableResolver.resolveVariable(pName);
+ }
+
+ /**
+ * XML-escape the given string.
+ *
+ * @param s The string to escape
+ * @return The escape string
+ */
+ 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;
+ if (SecurityUtil.isPackageProtectionEnabled()) {
+ try {
+ retValue = AccessController.doPrivileged(
+ new PrivilegedExceptionAction() {
+
+ public Object run() throws Exception {
+ return elExprEval.evaluate(expression,
+ expectedType,
+ pageContext.getVariableResolver(),
+ functionMap);
+ }
+ });
+ } catch (PrivilegedActionException ex) {
+ Exception realEx = ex.getException();
+ if (realEx instanceof ELException) {
+ throw (ELException) realEx;
+ } else {
+ throw new ELException(realEx);
+ }
+ }
+ } else {
+ retValue = elExprEval.evaluate(expression,
+ expectedType,
+ pageContext.getVariableResolver(),
+ functionMap);
+ }
+ if (escape) {
+ retValue = XmlEscape(retValue.toString());
+ }
+
+ return retValue;
+ }
+
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java
new file mode 100644
index 000000000..febb3535b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/PerThreadTagHandlerPool.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.Constants;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java
new file mode 100644
index 000000000..b494623a2
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ProtectedFunctionMapper.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.security.SecurityUtil;
+
+import javax.servlet.jsp.el.FunctionMapper;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+
+/**
+ * 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 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.java
new file mode 100644
index 000000000..86da34305
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/ServletResponseWrapperInclude.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.struts2.jasper.runtime;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java
new file mode 100644
index 000000000..782a78910
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/runtime/TagHandlerPool.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.runtime;
+
+import org.apache.struts2.jasper.Constants;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+
+/**
+ * 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";
+
+ // index of next available tag handler
+ private int current;
+
+ 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;
+ }
+
+ /**
+ * 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 {
+ 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) {
+ 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();
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java
new file mode 100644
index 000000000..5969b6eeb
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityClassLoad.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.security;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * 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 Logger log = LoggerFactory.getLogger(SecurityClassLoad.class);
+
+ public static void securityClassLoad(ClassLoader loader) {
+
+ if (System.getSecurityManager() == null) {
+ return;
+ }
+
+ String basePackage = "org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java
new file mode 100644
index 000000000..dc4e2930e
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/security/SecurityUtil.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.jasper.security;
+
+/**
+ * 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 SecurityManager only if Security is enabled AND
+ * package protection mechanism is enabled.
+ */
+ public static boolean isPackageProtectionEnabled() {
+ if (packageDefinitionEnabled && System.getSecurityManager() != null) {
+ return true;
+ }
+ return false;
+ }
+
+
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.java
new file mode 100644
index 000000000..19806b4d0
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JasperLoader.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.struts2.jasper.servlet;
+
+import org.apache.struts2.jasper.Constants;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+
+/**
+ * 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 ClassLoader parent;
+ private SecurityManager securityManager;
+
+ public JasperLoader(URL[] urls, ClassLoader parent,
+ PermissionCollection permissionCollection,
+ CodeSource codeSource) {
+ super(urls, parent);
+ this.permissionCollection = permissionCollection;
+ this.parent = parent;
+ this.securityManager = System.getSecurityManager();
+ }
+
+ /**
+ * Load the class with the specified name. This method searches for
+ * classes in the same manner as loadClass(String, boolean)
+ * with false as the second argument.
+ *
+ * @param name Name of the class to be loaded
+ * @throws 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 ClassNotFoundException.
+ *
+ * Call findLoadedClass(String) to check if the
+ * class has already been loaded. If it has, the same
+ * Class object is returned.
+ * If the delegate property is set to true,
+ * call the loadClass() method of the parent class
+ * loader, if any.
+ * Call findClass() to find this class in our locally
+ * defined repositories.
+ * Call the loadClass() method of our parent
+ * class loader, if any.
+ *
+ * If the class was found using the above steps, and the
+ * resolve flag is true, this method will then
+ * call resolveClass(Class) on the resulting Class object.
+ *
+ * @param name Name of the class to be loaded
+ * @param resolve If true then resolve the class
+ * @throws 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.struts2.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;
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java
new file mode 100644
index 000000000..d7b855223
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspCServletContext.java
@@ -0,0 +1,405 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.servlet;
+
+
+import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
+import com.opensymphony.xwork2.ActionContext;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+import org.apache.struts2.ServletActionContext;
+
+
+/**
+ * Simple ServletContext 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;
+
+ private ClassLoaderInterface classLoaderInterface;
+ private static final String WEB_XML = "/WEB-INF/web.xml";
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Create a new instance of this ServletContext implementation.
+ *
+ * @param aLogWriter PrintWriter which is used for log() calls
+ */
+ public JspCServletContext(PrintWriter aLogWriter, ClassLoaderInterface classLoaderInterface) {
+
+ myAttributes = new Hashtable();
+ myLogWriter = aLogWriter;
+ this.classLoaderInterface = classLoaderInterface;
+ }
+
+
+ // --------------------------------------------------------- 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 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) {
+ 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
+ * @throws MalformedURLException if the resource path is
+ * not properly formed
+ */
+ public URL getResource(String path) throws MalformedURLException {
+ if (WEB_XML.equals(path)) {
+ if (ActionContext.getContext() != null) {
+ ServletContext context = ServletActionContext.getServletContext();
+ return context.getResource(path);
+ }
+
+ return null;
+ }
+ return classLoaderInterface.getResource(path);
+ }
+
+
+ /**
+ * 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 classLoaderInterface.getResourceAsStream(path);
+ } 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java
new file mode 100644
index 000000000..dddec77c4
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServlet.java
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.servlet;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.EmbeddedServletOptions;
+import org.apache.struts2.jasper.Options;
+import org.apache.struts2.jasper.compiler.JspRuntimeContext;
+import org.apache.struts2.jasper.compiler.Localizer;
+
+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 java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.Enumeration;
+
+/**
+ * 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 {
+
+ // Logger
+ private Logger log = LoggerFactory.getLogger(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.
+ *
+ * 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.
+ *
+ * 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();
+ }
+
+
+ /**
+ *
Look for a precompilation request as described in
+ * Section 8.4.2 of the JSP 1.2 Specification. WARNING -
+ * we cannot use request.getParameter() for this, because
+ * that will trigger parsing all of the request parameters, and not give
+ * a servlet the opportunity to call
+ * request.setCharacterEncoding() first.
+ *
+ * @param request The servlet requset we are processing
+ * @throws ServletException if an invalid parameter value for the
+ * jsp_precompile 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 in 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();
+ }
+
+
+ // -------------------------------------------------------- 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
+ throw new ServletException(Localizer.getMessage(
+ "jsp.error.file.not.found", jspUri));
+ } 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java
new file mode 100644
index 000000000..1a62e6f9c
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/JspServletWrapper.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.servlet;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.Options;
+import org.apache.struts2.jasper.compiler.ErrorDispatcher;
+import org.apache.struts2.jasper.compiler.JavacErrorDetail;
+import org.apache.struts2.jasper.compiler.JspRuntimeContext;
+import org.apache.struts2.jasper.compiler.Localizer;
+import org.apache.struts2.jasper.runtime.JspSourceDependent;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.tagext.TagInfo;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * 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 {
+
+ 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.
+ */
+ 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, null);
+ }
+
+ /*
+ * 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();
+ } catch (IllegalAccessException ex1) {
+ throw new JasperException(ex1);
+ } catch (InstantiationException ex) {
+ throw new JasperException(ex);
+ }
+
+ 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;
+ }
+
+ /*
+ * (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();
+ }
+ }
+
+ /**
+ * @return Returns the lastModificationTest.
+ */
+ public long getLastModificationTest() {
+ return lastModificationTest;
+ }
+
+ /**
+ * @param lastModificationTest The lastModificationTest to set.
+ */
+ public void setLastModificationTest(long lastModificationTest) {
+ this.lastModificationTest = lastModificationTest;
+ }
+
+ /**
+ * 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.
+ *
+ *
+ * @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,
+ this.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);
+ }
+
+ return new JasperException("Exception in JSP: " +
+ detail.getJspFileName() + ":" + jspLineNumber + "\n\n" +
+ detail.getJspExtract() + "\n\nStacktrace:", 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml
new file mode 100644
index 000000000..bd83ecb38
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/servlet/mbeans-descriptors.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java
new file mode 100644
index 000000000..70371b862
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/Util.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.tagplugins.jstl;
+
+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;
+import java.io.*;
+import java.util.Locale;
+
+/**
+ * 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 true if our current URL is absolute,
+ * false 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 url . 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(";jsessionid=")) != -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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Catch.java
new file mode 100644
index 000000000..493214348
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.java
new file mode 100644
index 000000000..223afa64f
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Choose.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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+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, and .
+
+ ctxt.generateBody();
+ // See comments in When.java for the reason "}" is generated here.
+ ctxt.generateJavaSource("}");
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java
new file mode 100644
index 000000000..613dfc75b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForEach.java
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/ForTokens.java
new file mode 100644
index 000000000..ced592058
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java
new file mode 100644
index 000000000..e84ee6b0d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/If.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java
new file mode 100644
index 000000000..ce5492a58
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Import.java
@@ -0,0 +1,381 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.struts2.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.struts2.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.struts2.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+ ctxt.generateJavaSource(" if(" + charSetName + " == null) "
+ + charSetName + " = org.apache.struts2.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+ ctxt.generateJavaSource(" }else{");
+ ctxt.generateJavaSource(" " + charSetName + " = org.apache.struts2.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.struts2.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.struts2.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+ ctxt.generateJavaSource(" if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.struts2.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+
+ //initialize a ImportResponseWrapper to include the resource
+ ctxt.generateJavaSource(" org.apache.struts2.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.struts2.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+ ctxt.generateJavaSource(" if(" + charSetName + " == null){");
+ ctxt.generateJavaSource(" " + charSetName + " = org.apache.struts2.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.struts2.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.struts2.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+ ctxt.generateJavaSource(" if(" + charSetName + " == null) "
+ + charSetName + " = org.apache.struts2.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+ ctxt.generateJavaSource(" }else{");
+ ctxt.generateJavaSource(" " + charSetName + " = org.apache.struts2.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.struts2.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.struts2.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+ ctxt.generateJavaSource(" if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.struts2.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+
+ //initialize a ImportResponseWrapper to include the resource
+ ctxt.generateJavaSource(" org.apache.struts2.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.struts2.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+ ctxt.generateJavaSource(" if(" + charSetName + " == null){");
+ ctxt.generateJavaSource(" " + charSetName + " = org.apache.struts2.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java
new file mode 100644
index 000000000..acf133e38
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Otherwise.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Out.java
new file mode 100644
index 000000000..4fe4429e1
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.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.struts2.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Param.java
new file mode 100644
index 000000000..a7abc08b3
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Redirect.java
new file mode 100644
index 000000000..24084c416
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.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.struts2.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Remove.java
new file mode 100644
index 000000000..f0cdec122
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Set.java
new file mode 100644
index 000000000..2fe756be4
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/Url.java
new file mode 100644
index 000000000..5bc5efa40
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.struts2.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.struts2.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.struts2.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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/core/When.java
new file mode 100644
index 000000000..da6042543
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.jasper.tagplugins.jstl.core;
+
+import org.apache.struts2.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.struts2.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class When implements TagPlugin {
+
+ public void doTag(TagPluginContext ctxt) {
+ // Get the parent context to determine if this is the first
+ 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 's. Instead we delay
+ // generating it until the next or or
+ //
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml
new file mode 100644
index 000000000..8729c47c2
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/tagplugins/jstl/tagPlugins.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ org.apache.taglibs.standard.tag.rt.core.IfTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.If
+
+
+ org.apache.taglibs.standard.tag.common.core.ChooseTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Choose
+
+
+ org.apache.taglibs.standard.tag.rt.core.WhenTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.When
+
+
+ org.apache.taglibs.standard.tag.common.core.OtherwiseTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Otherwise
+
+
+ org.apache.taglibs.standard.tag.rt.core.ForEachTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.ForEach
+
+
+ org.apache.taglibs.standard.tag.rt.core.OutTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Out
+
+
+ org.apache.taglibs.standard.tag.rt.core.SetTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Set
+
+
+ org.apache.taglibs.standard.tag.common.core.RemoveTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Remove
+
+
+ org.apache.taglibs.standard.tag.common.core.CatchTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Catch
+
+
+ org.apache.taglibs.standard.tag.rt.core.ForTokensTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.ForTokens
+
+
+ org.apache.taglibs.standard.tag.rt.core.ImportTag
+ org.apache.struts2.jasper.tagplugins.jstl.core.Import
+
+
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/FastDateFormat.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/FastDateFormat.java
new file mode 100644
index 000000000..73984709d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/FastDateFormat.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.util;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Fast date formatter that caches recently formatted date information
+ * and uses it to avoid too-frequent calls to the underlying
+ * formatter. Note: breaks fieldPosition param of format(Date,
+ * StringBuffer, FieldPosition). If you care about the field
+ * position, call the underlying DateFormat directly.
+ *
+ * @author Stan Bailes
+ * @author Alex Chaffee
+ */
+public class FastDateFormat extends DateFormat {
+
+ private DateFormat df;
+ private long lastSec = -1;
+ private StringBuffer sb = new StringBuffer();
+ private FieldPosition fp = new FieldPosition(DateFormat.MILLISECOND_FIELD);
+
+ public FastDateFormat(DateFormat df) {
+ this.df = df;
+ }
+
+ public Date parse(String text, ParsePosition pos) {
+ return df.parse(text, pos);
+ }
+
+ /**
+ * Note: breaks functionality of fieldPosition param. Also:
+ * there's a bug in SimpleDateFormat with "S" and "SS", use "SSS"
+ * instead if you want a msec field.
+ */
+ public StringBuffer format(Date date, StringBuffer toAppendTo,
+ FieldPosition fieldPosition) {
+ long dt = date.getTime();
+ long ds = dt / 1000;
+ if (ds != lastSec) {
+ sb.setLength(0);
+ df.format(date, sb, fp);
+ lastSec = ds;
+ } else {
+ // munge current msec into existing string
+ int ms = (int) (dt % 1000);
+ int pos = fp.getEndIndex();
+ int begin = fp.getBeginIndex();
+ if (pos > 0) {
+ if (pos > begin)
+ sb.setCharAt(--pos, Character.forDigit(ms % 10, 10));
+ ms /= 10;
+ if (pos > begin)
+ sb.setCharAt(--pos, Character.forDigit(ms % 10, 10));
+ ms /= 10;
+ if (pos > begin)
+ sb.setCharAt(--pos, Character.forDigit(ms % 10, 10));
+ }
+ }
+ toAppendTo.append(sb.toString());
+ return toAppendTo;
+ }
+
+ public static void main(String[] args) {
+ String format = "yyyy-MM-dd HH:mm:ss.SSS";
+ if (args.length > 0)
+ format = args[0];
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ FastDateFormat fdf = new FastDateFormat(sdf);
+ Date d = new Date();
+
+ d.setTime(1);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(20);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(500);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(543);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(999);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(1050);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(2543);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(12345);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+ d.setTime(12340);
+ System.out.println(fdf.format(d) + "\t" + sdf.format(d));
+
+ final int reps = 100000;
+ {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < reps; i++) {
+ d.setTime(System.currentTimeMillis());
+ fdf.format(d);
+ }
+ long elap = System.currentTimeMillis() - start;
+ System.out.println("fast: " + elap + " elapsed");
+ System.out.println(fdf.format(d));
+ }
+ {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < reps; i++) {
+ d.setTime(System.currentTimeMillis());
+ sdf.format(d);
+ }
+ long elap = System.currentTimeMillis() - start;
+ System.out.println("slow: " + elap + " elapsed");
+ System.out.println(sdf.format(d));
+ }
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Queue.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Queue.java
new file mode 100644
index 000000000..9c4059740
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/Queue.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.util;
+
+import java.util.Vector;
+
+/**
+ * A simple FIFO queue class which causes the calling thread to wait
+ * if the queue is empty and notifies threads that are waiting when it
+ * is not empty.
+ *
+ * @author Anil V (akv@eng.sun.com)
+ */
+public class Queue {
+ private Vector vector = new Vector();
+
+ /**
+ * Put the object into the queue.
+ *
+ * @param object the object to be appended to the
+ * queue.
+ */
+ public synchronized void put(Object object) {
+ vector.addElement(object);
+ notify();
+ }
+
+ /**
+ * Pull the first object out of the queue. Wait if the queue is
+ * empty.
+ */
+ public synchronized Object pull() {
+ while (isEmpty())
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ }
+ return get();
+ }
+
+ /**
+ * Get the first object out of the queue. Return null if the queue
+ * is empty.
+ */
+ public synchronized Object get() {
+ Object object = peek();
+ if (object != null)
+ vector.removeElementAt(0);
+ return object;
+ }
+
+ /**
+ * Peek to see if something is available.
+ */
+ public Object peek() {
+ if (isEmpty())
+ return null;
+ return vector.elementAt(0);
+ }
+
+ /**
+ * Is the queue empty?
+ */
+ public boolean isEmpty() {
+ return vector.isEmpty();
+ }
+
+ /**
+ * How many elements are there in this queue?
+ */
+ public int size() {
+ return vector.size();
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SimplePool.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SimplePool.java
new file mode 100644
index 000000000..30502878e
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SimplePool.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.util;
+
+/**
+ * Simple object pool. Based on ThreadPool and few other classes
+ *
+ * The pool will ignore overflow and return null if empty.
+ *
+ * @author Gal Shachor
+ * @author Costin
+ */
+public final class SimplePool {
+
+ private static final int DEFAULT_SIZE = 16;
+
+ /*
+ * Where the threads are held.
+ */
+ private Object pool[];
+
+ private int max;
+ private int current = -1;
+
+ private Object lock;
+
+ public SimplePool() {
+ this.max = DEFAULT_SIZE;
+ this.pool = new Object[max];
+ this.lock = new Object();
+ }
+
+ public SimplePool(int max) {
+ this.max = max;
+ this.pool = new Object[max];
+ this.lock = new Object();
+ }
+
+ /**
+ * Adds the given object to the pool, and does nothing if the pool is full
+ */
+ public void put(Object o) {
+ synchronized (lock) {
+ if (current < (max - 1)) {
+ current += 1;
+ pool[current] = o;
+ }
+ }
+ }
+
+ /**
+ * Get an object from the pool, null if the pool is empty.
+ */
+ public Object get() {
+ Object item = null;
+ synchronized (lock) {
+ if (current >= 0) {
+ item = pool[current];
+ current -= 1;
+ }
+ }
+ return item;
+ }
+
+ /**
+ * Return the size of the pool
+ */
+ public int getMax() {
+ return max;
+ }
+}
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SystemLogHandler.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SystemLogHandler.java
new file mode 100644
index 000000000..d84846df3
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/util/SystemLogHandler.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+
+/**
+ * This helper class may be used to do sophisticated redirection of
+ * System.out and System.err.
+ *
+ * @author Remy Maucherat
+ */
+public 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java
new file mode 100644
index 000000000..3d2597f5f
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ASCIIReader.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import org.apache.struts2.jasper.compiler.Localizer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * 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: ASCIIReader.java 466606 2006-10-21 23:07:12Z markt $
+ */
+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.
+ *
+ * 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
+ * (0x00-0x7f ), or -1 if the end of the stream has
+ * been reached
+ * @throws 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
+ * @throws 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 = fBuffer[i];
+ 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
+ * @throws 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.
+ * @throws 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.
+ * @throws 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().
+ *
+ * @throws 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.
+ *
+ * @throws IOException If an I/O error occurs
+ */
+ public void close() throws IOException {
+ fInputStream.close();
+ } // close()
+
+} // class ASCIIReader
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java
new file mode 100644
index 000000000..6a68f5267
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/EncodingMap.java
@@ -0,0 +1,1026 @@
+/*
+ * 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
+ * .
+ */
+
+package org.apache.struts2.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 must
+ * be the IANA encoding names specified or one of the aliases for those names
+ * which IANA defines.
+ *
+ *
+ *
+ *
+ * Common Name
+ *
+ *
+ * Use this name in XML files
+ *
+ *
+ * Name Type
+ *
+ *
+ * Xerces converts to this Java Encoder Name
+ *
+ *
+ *
+ * 8 bit Unicode
+ *
+ * UTF-8
+ *
+ *
+ * IANA
+ *
+ *
+ * UTF8
+ *
+ *
+ *
+ * ISO Latin 1
+ *
+ * ISO-8859-1
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-1
+ *
+ *
+ *
+ * ISO Latin 2
+ *
+ * ISO-8859-2
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-2
+ *
+ *
+ *
+ * ISO Latin 3
+ *
+ * ISO-8859-3
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-3
+ *
+ *
+ *
+ * ISO Latin 4
+ *
+ * ISO-8859-4
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-4
+ *
+ *
+ *
+ * ISO Latin Cyrillic
+ *
+ * ISO-8859-5
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-5
+ *
+ *
+ *
+ * ISO Latin Arabic
+ *
+ * ISO-8859-6
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-6
+ *
+ *
+ *
+ * ISO Latin Greek
+ *
+ * ISO-8859-7
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-7
+ *
+ *
+ *
+ * ISO Latin Hebrew
+ *
+ * ISO-8859-8
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-8
+ *
+ *
+ *
+ * ISO Latin 5
+ *
+ * ISO-8859-9
+ *
+ *
+ * MIME
+ *
+ *
+ * ISO-8859-9
+ *
+ *
+ *
+ * EBCDIC: US
+ *
+ * ebcdic-cp-us
+ *
+ *
+ * IANA
+ *
+ *
+ * cp037
+ *
+ *
+ *
+ * EBCDIC: Canada
+ *
+ * ebcdic-cp-ca
+ *
+ *
+ * IANA
+ *
+ *
+ * cp037
+ *
+ *
+ *
+ * EBCDIC: Netherlands
+ *
+ * ebcdic-cp-nl
+ *
+ *
+ * IANA
+ *
+ *
+ * cp037
+ *
+ *
+ *
+ * EBCDIC: Denmark
+ *
+ * ebcdic-cp-dk
+ *
+ *
+ * IANA
+ *
+ *
+ * cp277
+ *
+ *
+ *
+ * EBCDIC: Norway
+ *
+ * ebcdic-cp-no
+ *
+ *
+ * IANA
+ *
+ *
+ * cp277
+ *
+ *
+ *
+ * EBCDIC: Finland
+ *
+ * ebcdic-cp-fi
+ *
+ *
+ * IANA
+ *
+ *
+ * cp278
+ *
+ *
+ *
+ * EBCDIC: Sweden
+ *
+ * ebcdic-cp-se
+ *
+ *
+ * IANA
+ *
+ *
+ * cp278
+ *
+ *
+ *
+ * EBCDIC: Italy
+ *
+ * ebcdic-cp-it
+ *
+ *
+ * IANA
+ *
+ *
+ * cp280
+ *
+ *
+ *
+ * EBCDIC: Spain, Latin America
+ *
+ * ebcdic-cp-es
+ *
+ *
+ * IANA
+ *
+ *
+ * cp284
+ *
+ *
+ *
+ * EBCDIC: Great Britain
+ *
+ * ebcdic-cp-gb
+ *
+ *
+ * IANA
+ *
+ *
+ * cp285
+ *
+ *
+ *
+ * EBCDIC: France
+ *
+ * ebcdic-cp-fr
+ *
+ *
+ * IANA
+ *
+ *
+ * cp297
+ *
+ *
+ *
+ * EBCDIC: Arabic
+ *
+ * ebcdic-cp-ar1
+ *
+ *
+ * IANA
+ *
+ *
+ * cp420
+ *
+ *
+ *
+ * EBCDIC: Hebrew
+ *
+ * ebcdic-cp-he
+ *
+ *
+ * IANA
+ *
+ *
+ * cp424
+ *
+ *
+ *
+ * EBCDIC: Switzerland
+ *
+ * ebcdic-cp-ch
+ *
+ *
+ * IANA
+ *
+ *
+ * cp500
+ *
+ *
+ *
+ * EBCDIC: Roece
+ *
+ * ebcdic-cp-roece
+ *
+ *
+ * IANA
+ *
+ *
+ * cp870
+ *
+ *
+ *
+ * EBCDIC: Yugoslavia
+ *
+ * ebcdic-cp-yu
+ *
+ *
+ * IANA
+ *
+ *
+ * cp870
+ *
+ *
+ *
+ * EBCDIC: Iceland
+ *
+ * ebcdic-cp-is
+ *
+ *
+ * IANA
+ *
+ *
+ * cp871
+ *
+ *
+ *
+ * EBCDIC: Urdu
+ *
+ * ebcdic-cp-ar2
+ *
+ *
+ * IANA
+ *
+ *
+ * cp918
+ *
+ *
+ *
+ * Chinese for PRC, mixed 1/2 byte
+ *
+ * gb2312
+ *
+ *
+ * MIME
+ *
+ *
+ * GB2312
+ *
+ *
+ *
+ * Extended Unix Code, packed for Japanese
+ *
+ * euc-jp
+ *
+ *
+ * MIME
+ *
+ *
+ * eucjis
+ *
+ *
+ *
+ * Japanese: iso-2022-jp
+ *
+ * iso-2020-jp
+ *
+ *
+ * MIME
+ *
+ *
+ * JIS
+ *
+ *
+ *
+ * Japanese: Shift JIS
+ *
+ * Shift_JIS
+ *
+ *
+ * MIME
+ *
+ *
+ * SJIS
+ *
+ *
+ *
+ * Chinese: Big5
+ *
+ * Big5
+ *
+ *
+ * MIME
+ *
+ *
+ * Big5
+ *
+ *
+ *
+ * Extended Unix Code, packed for Korean
+ *
+ * euc-kr
+ *
+ *
+ * MIME
+ *
+ *
+ * iso2022kr
+ *
+ *
+ *
+ * Cyrillic
+ *
+ * koi8-r
+ *
+ *
+ * MIME
+ *
+ *
+ * koi8-r
+ *
+ *
+ *
+ *
+ * @author TAMURA Kent, IBM
+ * @author Andy Clark, IBM
+ * @version $Id: EncodingMap.java 466606 2006-10-21 23:07:12Z markt $
+ */
+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");
+
+ } // ()
+
+ //
+ // 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java
new file mode 100644
index 000000000..e60e81480
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/ParserUtils.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.compiler.Localizer;
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * 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: 609409 $ $Date: 2008-01-06 14:31:23 -0700 (Sun, 06 Jan 2008) $
+ */
+
+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 TreeNode
+ * 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
+ * @throws JasperException if an input/output error occurs
+ * @throws 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 TreeNode
+ * 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
+ * @throws JasperException if an input/output error occurs
+ * @throws 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;
+ }
+ }
+ com.opensymphony.xwork2.util.logging.Logger log = LoggerFactory.getLogger(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 {
+ com.opensymphony.xwork2.util.logging.Logger log = LoggerFactory.getLogger(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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java
new file mode 100644
index 000000000..920d9f5ba
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/SymbolTable.java
@@ -0,0 +1,318 @@
+/*
+ * 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
+ * .
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+/**
+ * This class is a symbol table implementation that guarantees that
+ * strings used as identifiers are unique references. Multiple calls
+ * to addSymbol will always return the same string
+ * reference.
+ *
+ * The symbol table performs the same task as String.intern()
+ * with the following differences:
+ *
+ *
+ * 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.
+ *
+ *
+ * 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 mostly unique. Strings with similar leading
+ * characters are especially prone to this poor hashing behavior.
+ *
+ *
+ *
+ * @author Andy Clark
+ * @version $Id: SymbolTable.java 466606 2006-10-21 23:07:12Z markt $
+ */
+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 hash(char[],int,int) 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 hash(String) 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/TreeNode.java
new file mode 100644
index 000000000..6e82536a2
--- /dev/null
+++ b/plugins/embeddedjsp/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.struts2.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 org.w3c.dom need
+ * not be visible to the remainder of Jasper.
+ *
+ * WARNING - Construction of a new tree, or modifications
+ * to an existing one, are not thread-safe and such accesses must be
+ * synchronized.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision: 466606 $ $Date: 2006-10-21 17:07:12 -0600 (Sat, 21 Oct 2006) $
+ */
+
+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
+ * null 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 null.
+ *
+ * @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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java
new file mode 100644
index 000000000..058efbdd0
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UCSReader.java
@@ -0,0 +1,300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+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: UCSReader.java 466606 2006-10-21 23:07:12Z markt $
+ */
+public class UCSReader extends Reader {
+
+ private Logger log = LoggerFactory.getLogger(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);
+ } // (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;
+ } // (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.
+ *
+ * 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
+ * (0x00-0x7f ), or -1 if the end of the stream has
+ * been reached
+ * @throws 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
+ * @throws 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
+ * @throws 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.
+ * @throws 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.
+ * @throws 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().
+ *
+ * @throws 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.
+ *
+ * @throws IOException If an I/O error occurs
+ */
+ public void close() throws IOException {
+ fInputStream.close();
+ } // close()
+
+} // class UCSReader
diff --git a/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java
new file mode 100644
index 000000000..78ab1b863
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/UTF8Reader.java
@@ -0,0 +1,640 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.jasper.compiler.Localizer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.UTFDataFormatException;
+
+/**
+ * @author Andy Clark, IBM
+ * @version $Id: UTF8Reader.java 466606 2006-10-21 23:07:12Z markt $
+ */
+public class UTF8Reader
+ extends Reader {
+
+ private Logger log = LoggerFactory.getLogger(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.
+ *
+ * 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
+ * (0x00-0xffff ), or -1 if the end of the stream has
+ * been reached
+ * @throws 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
+ * @throws 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
+ * @throws 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.
+ * @throws 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.
+ * @throws 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().
+ *
+ * @throws 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.
+ *
+ * @throws 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java
new file mode 100644
index 000000000..266cca65a
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLChar.java
@@ -0,0 +1,1043 @@
+/*
+ * 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
+ * .
+ */
+
+package org.apache.struts2.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.
+ *
+ * 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 CHARS
+ * array and applying the appropriate mask flag (e.g.
+ * MASK_VALID), yields the same results as calling the
+ * convenience methods. There is one exception: check the comments
+ * for the isValid 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: XMLChar.java 466606 2006-10-21 23:07:12Z markt $
+ */
+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.
+ *
+ * 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
+
+ } // ()
+
+ //
+ // 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.
+ *
+ * If the program chooses to apply the mask directly to the
+ * CHARS 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java
new file mode 100644
index 000000000..c5a065359
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java
@@ -0,0 +1,1591 @@
+/*
+ * 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
+ * .
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.compiler.ErrorDispatcher;
+import org.apache.struts2.jasper.compiler.JspUtil;
+
+import java.io.*;
+import java.util.Locale;
+import java.util.jar.JarFile;
+
+public class XMLEncodingDetector {
+
+ private InputStream stream;
+ private String encoding;
+ private boolean isEncodingSetInProlog;
+ 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,
+ new Boolean(this.isEncodingSetInProlog)};
+ }
+
+ // 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]);
+
+ 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};
+ }
+
+ // 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", new Boolean(true)};
+ }
+ if (b0 == 0xFF && b1 == 0xFE) {
+ // UTF-16, little-endian
+ return new Object[]{"UTF-16LE", new Boolean(false)};
+ }
+
+ // 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};
+ }
+
+ // UTF-8 with a BOM
+ int b2 = b4[2] & 0xFF;
+ if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+ return new Object[]{"UTF-8", null};
+ }
+
+ // 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};
+ }
+
+ // 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)};
+ }
+ if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
+ // UCS-4, little endian (4321)
+ return new Object[]{"ISO-10646-UCS-4", new Boolean(false)};
+ }
+ 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};
+ }
+ 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};
+ }
+ 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)};
+ }
+ 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)};
+ }
+ if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
+ // EBCDIC
+ // a la xerces1, return CP037 instead of EBCDIC here
+ return new Object[]{"CP037", null};
+ }
+
+ // default encoding
+ return new Object[]{"UTF-8", null};
+
+ }
+
+ // 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.
+ *
+ * Note: The character is not 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.
+ *
+ * Note: 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.
+ *
+ * Note: The Name characters are consumed.
+ *
+ * Note: 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.
+ *
+ * Note: The characters are consumed.
+ *
+ * Note: 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.
+ *
+ * Note: 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 note 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.
+ *
+ * Note: The characters are consumed.
+ *
+ * Note: 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.
+ *
+ * Note: 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.
+ *
+ * Note: 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.
+ *
+ * Note: 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.
+ *
+ * Note: 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.
+ *
+ * Note: 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. 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!
+ *
+ * @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("
+ *
+ * [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? '?>'
+ *
+ *
+ * @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.
+ *
+ *
+ * [23] XMLDecl ::= ''
+ * [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 ::= ''
+ *
+ *
+ * @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).
+ *
+ * Note: 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
+ *
+ * Note: 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 starts with "xml". (e.g. xmlfoo)
+ *
+ * Note: 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.
+ *
+ * Note: 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java
new file mode 100644
index 000000000..1e8220ecb
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.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.
+ * ====================================================================
+ *
+ * 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
+ * .
+ */
+
+package org.apache.struts2.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.
+ *
+ * Note: 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.
+ *
+ * Note: 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: XMLString.java 466606 2006-10-21 23:07:12Z markt $
+ */
+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() {
+ } // ()
+
+ /**
+ * 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);
+ } // (char[],int,int)
+
+ /**
+ * Constructs an XMLString structure with copies of the values in
+ * the given structure.
+ *
+ * Note: 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);
+ } // (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.
+ *
+ * Note: 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/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java
new file mode 100644
index 000000000..d97dbde7d
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java
@@ -0,0 +1,185 @@
+/*
+ * 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
+ * .
+ */
+
+package org.apache.struts2.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 not modify
+ * the contents of the XMLString structure.
+ *
+ * The contents of the string are managed by the string buffer. As
+ * characters are appended, the string buffer will grow as needed.
+ *
+ * Note: Never set the ch,
+ * offset, and length fields directly.
+ * These fields are managed by the string buffer. In order to reset
+ * the buffer, call clear().
+ *
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ * @version $Id: XMLStringBuffer.java 466606 2006-10-21 23:07:12Z markt $
+ */
+public class XMLStringBuffer
+ extends XMLString {
+
+ //
+ // Constants
+ //
+
+ /**
+ * Default buffer size (32).
+ */
+ public static final int DEFAULT_SIZE = 32;
+
+ //
+ // Constructors
+ //
+
+ /**
+ *
+ */
+ public XMLStringBuffer() {
+ this(DEFAULT_SIZE);
+ } // ()
+
+ /**
+ * @param size
+ */
+ public XMLStringBuffer(int size) {
+ ch = new char[size];
+ } // (int)
+
+ /**
+ * Constructs a string buffer from a char.
+ */
+ public XMLStringBuffer(char c) {
+ this(1);
+ append(c);
+ } // (char)
+
+ /**
+ * Constructs a string buffer from a String.
+ */
+ public XMLStringBuffer(String s) {
+ this(s.length());
+ append(s);
+ } // (String)
+
+ /**
+ * Constructs a string buffer from the specified character array.
+ */
+ public XMLStringBuffer(char[] ch, int offset, int length) {
+ this(length);
+ append(ch, offset, length);
+ } // (char[],int,int)
+
+ /**
+ * Constructs a string buffer from the specified XMLString.
+ */
+ public XMLStringBuffer(XMLString s) {
+ this(s.length);
+ append(s);
+ } // (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
diff --git a/plugins/embeddedjsp/src/main/resources/LICENSE.txt b/plugins/embeddedjsp/src/main/resources/LICENSE.txt
new file mode 100644
index 000000000..dd5b3a58a
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/resources/LICENSE.txt
@@ -0,0 +1,174 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
diff --git a/plugins/embeddedjsp/src/main/resources/struts-plugin.xml b/plugins/embeddedjsp/src/main/resources/struts-plugin.xml
new file mode 100644
index 000000000..5d446712b
--- /dev/null
+++ b/plugins/embeddedjsp/src/main/resources/struts-plugin.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java
new file mode 100644
index 000000000..4d64e62d2
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java
@@ -0,0 +1,334 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
+import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
+import junit.framework.TestCase;
+import org.apache.commons.lang.xwork.StringUtils;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+public class EmbeddedJSPResultTest extends TestCase {
+ private HttpServletRequest request;
+ private MockHttpServletResponse response;
+ private MockServletContext context;
+ private EmbeddedJSPResult result;
+
+ public void testScriptlet() throws Exception {
+ result.setLocation("org/apache/struts2/scriptlet.jsp");
+ result.execute(null);
+
+ assertEquals("Saynotoscriptlets", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testEmbedded() throws Exception {
+ //the jsp is inside jsps.jar
+ result.setLocation("dir/all.jsp");
+ result.execute(null);
+
+ assertEquals("helloJGWhoamI?XXXXXXXXXXXYThissessionisnotsecure.", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testFilesAreReadOnlyOnce() throws Exception {
+ //make sure that files are not read multiple times
+ String jsp = "org/apache/struts2/dont-use.jsp";
+
+ CountingClassLoaderInterface classLoaderInterface = new CountingClassLoaderInterface(this.getClass().getClassLoader());
+ context.setAttribute(ClassLoaderInterface.CLASS_LOADER_INTERFACE, classLoaderInterface);
+ result.setLocation(jsp);
+
+ result.execute(null);
+ Integer counter0 = classLoaderInterface.counters.get(jsp);
+ assertNotNull(counter0);
+
+ result.execute(null);
+ Integer counter1 = classLoaderInterface.counters.get(jsp);
+ assertNotNull(counter1);
+
+ assertEquals(counter0, counter1);
+ }
+
+ public void testEmbeddedAbsolutePath() throws Exception {
+ //the jsp is inside jsps.jar
+ result.setLocation("/dir/all.jsp");
+ result.execute(null);
+
+ assertEquals("helloJGWhoamI?XXXXXXXXXXXYThissessionisnotsecure.", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testSimple() throws Exception {
+ result.setLocation("org/apache/struts2/simple0.jsp");
+ result.execute(null);
+
+ assertEquals("hello", response.getContentAsString());
+ }
+
+ public void testEL() throws Exception {
+ result.setLocation("org/apache/struts2/el.jsp");
+ result.execute(null);
+
+ assertEquals("somethingelseText", response.getContentAsString());
+ }
+
+ public void tesAbsolutePatht() throws Exception {
+ result.setLocation("/org/apache/struts2/simple0.jsp");
+ result.execute(null);
+
+ assertEquals("hello", response.getContentAsString());
+ }
+
+ public void testTag0() throws Exception {
+ result.setLocation("org/apache/struts2/tag0.jsp");
+ result.execute(null);
+
+ assertEquals("Thissessionisnotsecure.OtherText", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testIncludeSimple() throws Exception {
+ result.setLocation("org/apache/struts2/includes0.jsp");
+ result.execute(null);
+
+ assertEquals("helloTest", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testIncludeSimpleWithDirective() throws Exception {
+ result.setLocation("org/apache/struts2/includes3.jsp");
+ result.execute(null);
+
+ assertEquals("helloTest", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testIncludeWithSubdir() throws Exception {
+ result.setLocation("org/apache/struts2/includes1.jsp");
+ result.execute(null);
+
+ assertEquals("subTest", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testIncludeWithParam() throws Exception {
+ result.setLocation("org/apache/struts2/includes2.jsp");
+ result.execute(null);
+
+ assertEquals("JGTest", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ public void testBroken0() throws Exception {
+ try {
+ result.setLocation("org/apache/struts2/broken0.jsp");
+ result.execute(null);
+ fail("should have failed with broken jsp");
+ } catch (IllegalStateException ex) {
+ //ok
+ }
+ }
+
+ public void testJSTL() throws Exception {
+ result.setLocation("org/apache/struts2/jstl.jsp");
+ result.execute(null);
+
+ assertEquals("XXXXXXXXXXXY", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+
+ public void testCachedInstances() throws InterruptedException {
+ ServletCache cache = new ServletCache();
+ Servlet servlet1 = cache.get("org/apache/struts2/simple0.jsp");
+ Servlet servlet2 = cache.get("org/apache/struts2/simple0.jsp");
+
+ assertSame(servlet1, servlet2);
+ }
+
+ public void testCacheInstanceWithManyThreads() throws BrokenBarrierException, InterruptedException {
+ //start a bunch of thread at the same time using CyclicBarrier and hit the cache
+ //then wait for all the threads to end and check that they all got a reference to the same object
+ //and the cache size should be 1
+
+ DummyServletCache cache = new DummyServletCache();
+ int numThreads = 70;
+
+ CyclicBarrier startBarrier = new CyclicBarrier(numThreads + 1);
+ CyclicBarrier endBarrier = new CyclicBarrier(numThreads + 1);
+
+ List runnables = new ArrayList(numThreads);
+
+ //create the threads
+ for (int i = 0; i < numThreads; i++) {
+ ServletGetRunnable runnable = new ServletGetRunnable(cache, startBarrier, endBarrier, ActionContext.getContext());
+ Thread thread = new Thread(runnable);
+ runnables.add(runnable);
+ thread.start();
+ }
+
+ startBarrier.await();
+ endBarrier.await();
+ Object servlet = cache.get("org/apache/struts2/simple0.jsp");
+ assertEquals(1, cache.size());
+
+ for (ServletGetRunnable runnable : runnables) {
+ assertSame(servlet, runnable.getObject());
+ }
+ }
+
+ public void testBeans() throws Exception {
+ result.setLocation("org/apache/struts2/beans.jsp");
+ result.execute(null);
+
+ assertEquals("WhoamI?", StringUtils.deleteWhitespace(response.getContentAsString()));
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ result = new EmbeddedJSPResult();
+
+ request = EasyMock.createNiceMock(HttpServletRequest.class);
+ response = new MockHttpServletResponse();
+ context = new MockServletContext();
+
+ final Map params = new HashMap();
+
+ HttpSession session = EasyMock.createNiceMock(HttpSession.class);
+ EasyMock.replay(session);
+
+ EasyMock.expect(request.getSession()).andReturn(session).anyTimes();
+ EasyMock.expect(request.getParameterMap()).andReturn(params).anyTimes();
+ EasyMock.expect(request.getParameter("username")).andAnswer(new IAnswer() {
+ @Override
+ public String answer() throws Throwable {
+ return ((String[]) params.get("username"))[0];
+ }
+ });
+ EasyMock.expect(request.getAttribute("something")).andReturn("somethingelse").anyTimes();
+
+ EasyMock.replay(request);
+
+ ActionContext actionContext = new ActionContext(new HashMap());
+ ActionContext.setContext(actionContext);
+ actionContext.setParameters(params);
+ ServletActionContext.setRequest(request);
+ ServletActionContext.setResponse(response);
+ ServletActionContext.setServletContext(context);
+
+ //mock value stack
+ Map stackContext = new HashMap();
+ ValueStack valueStack = EasyMock.createNiceMock(ValueStack.class);
+ EasyMock.expect(valueStack.getContext()).andReturn(stackContext).anyTimes();
+ EasyMock.replay(valueStack);
+
+ //mock converter
+ XWorkConverter converter = new DummyConverter();
+
+ //mock container
+ Container container = EasyMock.createNiceMock(Container.class);
+ EasyMock.expect(container.getInstance(XWorkConverter.class)).andReturn(converter).anyTimes();
+ EasyMock.replay(container);
+ stackContext.put(ActionContext.CONTAINER, container);
+ actionContext.setContainer(container);
+
+ actionContext.setValueStack(valueStack);
+ }
+}
+
+//converter has a protected default constructor...meh
+class DummyConverter extends XWorkConverter {
+
+}
+
+class DummyServletCache extends ServletCache {
+ public int size() {
+ return cache.size();
+ }
+}
+
+class ServletGetRunnable implements Runnable {
+ private ServletCache servletCache;
+ private Object object;
+ private CyclicBarrier startBarrier;
+ private ActionContext actionContext;
+ private CyclicBarrier endBarrier;
+
+ ServletGetRunnable(ServletCache servletCache, CyclicBarrier startBarrier, CyclicBarrier endBarrier, ActionContext actionContext) {
+ this.servletCache = servletCache;
+ this.startBarrier = startBarrier;
+ this.endBarrier = endBarrier;
+ this.actionContext = actionContext;
+ }
+
+ @Override
+ public void run() {
+ ActionContext.setContext(actionContext);
+ //wait to start all therads at once..or try at least
+ try {
+ startBarrier.await();
+ object = servletCache.get("org/apache/struts2/simple0.jsp");
+
+ for (int i = 0; i < 10; i++) {
+ Object object2 = servletCache.get("org/apache/struts2/simple0.jsp");
+ if (object2 != object)
+ throw new RuntimeException("got different object from cache");
+ }
+
+ endBarrier.await();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Object getObject() {
+ return object;
+ }
+}
+
+class CountingClassLoaderInterface extends ClassLoaderInterfaceDelegate {
+ public Map counters = new HashMap();
+
+ public CountingClassLoaderInterface(ClassLoader classLoader) {
+ super(classLoader);
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String name) {
+ Integer counter = counters.get(name);
+ counter = counter == null ? 1 : counter + 1;
+ counters.put(name, counter);
+
+ return super.getResourceAsStream(name);
+ }
+}
diff --git a/plugins/embeddedjsp/src/test/java/org/apache/struts2/SoyBean.java b/plugins/embeddedjsp/src/test/java/org/apache/struts2/SoyBean.java
new file mode 100644
index 000000000..16d8c0616
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/java/org/apache/struts2/SoyBean.java
@@ -0,0 +1,33 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2;
+
+public class SoyBean {
+ private String message = "Who am I?";
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/plugins/embeddedjsp/src/test/resources/jsps.jar b/plugins/embeddedjsp/src/test/resources/jsps.jar
new file mode 100644
index 000000000..4f9788545
Binary files /dev/null and b/plugins/embeddedjsp/src/test/resources/jsps.jar differ
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/beans.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/beans.jsp
new file mode 100644
index 000000000..6d5222d1d
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/beans.jsp
@@ -0,0 +1,2 @@
+
+
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/broken0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/broken0.jsp
new file mode 100644
index 000000000..b4bf816be
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/broken0.jsp
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/dont-use.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/dont-use.jsp
new file mode 100644
index 000000000..d6404b8f6
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/dont-use.jsp
@@ -0,0 +1 @@
+dont
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/el.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/el.jsp
new file mode 100644
index 000000000..9ea11949e
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/el.jsp
@@ -0,0 +1 @@
+${something}Text
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes0.jsp
new file mode 100644
index 000000000..d660643d2
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes0.jsp
@@ -0,0 +1,2 @@
+
+Test
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes1.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes1.jsp
new file mode 100644
index 000000000..b08963fb7
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes1.jsp
@@ -0,0 +1,2 @@
+
+Test
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes2.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes2.jsp
new file mode 100644
index 000000000..442f5b334
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes2.jsp
@@ -0,0 +1,4 @@
+
+
+
+Test
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes3.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes3.jsp
new file mode 100644
index 000000000..1ff0f54fa
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/includes3.jsp
@@ -0,0 +1,2 @@
+<%@ include file="org/apache/struts2/simple0.jsp" %>
+Test
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/jstl.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/jstl.jsp
new file mode 100644
index 000000000..82c7e6152
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/jstl.jsp
@@ -0,0 +1,9 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+ X
+
+
+ Y
+
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/printParam.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/printParam.jsp
new file mode 100644
index 000000000..118875218
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/printParam.jsp
@@ -0,0 +1 @@
+<%= request.getParameter("username") %>
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/scriptlet.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/scriptlet.jsp
new file mode 100644
index 000000000..1ce91108e
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/scriptlet.jsp
@@ -0,0 +1,4 @@
+<%@ page import="org.apache.commons.lang.xwork.StringUtils" %>
+<%
+ out.write(StringUtils.removeEnd("Say no to scriptlets/", "/"));
+%>
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/simple0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/simple0.jsp
new file mode 100644
index 000000000..b6fc4c620
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/simple0.jsp
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/sub/simple0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/sub/simple0.jsp
new file mode 100644
index 000000000..3de0f365b
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/sub/simple0.jsp
@@ -0,0 +1 @@
+sub
\ No newline at end of file
diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/tag0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/tag0.jsp
new file mode 100644
index 000000000..55e2b05db
--- /dev/null
+++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/tag0.jsp
@@ -0,0 +1,5 @@
+<%@ taglib prefix="r" uri="http://jakarta.apache.org/taglibs/request-1.0" %>
+
+ This session is not secure.
+
+OtherText
\ No newline at end of file