/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.jasper.compiler;
private String createJspId() { if (this.jspIdPrefix == null) {
StringBuilder sb = new StringBuilder(32);
String name = ctxt.getServletJavaFileName();
sb.append("jsp_"); // Cast to long to avoid issue with Integer.MIN_VALUE
sb.append(Math.abs((long) name.hashCode()));
sb.append('_'); this.jspIdPrefix = sb.toString();
} returnthis.jspIdPrefix + (this.jspId++);
}
/** * Generates declarations. This includes "info" of the page directive, and * scriptlet declarations.
*/ privatevoid generateDeclarations(Node.Nodes page) throws JasperException {
class DeclarationVisitor extends Node.Visitor {
privateboolean getServletInfoGenerated = false;
/* * Generates getServletInfo() method that returns the value of the * page directive's 'info' attribute, if present. * * The Validator has already ensured that if the translation unit * contains more than one page directive with an 'info' attribute, * their values match.
*/
@Override publicvoid visit(Node.PageDirective n) throws JasperException {
if (getServletInfoGenerated) { return;
}
String info = n.getAttributeValue("info"); if (info == null) { return;
}
// Custom Tags may contain declarations from tag plugins.
@Override publicvoid visit(Node.CustomTag n) throws JasperException { if (n.useTagPlugin()) { // If a custom tag is configured to use a plug-in // getAtSTag() and getAtETag() will always be non-null
n.getAtSTag().visit(this);
visitBody(n);
n.getAtETag().visit(this);
} else {
visitBody(n);
}
}
}
/** * Compiles list of tag handler pool names.
*/ privatevoid compileTagHandlerPoolList(Node.Nodes page) throws JasperException {
class TagHandlerPoolVisitor extends Node.Visitor {
privatefinal List<String> names;
/* * Constructor * * @param v Vector of tag handler pool names to populate
*/
TagHandlerPoolVisitor(List<String> v) {
names = v;
}
/* * Gets the name of the tag handler pool for the given custom tag * and adds it to the list of tag handler pool names unless it is * already contained in it.
*/
@Override publicvoid visit(Node.CustomTag n) throws JasperException {
if (!n.implementsSimpleTag()) {
String name = createTagHandlerPoolName(n.getPrefix(), n
.getLocalName(), n.getAttributes(),
n.getNamedAttributeNodes(), n.hasEmptyBody());
n.setTagHandlerPoolName(name); if (!names.contains(name)) {
names.add(name);
}
}
visitBody(n);
}
/* * Creates the name of the tag handler pool whose tag handlers may * be (re)used to service this action. * * @return The name of the tag handler pool
*/ private String createTagHandlerPoolName(String prefix,
String shortName, Attributes attrs, Node.Nodes namedAttrs, boolean hasEmptyBody) {
StringBuilder poolName = new StringBuilder(64);
poolName.append("_jspx_tagPool_").append(prefix).append('_')
.append(shortName);
if (attrs != null) {
String[] attrNames = new String[attrs.getLength() + namedAttrs.size()]; for (int i = 0; i < attrNames.length; i++) {
attrNames[i] = attrs.getQName(i);
} for (int i = 0; i < namedAttrs.size(); i++) {
attrNames[attrs.getLength() + i] =
namedAttrs.getNode(i).getQName();
}
Arrays.sort(attrNames, Collections.reverseOrder()); if (attrNames.length > 0) {
poolName.append('&');
} for (String attrName : attrNames) {
poolName.append('_');
poolName.append(attrName);
}
} if (hasEmptyBody) {
poolName.append("_nobody");
} return JspUtil.makeJavaIdentifier(poolName.toString());
}
}
ScriptingVarVisitor() {
vars = new ArrayList<>();
}
@Override publicvoid visit(Node.CustomTag n) throws JasperException { // XXX - Actually there is no need to declare those // "_jspx_" + varName + "_" + nestingLevel variables when we are // inside a JspFragment.
/* * Generates getters for * - instance manager * - expression factory * * For JSPs these methods use lazy init. This is not an option for tag files * (at least it would be more complicated to generate) because the * ServletConfig is not readily available.
*/ privatevoid generateGetters() {
out.printil("public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() {");
out.pushIndent(); if (!ctxt.isTagFile()) {
out.printin("if (");
out.print(ctxt.getOptions().getVariableForExpressionFactory());
out.println(" == null) {");
out.pushIndent();
out.printil("synchronized (this) {");
out.pushIndent();
out.printin("if (");
out.print(ctxt.getOptions().getVariableForExpressionFactory());
out.println(" == null) {");
out.pushIndent();
out.printin(ctxt.getOptions().getVariableForExpressionFactory());
out.println(" = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();");
out.popIndent();
out.printil("}");
out.popIndent();
out.printil("}");
out.popIndent();
out.printil("}");
}
out.printin("return ");
out.print(ctxt.getOptions().getVariableForExpressionFactory());
out.println(";");
out.popIndent();
out.printil("}");
/** * Generates the _jspInit() method for instantiating the tag handler pools. * For tag file, _jspInit has to be invoked manually, and the ServletConfig * object explicitly passed. * * In JSP 2.1, we also instantiate an ExpressionFactory
*/ privatevoid generateInit() {
out.pushIndent(); if (isPoolingEnabled) { for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
out.printin(tagHandlerPoolNames.get(i));
out.print(" = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool("); if (ctxt.isTagFile()) {
out.print("config");
} else {
out.print("getServletConfig()");
}
out.println(");");
}
}
// Tag files can't (easily) use lazy init for these so initialise them // here. if (ctxt.isTagFile()) {
out.printin(ctxt.getOptions().getVariableForExpressionFactory());
out.println(" = _jspxFactory.getJspApplicationContext(config.getServletContext()).getExpressionFactory();");
out.printin(ctxt.getOptions().getVariableForInstanceManager());
out.println(" = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(config);");
}
/** * Generates the _jspDestroy() method which is responsible for calling the * release() method on every tag handler in any of the tag handler pools.
*/ privatevoid generateDestroy() {
if (isPoolingEnabled) { for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
out.printin(tagHandlerPoolNames.get(i));
out.println(".release();");
}
}
/** * Generate preamble package name (shared by servlet and tag handler * preamble generation). Package is always non-null as neither Servlets nor * tags can use a default package.
*/ privatevoid genPreamblePackage(String packageName) {
out.printil("package " + packageName + ";");
out.println();
}
/** * Generate preamble imports (shared by servlet and tag handler preamble * generation)
*/ privatevoid genPreambleImports() { for (String i : pageInfo.getImports()) {
out.printin("import ");
out.print(i);
out.println(";");
}
out.println();
}
/** * Generation of static initializers in preamble. For example, dependent * list, el function map, prefix map. (shared by servlet and tag handler * preamble generation)
*/ privatevoid genPreambleStaticInitializers() {
out.printil("private static final jakarta.servlet.jsp.JspFactory _jspxFactory =");
out.printil(" jakarta.servlet.jsp.JspFactory.getDefaultFactory();");
out.println();
// Static data for getDependants()
out.printil("private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;");
out.println();
Map<String,Long> dependants = pageInfo.getDependants(); if (!dependants.isEmpty()) {
out.printil("static {");
out.pushIndent();
out.printin("_jspx_dependants = new java.util.HashMap<java.lang.String,java.lang.Long>(");
out.print("" + dependants.size());
out.println(");"); for (Entry<String, Long> entry : dependants.entrySet()) {
out.printin("_jspx_dependants.put(\"");
out.print(entry.getKey());
out.print("\", Long.valueOf(");
out.print(entry.getValue().toString());
out.println("L));");
}
out.popIndent();
out.printil("}");
out.println();
}
// Static data for getImports()
List<String> imports = pageInfo.getImports();
Set<String> packages = new HashSet<>();
Set<String> classes = new HashSet<>(); for (String importName : imports) {
String trimmed = importName.trim(); if (trimmed.endsWith(".*")) {
packages.add(trimmed.substring(0, trimmed.length() - 2));
} else {
classes.add(trimmed);
}
}
out.printil("private static final java.util.Set<java.lang.String> _jspx_imports_packages;");
out.println();
out.printil("private static final java.util.Set<java.lang.String> _jspx_imports_classes;");
out.println();
out.printil("static {");
out.pushIndent(); // Packages is never empty because o.a.j.Constants.STANDARD_IMPORTS // contains 3 packages and is always added to the imports.
out.printin("_jspx_imports_packages = new java.util.HashSet<>();");
out.println(); for (String packageName : packages) {
out.printin("_jspx_imports_packages.add(\"");
out.print(packageName);
out.println("\");");
} // classes however, may be empty depending on the import declarations if (classes.size() == 0) {
out.printin("_jspx_imports_classes = null;");
out.println();
} else {
out.printin("_jspx_imports_classes = new java.util.HashSet<>();");
out.println(); for (String className : classes) {
out.printin("_jspx_imports_classes.add(\"");
out.print(className);
out.println("\");");
}
}
out.popIndent();
out.printil("}");
out.println();
}
/** * Declare tag handler pools (tags of the same type and with the same * attribute set share the same tag handler pool) (shared by servlet and tag * handler preamble generation) * * In JSP 2.1, we also scope an instance of ExpressionFactory
*/ privatevoid genPreambleClassVariableDeclarations() { if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) { for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
out.printil("private org.apache.jasper.runtime.TagHandlerPool " + tagHandlerPoolNames.get(i) + ";");
}
out.println();
}
out.printin("private volatile jakarta.el.ExpressionFactory ");
out.print(ctxt.getOptions().getVariableForExpressionFactory());
out.println(";");
out.printin("private volatile org.apache.tomcat.InstanceManager ");
out.print(ctxt.getOptions().getVariableForInstanceManager());
out.println(";");
out.println();
}
// First the package name:
genPreamblePackage(servletPackageName);
// Generate imports
genPreambleImports();
// Generate class declaration
out.printin("public final class ");
out.print(servletClassName);
out.print(" extends ");
out.println(pageInfo.getExtends());
out.printin(" implements org.apache.jasper.runtime.JspSourceDependent,");
out.println();
out.printin(" org.apache.jasper.runtime.JspSourceImports");
out.println(",");
out.printin(" org.apache.jasper.runtime.JspSourceDirectives");
out.println(" {");
out.pushIndent();
// Class body begins here
generateDeclarations(page);
// Static initializations here
genPreambleStaticInitializers();
// Class variable declarations
genPreambleClassVariableDeclarations();
// Methods here
genPreambleMethods();
// Now the service method if (pageInfo.isThreadSafe()) {
out.printin("public void ");
} else { // This is unlikely to perform well.
out.printin("public synchronized void "); // As required by JSP 3.1, log a warning
log.warn(Localizer.getMessage("jsp.warning.isThreadSafe", ctxt.getJspFile()));
}
out.print(serviceMethodName);
out.println("(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)");
out.pushIndent();
out.pushIndent();
out.printil("throws java.io.IOException, jakarta.servlet.ServletException {");
out.popIndent();
out.println();
/** * Generates an XML Prolog, which includes an XML declaration and an XML * doctype declaration.
*/ privatevoid generateXmlProlog(Node.Nodes page) {
/* * An XML declaration is generated under the following conditions: - * 'omit-xml-declaration' attribute of <jsp:output> action is set to * "no" or "false" - JSP document without a <jsp:root>
*/
String omitXmlDecl = pageInfo.getOmitXmlDecl(); if ((omitXmlDecl != null && !JspUtil.booleanValue(omitXmlDecl)) ||
(omitXmlDecl == null && page.getRoot().isXmlSyntax() && !pageInfo.hasJspRoot() && !ctxt.isTagFile())) {
String cType = pageInfo.getContentType();
String charSet = cType.substring(cType.indexOf("charset=") + 8);
out.printil("out.write(\"<?xml version=\\\"1.0\\\" encoding=\\\""
+ charSet + "\\\"?>\\n\");");
}
/* * Output a DOCTYPE declaration if the doctype-root-element appears. If * doctype-public appears: <!DOCTYPE name PUBLIC "doctypePublic" * "doctypeSystem"> else <!DOCTYPE name SYSTEM "doctypeSystem" >
*/
/** * A visitor that generates codes for the elements in the page.
*/ privateclass GenerateVisitor extends Node.Visitor {
/* * Map containing introspection information on tag handlers: * <key>: tag prefix <value>: Map containing introspection on tag * handlers: <key>: tag short name <value>: introspection info of tag * handler for <prefix:shortName> tag
*/ privatefinal Map<String,Map<String,TagHandlerInfo>> handlerInfos;
privatefinal Map<String,Integer> tagVarNumbers;
private String parent;
privateboolean isSimpleTagParent; // Is parent a SimpleTag?
/** * Returns an attribute value, optionally URL encoded. If the value is a * runtime expression, the result is the expression itself, as a string. * If the result is an EL expression, we insert a call to the * interpreter. If the result is a Named Attribute we insert the * generated variable name. Otherwise the result is a string literal, * quoted and escaped. * * @param attr * An JspAttribute object * @param encode * true if to be URL encoded * @param expectedType * the expected type for an EL evaluation (ignored for * attributes that aren't EL expressions)
*/ private String attributeValue(Node.JspAttribute attr, boolean encode, Class<?> expectedType) {
String v = attr.getValue(); if (attr.isExpression()) { if (encode) { return"org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(String.valueOf("
+ v + "), request.getCharacterEncoding())";
} return v;
} elseif (attr.isELInterpreterInput()) {
v = elInterpreter.interpreterCall(ctxt, this.isTagFile, v,
expectedType, attr.getEL().getMapName()); if (encode) { return"org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+ v + ", request.getCharacterEncoding())";
} return v;
} elseif (attr.isNamedAttribute()) { return attr.getNamedAttributeNode().getTemporaryVariableName();
} else { if (encode) { return"org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+ quote(v) + ", request.getCharacterEncoding())";
} return quote(v);
}
}
/** * Prints the attribute value specified in the param action, in the form * of name=value string. * * @param n * the parent node for the param action nodes.
*/ privatevoid printParams(Node n, String pageParam, boolean literal) throws JasperException {
class ParamVisitor extends Node.Visitor { private String separator;
String pageParam; if (page.isNamedAttribute()) { // If the page for jsp:include was specified via // jsp:attribute, first generate code to evaluate // that body.
pageParam = generateNamedAttributeValue(page
.getNamedAttributeNode());
} else {
pageParam = attributeValue(page, false, String.class);
}
// If any of the params have their values specified by // jsp:attribute, prepare those values first.
Node jspBody = findJspBody(n); if (jspBody != null) {
prepareParams(jspBody);
} else {
prepareParams(n);
}
/** * Scans through all child nodes of the given parent for <param> * subelements. For each <param> element, if its value is specified via * a Named Attribute (<jsp:attribute>), generate the code to evaluate * those bodies first. * <p> * {@code parent} is assumed to be non-null
*/ privatevoid prepareParams(Node parent) throws JasperException {
Node.Nodes subelements = parent.getBody(); if (subelements != null) { for (int i = 0; i < subelements.size(); i++) {
Node n = subelements.getNode(i); // Validation during parsing ensures n is an instance of // Node.ParamAction
Node.Nodes paramSubElements = n.getBody(); for (int j = 0; (paramSubElements != null)
&& (j < paramSubElements.size()); j++) {
Node m = paramSubElements.getNode(j); if (m instanceof Node.NamedAttribute) {
generateNamedAttributeValue((Node.NamedAttribute) m);
}
}
}
}
}
/** * Finds the <jsp:body> subelement of the given parent node. If not * found, null is returned.
*/ private Node.JspBody findJspBody(Node parent) {
Node.JspBody result = null;
Node.Nodes subelements = parent.getBody(); for (int i = 0; (subelements != null) && (i < subelements.size()); i++) {
Node n = subelements.getNode(i); if (n instanceof Node.JspBody) {
result = (Node.JspBody) n; break;
}
}
out.printil("if (true) {"); // So that javac won't complain about
out.pushIndent(); // codes after "return"
String pageParam; if (page.isNamedAttribute()) { // If the page for jsp:forward was specified via // jsp:attribute, first generate code to evaluate // that body.
pageParam = generateNamedAttributeValue(page.getNamedAttributeNode());
} else {
pageParam = attributeValue(page, false, String.class);
}
// If any of the params have their values specified by // jsp:attribute, prepare those values first.
Node jspBody = findJspBody(n); if (jspBody != null) {
prepareParams(jspBody);
} else {
prepareParams(n);
}
if (beanInfo.checkVariable(name)) { // Bean is defined using useBean, introspect at compile time Class<?> bean = beanInfo.getBeanType(name);
String beanName = bean.getCanonicalName();
Method meth = JspRuntimeLibrary.getReadMethod(bean, property);
String methodName = meth.getName();
out.printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString("
+ "((("
+ beanName
+ ")_jspx_page_context.findAttribute("
+ "\""
+ name + "\"))." + methodName + "())));");
} elseif (!ctxt.getOptions().getStrictGetProperty() || varInfoNames.contains(name)) { // The object is a custom action with an associated // VariableInfo entry for this name. // Get the class name and then introspect at runtime.
out.printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString"
+ "(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty"
+ "(_jspx_page_context.findAttribute(\""
+ name
+ "\"), \""
+ property
+ "\")));");
} else { thrownew JasperException(Localizer.getMessage("jsp.error.invalid.name", n.getStart(), name));
}
if ("*".equals(property)) {
out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspect("
+ "_jspx_page_context.findAttribute("
+ "\""
+ name + "\"), request);");
} elseif (value == null) { if (param == null)
{
param = property; // default to same as property
}
out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+ "_jspx_page_context.findAttribute(\""
+ name
+ "\"), \""
+ property
+ "\", request.getParameter(\""
+ param
+ "\"), "
+ "request, \""
+ param
+ "\", false);");
} elseif (value.isExpression()) {
out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetProperty("
+ "_jspx_page_context.findAttribute(\""
+ name
+ "\"), \"" + property + "\",");
out.print(attributeValue(value, false, null));
out.println(");");
} elseif (value.isELInterpreterInput()) { // We've got to resolve the very call to the interpreter // at runtime since we don't know what type to expect // in the general case; we thus can't hard-wire the call // into the generated code. (XXX We could, however, // optimize the case where the bean is exposed with // <jsp:useBean>, much as the code here does for // getProperty.)
// The following holds true for the arguments passed to // JspRuntimeLibrary.handleSetPropertyExpression(): // - 'pageContext' is a VariableResolver. // - 'this' (either the generated Servlet or the generated tag // handler for Tag files) is a FunctionMapper.
out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetPropertyExpression("
+ "_jspx_page_context.findAttribute(\""
+ name
+ "\"), \""
+ property
+ "\", "
+ quote(value.getValue())
+ ", "
+ "_jspx_page_context, "
+ value.getEL().getMapName() + ");");
} elseif (value.isNamedAttribute()) { // If the value for setProperty was specified via // jsp:attribute, first generate code to evaluate // that body.
String valueVarName = generateNamedAttributeValue(value
.getNamedAttributeNode());
out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+ "_jspx_page_context.findAttribute(\""
+ name
+ "\"), \""
+ property
+ "\", "
+ valueVarName
+ ", null, null, false);");
} else {
out.printin("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+ "_jspx_page_context.findAttribute(\""
+ name
+ "\"), \"" + property + "\", ");
out.print(attributeValue(value, false, null));
out.println(", null, null, false);");
}
String name = n.getTextAttribute("id");
String scope = n.getTextAttribute("scope");
String klass = n.getTextAttribute("class");
String type = n.getTextAttribute("type");
Node.JspAttribute beanName = n.getBeanName();
// If "class" is specified, try an instantiation at compile time boolean generateNew = false;
String canonicalName = null; // Canonical name for klass if (klass != null) { try { Class<?> bean = ctxt.getClassLoader().loadClass(klass); if (klass.indexOf('$') >= 0) { // Obtain the canonical type name
canonicalName = bean.getCanonicalName();
} else {
canonicalName = klass;
} // Check that there is a 0 arg constructor
Constructor<?> constructor = bean.getConstructor(newClass[] {}); // Check the bean is public, not an interface, not abstract // and in an exported module int modifiers = bean.getModifiers(); // No need to test for being an interface here as the // getConstructor() call above will have already failed for // any interfaces. if (!Modifier.isPublic(modifiers) ||
Modifier.isAbstract(modifiers) ||
!constructor.canAccess(null) ) { thrownew JasperException(Localizer.getMessage("jsp.error.invalid.bean",
Integer.valueOf(modifiers)));
} // At compile time, we have determined that the bean class // exists, with a public zero constructor, new() can be // used for bean instantiation.
generateNew = true;
} catch (Exception e) { // Cannot instantiate the specified class, either a // compilation error or a runtime error will be raised, // depending on a compiler flag. if (ctxt.getOptions()
.getErrorOnUseBeanInvalidClassAttribute()) {
err.jspError(n, "jsp.error.invalid.bean", klass);
} if (canonicalName == null) { // Doing our best here to get a canonical name // from the binary name, should work 99.99% of time.
canonicalName = klass.replace('$', '.');
}
} if (type == null) { // if type is unspecified, use "class" as type of bean
type = canonicalName;
}
}
// JSP.5.1, Semantics, para 1 - lock not required for request or // page scope
String scopename = "jakarta.servlet.jsp.PageContext.PAGE_SCOPE"; // Default to page
String lock = null;
// Create bean /* * Check if bean is already there
*/
out.printin("if (");
out.print(name);
out.println(" == null){");
out.pushIndent(); if (klass == null && beanName == null) { /* * If both class name and beanName is not specified, the bean * must be found locally, otherwise it's an error
*/
out.printin("throw new java.lang.InstantiationException(\"bean ");
out.print(name);
out.println(" not found within scope\");");
} else { /* * Instantiate the bean if it is not in the specified scope.
*/ if (!generateNew) {
String binaryName; if (beanName != null) { if (beanName.isNamedAttribute()) { // If the value for beanName was specified via // jsp:attribute, first generate code to evaluate // that body.
binaryName = generateNamedAttributeValue(beanName
.getNamedAttributeNode());
} else {
binaryName = attributeValue(beanName, false, String.class);
}
} else { // Implies klass is not null
binaryName = quote(klass);
}
out.printil("try {");
out.pushIndent();
out.printin(name);
out.print(" = (");
out.print(type);
out.print(") java.beans.Beans.instantiate(");
out.print("this.getClass().getClassLoader(), ");
out.print(binaryName);
out.println(");");
out.popIndent(); /* * Note: Beans.instantiate throws ClassNotFoundException if * the bean class is abstract.
*/
out.printil("} catch (java.lang.ClassNotFoundException exc) {");
out.pushIndent();
out.printil("throw new InstantiationException(exc.getMessage());");
out.popIndent();
out.printil("} catch (java.lang.Exception exc) {");
out.pushIndent();
out.printin("throw new jakarta.servlet.ServletException(");
out.print("\"Cannot create bean of class \" + ");
out.print(binaryName);
out.println(", exc);");
out.popIndent();
out.printil("}"); // close of try
} else { // Implies klass is not null // Generate codes to instantiate the bean class
out.printin(name);
out.print(" = new ");
out.print(canonicalName);
out.println("();");
} /* * Set attribute for bean in the specified scope
*/
out.printin("_jspx_page_context.setAttribute(");
out.print(quote(name));
out.print(", ");
out.print(name);
out.print(", ");
out.print(scopename);
out.println(");");
// Only visit the body when bean is instantiated
visitBody(n);
}
out.popIndent();
out.printil("}");
// End of lock block if (lock != null) {
out.popIndent();
out.printil("}");
}
// If the tag contains no scripting element, generate its codes // to a method.
ServletWriter outSave = null;
Node.ChildInfo ci = n.getChildInfo(); if (ci.isScriptless() && !ci.hasScriptingVars()) { // The tag handler and its body code can reside in a separate // method if it is scriptless and does not have any scripting // variable defined.
String tagMethod = "_jspx_meth_" + baseVar;
// Generate a call to this method
out.printin("if (");
out.print(tagMethod);
out.print("("); if (parent != null) {
out.print(parent);
out.print(", ");
}
out.print("_jspx_page_context"); if (pushBodyCountVar != null) {
out.print(", ");
out.print(pushBodyCountVar);
}
out.println("))");
out.pushIndent();
out.printil((methodNesting > 0) ? "return true;" : "return;");
out.popIndent();
// Set up new buffer for the method
outSave = out; /* * For fragments, their bodies will be generated in fragment * helper classes, and the Java line adjustments will be done * there, hence they are set to null here to avoid double * adjustments.
*/
GenBuffer genBuffer = new GenBuffer(n,
n.implementsSimpleTag() ? null : n.getBody());
methodsBuffered.add(genBuffer);
out = genBuffer.getOut();
// Initialize local variables used in this method. if (!isTagFile) {
out.printil("jakarta.servlet.jsp.PageContext pageContext = _jspx_page_context;");
} // Only need to define out if the tag has a non-empty body, // implements TryCatchFinally or uses // <jsp:attribute>...</jsp:attribute> nodes if (!n.hasEmptyBody() || n.implementsTryCatchFinally() || n.getNamedAttributeNodes().size() > 0) {
out.printil("jakarta.servlet.jsp.JspWriter out = _jspx_page_context.getOut();");
}
generateLocalVariables(out, n);
}
// Add the named objects to the list of 'introduced' names to enable // a later test as per JSP.5.3
VariableInfo[] infos = n.getVariableInfos(); // The Validator always calls setTagData() which ensures infos is // non-null if (infos.length > 0) { for (VariableInfo info : infos) { // A null variable name will trigger multiple compilation // failures so assume non-null here
pageInfo.getVarInfoNames().add(info.getVarName());
}
}
TagVariableInfo[] tagInfos = n.getTagVariableInfos(); // The way Tomcat constructs the TagInfo, getTagVariableInfos() // will never return null. if (tagInfos.length > 0) { for (TagVariableInfo tagInfo : tagInfos) { // tagInfo is always non-null
String name = tagInfo.getNameGiven(); if (name == null) {
String nameFromAttribute =
tagInfo.getNameFromAttribute();
name = n.getAttributeValue(nameFromAttribute);
}
pageInfo.getVarInfoNames().add(name);
}
}
if (n.implementsSimpleTag()) {
generateCustomDoTag(n, handlerInfo, tagHandlerVar);
} else { /* * Classic tag handler: Generate code for start element, body, * and end element
*/
generateCustomStart(n, handlerInfo, tagHandlerVar, tagEvalVar,
tagPushBodyCountVar);
if (ci.isScriptless() && !ci.hasScriptingVars()) { // Generate end of method
out.printil("return false;");
out.popIndent();
out.printil("}");
out.popIndent();
// Compute attribute value string for XML-style and named // attributes
Map<String,String> map = new HashMap<>(); // Validator ensures this is non-null
Node.JspAttribute[] attrs = n.getJspAttributes(); for (int i = 0; i < attrs.length; i++) {
String value = null;
String nvp = null; if (attrs[i].isNamedAttribute()) {
NamedAttribute attr = attrs[i].getNamedAttributeNode();
Node.JspAttribute omitAttr = attr.getOmit();
String omit; if (omitAttr == null) {
omit = "false";
} else { // String literals returned by attributeValue will be // quoted and escaped.
omit = attributeValue(omitAttr, false, boolean.class); if ("\"true\"".equals(omit)) { continue;
}
}
value = generateNamedAttributeValue(
attrs[i].getNamedAttributeNode()); if ("\"false\"".equals(omit)) {
nvp = " + \"" + attrs[i].getName() + "=\\\"\" + " +
value + " + \"\\\"\"";
} else {
nvp = " + (java.lang.Boolean.valueOf(" + omit + ")?\"\":\"" +
attrs[i].getName() + "=\\\"\" + " + value + " + \"\\\"\")";
}
} else {
value = attributeValue(attrs[i], false, Object.class);
nvp = " + \"" + attrs[i].getName() + "=\\\"\" + " +
value + " + \"\\\"\"";
}
map.put(attrs[i].getName(), nvp);
}
// Write begin tag, using XML-style 'name' attribute as the // element name
String elemName = attributeValue(n.getNameAttribute(), false, String.class);
out.printin("out.write(\"<\"");
out.print(" + " + elemName);
String text = n.getText(); // If the extended option is being used attempt to minimize the // frequency of regex operations. if (ctxt.getOptions().getTrimSpaces().equals(TrimSpacesOption.EXTENDED) && text.contains("\n")) { // Ensure there are no <pre> or </pre> tags embedded in this // text - if there are, we want to NOT modify the whitespace.
Matcher preMatcher = PRE_TAG_PATTERN.matcher(text); if (!preMatcher.matches()) {
Matcher matcher = BLANK_LINE_PATTERN.matcher(text);
String revisedText = matcher.replaceAll("\n"); // Leading and trailing whitespace can be trimmed so remove // it here as the regex won't remove it.
text = revisedText.trim();
}
}
int textSize = text.length(); if (textSize == 0) { return;
}
if (textSize <= 3) { // Special case small text strings
n.setBeginJavaLine(out.getJavaLine()); int lineInc = 0; for (int i = 0; i < textSize; i++) { char ch = text.charAt(i);
out.printil("out.write(" + quote(ch) + ");"); if (i > 0) {
n.addSmap(lineInc);
} if (ch == '\n') {
lineInc++;
}
}
n.setEndJavaLine(out.getJavaLine()); return;
}
if (ctxt.getOptions().genStringAsCharArray()) { // Generate Strings as char arrays, for performance
ServletWriter caOut; if (charArrayBuffer == null) {
charArrayBuffer = new GenBuffer();
caOut = charArrayBuffer.getOut();
caOut.pushIndent();
textMap = new HashMap<>();
} else {
caOut = charArrayBuffer.getOut();
} // UTF-8 is up to 4 bytes per character // String constants are limited to 64k bytes // Limit string constants here to 16k characters int textIndex = 0; int textLength = text.length(); while (textIndex < textLength) { int len = 0; if (textLength - textIndex > 16384) {
len = 16384;
} else {
len = textLength - textIndex;
}
String output = text.substring(textIndex, textIndex + len);
String charArrayName = textMap.get(output); if (charArrayName == null) {
charArrayName = "_jspx_char_array_" + charArrayCount++;
textMap.put(output, charArrayName);
caOut.printin("static char[] ");
caOut.print(charArrayName);
caOut.print(" = ");
caOut.print(quote(output));
caOut.println(".toCharArray();");
}
out.printin();
StringBuilder sb = new StringBuilder("out.write(\""); int initLength = sb.length(); int count = JspUtil.CHUNKSIZE; int srcLine = 0; // relative to starting source line for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i);
--count; switch (ch) { case'"':
sb.append('\\').append('\"'); break; case'\\':
sb.append('\\').append('\\'); break; case'\r':
sb.append('\\').append('r'); break; case'\n':
sb.append('\\').append('n');
srcLine++;
if (breakAtLF || count < 0) { // Generate an out.write() when see a '\n' in template
sb.append("\");");
out.println(sb.toString()); if (i < text.length() - 1) {
out.printin();
}
sb.setLength(initLength);
count = JspUtil.CHUNKSIZE;
} // add a Smap for this line
n.addSmap(srcLine); break; default:
sb.append(ch);
}
}
if (sb.length() > initLength) {
sb.append("\");");
out.println(sb.toString());
}
// Copy virtual page scope of tag file to page scope of invoking // page
out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();");
// Store varReader in appropriate scope if (varReaderAttr != null || varAttr != null) {
String scopeName = n.getTextAttribute("scope");
out.printin("_jspx_page_context.setAttribute("); if (varReaderAttr != null) {
out.print(quote(varReaderAttr));
out.print(", new java.io.StringReader(_jspx_sout.toString())");
} else {
out.print(quote(varAttr));
out.print(", _jspx_sout.toString()");
} if (scopeName != null) {
out.print(", ");
out.print(getScopeConstant(scopeName));
}
out.println(");");
}
// Restore EL context
out.printil("jspContext.getELContext().putContext(jakarta.servlet.jsp.JspContext.class,getJspContext());");
n.setEndJavaLine(out.getJavaLine());
}
@Override publicvoid visit(Node.AttributeGenerator n) throws JasperException {
Node.CustomTag tag = n.getTag();
Node.JspAttribute[] attrs = tag.getJspAttributes(); // The TagPluginManager only creates AttributeGenerator nodes for // attributes that are present. for (int i = 0; i < attrs.length; i++) { if (attrs[i].getName().equals(n.getName())) {
out.print(evaluateAttribute(getTagHandlerInfo(tag),
attrs[i], tag, null)); break;
}
}
}
private TagHandlerInfo getTagHandlerInfo(Node.CustomTag n) throws JasperException {
Map<String, TagHandlerInfo> handlerInfosByShortName = handlerInfos.
computeIfAbsent(n.getPrefix(), k -> new HashMap<>());
TagHandlerInfo handlerInfo =
handlerInfosByShortName.get(n.getLocalName()); if (handlerInfo == null) {
handlerInfo = new TagHandlerInfo(n, n.getTagHandlerClass(), err);
handlerInfosByShortName.put(n.getLocalName(), handlerInfo);
} return handlerInfo;
}
if (n.implementsIterationTag()) {
out.printil("do {");
out.pushIndent();
}
} // Map the Java lines that handles start of custom tags to the // JSP line for this tag
n.setEndJavaLine(out.getJavaLine());
}
privatevoid writeNewInstance(String tagHandlerVar, Class<?> tagHandlerClass) {
String tagHandlerClassName = tagHandlerClass.getCanonicalName();
out.printin(tagHandlerClassName);
out.print(" ");
out.print(tagHandlerVar);
out.print(" = "); if (useInstanceManagerForTags) {
out.print("(");
out.print(tagHandlerClassName);
out.print(")");
out.print("_jsp_getInstanceManager().newInstance(\""); // Need the binary name here, not the canonical name
out.print(tagHandlerClass.getName());
out.println("\", this.getClass().getClassLoader());");
} else {
out.print("new ");
out.print(tagHandlerClassName);
out.println("();");
out.printin("_jsp_getInstanceManager().newInstance(");
out.print(tagHandlerVar);
out.println(");");
}
}
if (n.implementsTryCatchFinally()) {
out.popIndent();
out.printil("}");
}
// Print tag reuse if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
out.printin(n.getTagHandlerPoolName());
out.print(".reuse(");
out.print(tagHandlerVar);
out.println(");");
out.printin(tagHandlerVar);
out.println("_reused = true;");
}
// Ensure clean-up takes place // Use JspRuntimeLibrary to minimise code in _jspService()
out.popIndent();
out.printil("} finally {");
out.pushIndent();
out.printin("org.apache.jasper.runtime.JspRuntimeLibrary.releaseTag(");
out.print(tagHandlerVar);
out.print(", _jsp_getInstanceManager(), "); if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
out.print(tagHandlerVar);
out.println("_reused);");
} else {
out.println("false);");
}
out.popIndent();
out.printil("}");
// Declare and synchronize AT_END scripting variables (must do this // outside the try/catch/finally block)
declareScriptingVars(n, VariableInfo.AT_END);
syncScriptingVars(n, VariableInfo.AT_END);
restoreScriptingVars(n, VariableInfo.AT_BEGIN);
}
privatevoid generateCustomDoTag(Node.CustomTag n,
TagHandlerInfo handlerInfo, String tagHandlerVar) throws JasperException {
// Set the body if (findJspBody(n) == null) { /* * Encapsulate body of custom tag invocation in JspFragment and * pass it to tag handler's setJspBody(), unless tag body is * empty
*/ if (!n.hasEmptyBody()) {
out.printin(tagHandlerVar);
out.print(".setJspBody(");
generateJspFragment(n, tagHandlerVar);
out.println(");");
}
} else { /* * Body of tag is the body of the <jsp:body> element. The visit * method for that element is going to encapsulate that * element's body in a JspFragment and pass it to the tag * handler's setJspBody()
*/
String tmpTagHandlerVar = simpleTagHandlerVar;
simpleTagHandlerVar = tagHandlerVar; boolean tmpIsSimpleTagHandler = isSimpleTagHandler;
isSimpleTagHandler = true;
visitBody(n);
simpleTagHandlerVar = tmpTagHandlerVar;
isSimpleTagHandler = tmpIsSimpleTagHandler;
}
privatevoid declareScriptingVars(Node.CustomTag n, int scope) { if (isFragment) { // No need to declare Java variables, if we inside a // JspFragment, because a fragment is always scriptless. return;
}
// Note: ScriptingVariabler$ScriptingVariableVisitor will already // have skipped any variables where declare is set to false.
List<Object> vec = n.getScriptingVars(scope); if (vec != null) { for (Object elem : vec) { if (elem instanceof VariableInfo) {
VariableInfo varInfo = (VariableInfo) elem;
out.printin(varInfo.getClassName());
out.print(" ");
out.print(varInfo.getVarName());
out.println(" = null;");
} else {
TagVariableInfo tagVarInfo = (TagVariableInfo) elem;
String varName = tagVarInfo.getNameGiven(); if (varName == null) {
varName = n.getTagData().getAttributeString(
tagVarInfo.getNameFromAttribute());
} elseif (tagVarInfo.getNameFromAttribute() != null) { // alias continue;
}
out.printin(tagVarInfo.getClassName());
out.print(" ");
out.print(varName);
out.println(" = null;");
}
}
}
}
/* * This method is called as part of the custom tag's start element. * * If the given custom tag has a custom nesting level greater than 0, * save the current values of its scripting variables to temporary * variables, so those values may be restored in the tag's end element. * This way, the scripting variables may be synchronized by the given * tag without affecting their original values.
*/ privatevoid saveScriptingVars(Node.CustomTag n, int scope) { if (n.getCustomNestingLevel() == 0) { return;
} if (isFragment) { // No need to declare Java variables, if we inside a // JspFragment, because a fragment is always scriptless. // Thus, there is no need to save/ restore/ sync them. // Note, that JspContextWrapper.syncFoo() methods will take // care of saving/ restoring/ sync'ing of JspContext attributes. return;
}
if (varInfos.length > 0) { for (VariableInfo varInfo : varInfos) { if (varInfo.getScope() != scope) { continue;
} // If the scripting variable has been declared, skip codes // for saving and restoring it. if (declaredVariables.contains(varInfo)) { continue;
}
String varName = varInfo.getVarName();
String tmpVarName = "_jspx_" + varName + "_"
+ n.getCustomNestingLevel();
out.printin(tmpVarName);
out.print(" = ");
out.print(varName);
out.println(";");
}
} else { for (TagVariableInfo tagVarInfo : tagVarInfos) { if (tagVarInfo.getScope() != scope) { continue;
} // If the scripting variable has been declared, skip codes // for saving and restoring it. if (declaredVariables.contains(tagVarInfo)) { continue;
}
String varName = tagVarInfo.getNameGiven(); if (varName == null) {
varName = n.getTagData().getAttributeString(
tagVarInfo.getNameFromAttribute());
} // Alias is not possible here. // Alias can only be configured for tag files. As SimpleTag // implementations, isFragment will always be true above // hence execution never reaches this point.
/* * This method is called as part of the custom tag's end element. * * If the given custom tag has a custom nesting level greater than 0, * restore its scripting variables to their original values that were * saved in the tag's start element.
*/ privatevoid restoreScriptingVars(Node.CustomTag n, int scope) { if (n.getCustomNestingLevel() == 0) { return;
} if (isFragment) { // No need to declare Java variables, if we inside a // JspFragment, because a fragment is always scriptless. // Thus, there is no need to save/ restore/ sync them. // Note, that JspContextWrapper.syncFoo() methods will take // care of saving/ restoring/ sync'ing of JspContext attributes. return;
}
if (varInfos.length > 0) { for (VariableInfo varInfo : varInfos) { if (varInfo.getScope() != scope) { continue;
} // If the scripting variable has been declared, skip codes // for saving and restoring it. if (declaredVariables.contains(varInfo)) { continue;
}
String varName = varInfo.getVarName();
String tmpVarName = "_jspx_" + varName + "_"
+ n.getCustomNestingLevel();
out.printin(varName);
out.print(" = ");
out.print(tmpVarName);
out.println(";");
}
} else { for (TagVariableInfo tagVarInfo : tagVarInfos) { if (tagVarInfo.getScope() != scope) { continue;
} // If the scripting variable has been declared, skip codes // for saving and restoring it. if (declaredVariables.contains(tagVarInfo)) { continue;
}
String varName = tagVarInfo.getNameGiven(); if (varName == null) {
varName = n.getTagData().getAttributeString(
tagVarInfo.getNameFromAttribute());
} // Alias is not possible here. // Alias can only be configured for tag files. As SimpleTag // implementations, isFragment will always be true above // hence execution never reaches this point.
/* * Synchronizes the scripting variables of the given custom tag for the * given scope.
*/ privatevoid syncScriptingVars(Node.CustomTag n, int scope) { if (isFragment) { // No need to declare Java variables, if we inside a // JspFragment, because a fragment is always scriptless. // Thus, there is no need to save/ restore/ sync them. // Note, that JspContextWrapper.syncFoo() methods will take // care of saving/ restoring/ sync'ing of JspContext attributes. return;
}
/* * Creates a tag variable name by concatenating the given prefix and * shortName and encoded to make the resultant string a valid Java * Identifier.
*/ private String createTagVarName(String fullName, String prefix,
String shortName) {
String attrValue = attr.getValue(); if (attrValue == null) { // Must be a named attribute if (n.checkIfAttributeIsJspFragment(attr.getName())) { // XXX - no need to generate temporary variable here
attrValue = generateNamedAttributeJspFragment(attr
.getNamedAttributeNode(), tagHandlerVar);
} else {
attrValue = generateNamedAttributeValue(attr
.getNamedAttributeNode());
}
}
String localName = attr.getLocalName();
Method m = null; Class<?>[] c = null; if (attr.isDynamic()) {
c = OBJECT_CLASS;
} else {
m = handlerInfo.getSetterMethod(localName); if (m == null) {
err.jspError(n, "jsp.error.unable.to_find_method", attr
.getName());
}
c = m.getParameterTypes(); // XXX assert(c.length > 0)
}
if (attr.isExpression()) { // Do nothing
} elseif (attr.isNamedAttribute()) { if (!n.checkIfAttributeIsJspFragment(attr.getName())
&& !attr.isDynamic()) {
attrValue = stringInterpreter.convertString(c[0], attrValue, localName,
handlerInfo.getPropertyEditorClass(localName), true);
}
} elseif (attr.isELInterpreterInput()) {
// results buffer
StringBuilder sb = new StringBuilder(64);
TagAttributeInfo tai = attr.getTagAttributeInfo();
// create our mark
sb.append(n.getStart().toString());
sb.append(" '");
sb.append(attrValue);
sb.append('\'');
String mark = sb.toString();
// reset buffer
sb.setLength(0);
// depending on type if (attr.isDeferredInput()
|| ((tai != null) && ValueExpression.class.getName().equals(tai.getTypeName()))) {
sb.append("new org.apache.jasper.el.JspValueExpression(");
sb.append(quote(mark));
sb.append(",_jsp_getExpressionFactory().createValueExpression("); if (attr.getEL() != null) { // optimize
sb.append(elContext);
sb.append(',');
}
sb.append(quote(attrValue));
sb.append(',');
sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
sb.append("))"); // should the expression be evaluated before passing to // the setter? boolean evaluate = false; if (tai.canBeRequestTime()) {
evaluate = true; // JSP.2.3.2
} if (attr.isDeferredInput()) {
evaluate = false; // JSP.2.3.3
} if (attr.isDeferredInput() && tai.canBeRequestTime()) {
evaluate = !attrValue.contains("#{"); // JSP.2.3.5
} if (evaluate) {
sb.append(".getValue(");
sb.append(getJspContextVar());
sb.append(".getELContext()");
sb.append(')');
}
attrValue = sb.toString();
} elseif (attr.isDeferredMethodInput()
|| ((tai != null) && MethodExpression.class.getName().equals(tai.getTypeName()))) {
sb.append("new org.apache.jasper.el.JspMethodExpression(");
sb.append(quote(mark));
sb.append(",_jsp_getExpressionFactory().createMethodExpression(");
sb.append(elContext);
sb.append(',');
sb.append(quote(attrValue));
sb.append(',');
sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
sb.append(',');
sb.append("new java.lang.Class[] {");
String[] p = attr.getParameterTypeNames(); for (String s : p) {
sb.append(JspUtil.toJavaSourceTypeFromTld(s));
sb.append(',');
} if (p.length > 0) {
sb.setLength(sb.length() - 1);
}
sb.append("}))");
attrValue = sb.toString();
} else { // Must be EL // run attrValue through the expression interpreter
String mapName = attr.getEL().getMapName();
attrValue = elInterpreter.interpreterCall(ctxt, this.isTagFile, attrValue, c[0], mapName);
}
} else {
attrValue = stringInterpreter.convertString(c[0], attrValue, localName,
handlerInfo.getPropertyEditorClass(localName), false);
} return attrValue;
}
/** * Generate code to create a map for the alias variables * * @return the name of the map
*/ private String generateAliasMap(Node.CustomTag n,
String tagHandlerVar) {
// Set context if (simpleTag) { // Generate alias map
String aliasMapVar = null; if (n.isTagFile()) {
aliasMapVar = generateAliasMap(n, tagHandlerVar);
}
out.printin(tagHandlerVar); if (aliasMapVar == null) {
out.println(".setJspContext(_jspx_page_context);");
} else {
out.print(".setJspContext(_jspx_page_context, ");
out.print(aliasMapVar);
out.println(");");
}
} else {
out.printin(tagHandlerVar);
out.println(".setPageContext(_jspx_page_context);");
}
// Set parent if (isTagFile && parent == null) {
out.printin(tagHandlerVar);
out.print(".setParent(");
out.print("new jakarta.servlet.jsp.tagext.TagAdapter(");
out.println("(jakarta.servlet.jsp.tagext.SimpleTag) this ));");
} elseif (!simpleTag) {
out.printin(tagHandlerVar);
out.print(".setParent("); if (parent != null) { if (isSimpleTagParent) {
out.print("new jakarta.servlet.jsp.tagext.TagAdapter(");
out.print("(jakarta.servlet.jsp.tagext.SimpleTag) ");
out.print(parent);
out.println("));");
} else {
out.print("(jakarta.servlet.jsp.tagext.Tag) ");
out.print(parent);
out.println(");");
}
} else {
out.println("null);");
}
} else { // The setParent() method need not be called if the value being // passed is null, since SimpleTag instances are not reused if (parent != null) {
out.printin(tagHandlerVar);
out.print(".setParent(");
out.print(parent);
out.println(");");
}
}
// need to handle deferred values and methods
Node.JspAttribute[] attrs = n.getJspAttributes(); for (int i = 0; attrs != null && i < attrs.length; i++) {
String attrValue = evaluateAttribute(handlerInfo, attrs[i], n,
tagHandlerVar);
/** * Generates anonymous JspFragment inner class which is passed as an * argument to SimpleTag.setJspBody().
*/ privatevoid generateJspFragment(ChildInfoBase n, String tagHandlerVar) throws JasperException { // XXX - A possible optimization here would be to check to see // if the only child of the parent node is TemplateText. If so, // we know there won't be any parameters, etc, so we can // generate a low-overhead JspFragment that just echoes its // body. The implementation of this fragment can come from // the org.apache.jasper.runtime package as a support class.
FragmentHelperClass.Fragment fragment = fragmentHelperClass
.openFragment(n, methodNesting);
ServletWriter outSave = out;
out = fragment.getGenBuffer().getOut();
String tmpParent = parent;
parent = "_jspx_parent"; boolean isSimpleTagParentSave = isSimpleTagParent;
isSimpleTagParent = true; boolean tmpIsFragment = isFragment;
isFragment = true;
String pushBodyCountVarSave = pushBodyCountVar; if (pushBodyCountVar != null) { // Use a fixed name for push body count, to simplify code gen
pushBodyCountVar = "_jspx_push_body_count";
}
visitBody(n);
out = outSave;
parent = tmpParent;
isSimpleTagParent = isSimpleTagParentSave;
isFragment = tmpIsFragment;
pushBodyCountVar = pushBodyCountVarSave;
fragmentHelperClass.closeFragment(fragment, methodNesting); // XXX - Need to change pageContext to jspContext if // we're not in a place where pageContext is defined (e.g. // in a fragment or in a tag file.
out.print("new " + fragmentHelperClass.getClassName() + "( "
+ fragment.getId() + ", _jspx_page_context, "
+ tagHandlerVar + ", " + pushBodyCountVar + ")");
}
/** * Generate the code required to obtain the runtime value of the given * named attribute. * * @param n The named attribute node whose value is required * * @return The name of the temporary variable the result is stored in. * * @throws JasperException If an error
*/ public String generateNamedAttributeValue(Node.NamedAttribute n) throws JasperException {
String varName = n.getTemporaryVariableName();
// If the only body element for this named attribute node is // template text, we need not generate an extra call to // pushBody and popBody. Maybe we can further optimize // here by getting rid of the temporary variable, but in // reality it looks like javac does this for us.
Node.Nodes body = n.getBody(); if (body != null) { boolean templateTextOptimization = false; if (body.size() == 1) {
Node bodyElement = body.getNode(0); if (bodyElement instanceof Node.TemplateText) {
templateTextOptimization = true;
out.printil("java.lang.String "
+ varName
+ " = "
+ quote(bodyElement.getText()) + ";");
}
}
// XXX - Another possible optimization would be for // lone EL expressions (no need to pushBody here either).
if (!templateTextOptimization) {
out.printil("out = _jspx_page_context.pushBody();");
visitBody(n);
out.printil("java.lang.String " + varName + " = "
+ "((jakarta.servlet.jsp.tagext.BodyContent)"
+ "out).getString();");
out.printil("out = _jspx_page_context.popBody();");
}
} else { // Empty body must be treated as ""
out.printil("java.lang.String " + varName + " = \"\";");
}
return varName;
}
/** * Similar to generateNamedAttributeValue, but create a JspFragment * instead. * * @param n * The parent node of the named attribute * @param tagHandlerVar * The variable the tag handler is stored in, so the fragment * knows its parent tag. * @return The name of the temporary variable the fragment is stored in. * * @throws JasperException If an error occurs trying to generate the * fragment
*/ public String generateNamedAttributeJspFragment(Node.NamedAttribute n,
String tagHandlerVar) throws JasperException {
String varName = n.getTemporaryVariableName();
/** * Common part of postamble, shared by both servlets and tag files.
*/ privatevoid genCommonPostamble() { // Append any methods that were generated in the buffer. for (GenBuffer methodBuffer : methodsBuffered) {
methodBuffer.adjustJavaLines(out.getJavaLine() - 1);
out.printMultiLn(methodBuffer.toString());
}
// Append the helper class if (fragmentHelperClass.isUsed()) {
fragmentHelperClass.generatePostamble();
fragmentHelperClass.adjustJavaLines(out.getJavaLine() - 1);
out.printMultiLn(fragmentHelperClass.toString());
}
/* * Temporary hack. If a JSP page uses the "extends" attribute of the * page directive, the _jspInit() method of the generated servlet class * will not be called (it is only called for those generated servlets * that extend HttpJspBase, the default), causing the tag handler pools * not to be initialized and resulting in a NPE. The JSP spec needs to * clarify whether containers can override init() and destroy(). For * now, we just disable tag pooling for pages that use "extends".
*/ if (pageInfo.getExtends(false) == null || ctxt.getOptions().getPoolTagsWithExtends()) {
isPoolingEnabled = ctxt.getOptions().isPoolingEnabled();
} else {
isPoolingEnabled = false;
}
beanInfo = pageInfo.getBeanRepository();
varInfoNames = pageInfo.getVarInfoNames();
breakAtLF = ctxt.getOptions().getMappedFile(); if (isPoolingEnabled) {
tagHandlerPoolNames = new ArrayList<>();
} else {
tagHandlerPoolNames = null;
}
timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
timestampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
/** * The main entry for Generator. * * @param out * The servlet output writer * @param compiler * The compiler * @param page * The input page * * @throws JasperException If something goes wrong during generation
*/ publicstaticvoid generate(ServletWriter out, Compiler compiler,
Node.Nodes page) throws JasperException {
Generator gen = new Generator(out, compiler);
if (gen.isPoolingEnabled) {
gen.compileTagHandlerPoolList(page);
}
gen.generateCommentHeader(); if (gen.ctxt.isTagFile()) {
JasperTagInfo tagInfo = (JasperTagInfo) gen.ctxt.getTagInfo();
gen.generateTagHandlerPreamble(tagInfo, page);
privatevoid generateCommentHeader() {
out.println("/*");
out.println(" * Generated by the Jasper component of Apache Tomcat");
out.println(" * Version: " + ctxt.getServletContext().getServerInfo()); if (ctxt.getOptions().getGeneratedJavaAddTimestamp()) {
out.println(" * Generated at: " + timestampFormat.format(new Date()) + " UTC");
}
out.println(" * Note: The last modified time of this file was set to");
out.println(" * the last modified time of the source file after");
out.println(" * generation to assist with modification tracking.");
out.println(" */");
}
// Declare writer used for storing result of fragment/body invocation // if 'varReader' or 'var' attribute is specified
out.printil("private java.io.Writer _jspx_sout;");
// Class variable declarations
genPreambleClassVariableDeclarations();
generateSetJspContext(tagInfo);
// Tag-handler specific declarations
generateTagHandlerAttributes(tagInfo); if (tagInfo.hasDynamicAttributes()) {
generateSetDynamicAttribute();
}
// Methods here
genPreambleMethods();
// Now the doTag() method
out.printil("public void doTag() throws jakarta.servlet.jsp.JspException, java.io.IOException {");
if (ctxt.isPrototypeMode()) {
out.printil("}");
out.popIndent();
out.printil("}"); return;
}
out.pushIndent();
/* * According to the spec, 'pageContext' must not be made available as an * implicit object in tag files. Declare _jspx_page_context, so we can * share the code generator with JSPs.
*/
out.printil("jakarta.servlet.jsp.PageContext _jspx_page_context = (jakarta.servlet.jsp.PageContext)jspContext;");
// Close the doTag method
out.popIndent();
out.printil("}");
// Generated methods, helper classes, etc.
genCommonPostamble();
}
/** * Generates declarations for tag handler attributes, and defines the getter * and setter methods for each.
*/ privatevoid generateTagHandlerAttributes(TagInfo tagInfo) {
if (tagInfo.hasDynamicAttributes()) {
out.printil("private java.util.HashMap _jspx_dynamic_attrs = new java.util.HashMap();");
}
// setter method
out.printin("public void ");
out.print(toSetterMethodName(attrInfo.getName())); if (attrInfo.isFragment()) {
out.print("(jakarta.servlet.jsp.tagext.JspFragment ");
} else {
out.print("(");
out.print(JspUtil.toJavaSourceType(attrInfo.getTypeName()));
out.print(" ");
}
out.print(javaName);
out.println(") {");
out.pushIndent();
out.printin("this.");
out.print(javaName);
out.print(" = ");
out.print(javaName);
out.println(";"); // Tag files should also set jspContext attributes // Only called for tag files so always set the jspContext
out.printin("jspContext.setAttribute(\"");
out.print(attrInfo.getName());
out.print("\", ");
out.print(javaName);
out.println(");");
out.popIndent();
out.printil("}");
out.println();
}
}
/* * Generate setter for JspContext so we can create a wrapper and store both * the original and the wrapper. We need the wrapper to mask the page * context from the tag file and simulate a fresh page context. We need the * original to do things like sync AT_BEGIN and AT_END scripting variables.
*/ privatevoid generateSetJspContext(TagInfo tagInfo) {
// Determine if there are any aliases boolean aliasSeen = false;
TagVariableInfo[] tagVars = tagInfo.getTagVariableInfos(); for (TagVariableInfo var : tagVars) { // If a tag file uses a named attribute, the TagFileDirectiveVisitor // will ensure that an alias is configured. if (var.getNameFromAttribute() != null) {
aliasSeen = true; break;
}
}
switch (tagVar.getScope()) { case VariableInfo.NESTED: if (!nestedSeen) {
out.printil("_jspx_nested = new java.util.ArrayList();");
nestedSeen = true;
}
out.printin("_jspx_nested.add("); break;
case VariableInfo.AT_BEGIN: if (!atBeginSeen) {
out.printil("_jspx_at_begin = new java.util.ArrayList();");
atBeginSeen = true;
}
out.printin("_jspx_at_begin.add("); break;
case VariableInfo.AT_END: if (!atEndSeen) {
out.printil("_jspx_at_end = new java.util.ArrayList();");
atEndSeen = true;
}
out.printin("_jspx_at_end.add("); break;
} // switch
/* * Generates implementation of * jakarta.servlet.jsp.tagext.DynamicAttributes.setDynamicAttribute() method, * which saves each dynamic attribute that is passed in so that a scoped * variable can later be created for it.
*/ publicvoid generateSetDynamicAttribute() {
out.printil("public void setDynamicAttribute(java.lang.String uri, java.lang.String localName, java.lang.Object value) throws jakarta.servlet.jsp.JspException {");
out.pushIndent(); /* * According to the spec, only dynamic attributes with no uri are to be * present in the Map; all other dynamic attributes are ignored.
*/
out.printil("if (uri == null)");
out.pushIndent();
out.printil("_jspx_dynamic_attrs.put(localName, value);");
out.popIndent();
out.popIndent();
out.printil("}");
}
/* * Creates a page-scoped variable for each declared tag attribute. Also, if * the tag accepts dynamic attributes, a page-scoped variable is made * available for each dynamic attribute that was passed in.
*/ privatevoid generatePageScopedVariables(JasperTagInfo tagInfo) {
// "normal" attributes
TagAttributeInfo[] attrInfos = tagInfo.getAttributes(); boolean variableMapperVar = false; for (int i = 0; i < attrInfos.length; i++) {
String attrName = attrInfos[i].getName();
// handle assigning deferred vars to VariableMapper, storing // previous values under '_el_ve[i]' for later re-assignment if (attrInfos[i].isDeferredValue() || attrInfos[i].isDeferredMethod()) {
// we need to scope the modified VariableMapper for consistency and performance if (!variableMapperVar) {
out.printil("jakarta.el.VariableMapper _el_variablemapper = jspContext.getELContext().getVariableMapper();");
variableMapperVar = true;
}
// Expose the Map containing dynamic attributes as a page-scoped var if (tagInfo.hasDynamicAttributes()) {
out.printin("_jspx_page_context.setAttribute(\"");
out.print(tagInfo.getDynamicAttributesMapName());
out.print("\", _jspx_dynamic_attrs);");
}
}
/* * Generates the getter method for the given attribute name.
*/ private String toGetterMethod(String attrName) { char[] attrChars = attrName.toCharArray();
attrChars[0] = Character.toUpperCase(attrChars[0]); return"get" + new String(attrChars) + "()";
}
/* * Generates the setter method name for the given attribute name.
*/ private String toSetterMethodName(String attrName) { char[] attrChars = attrName.toCharArray();
attrChars[0] = Character.toUpperCase(attrChars[0]); return"set" + new String(attrChars);
}
/** * Class storing the result of introspecting a custom tag handler.
*/ privatestaticclass TagHandlerInfo {
private Map<String, Method> methodMaps;
private Map<String, Class<?>> propertyEditorMaps;
privateClass<?> tagHandlerClass;
/** * Constructor. * * @param n * The custom tag whose tag handler class is to be * introspected * @param tagHandlerClass * Tag handler class * @param err * Error dispatcher
*/
TagHandlerInfo(Node n, Class<?> tagHandlerClass,
ErrorDispatcher err) throws JasperException { this.tagHandlerClass = tagHandlerClass; this.methodMaps = new HashMap<>(); this.propertyEditorMaps = new HashMap<>();
try {
BeanInfo tagClassInfo = Introspector.getBeanInfo(tagHandlerClass);
PropertyDescriptor[] pd = tagClassInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : pd) { /* * FIXME: should probably be checking for things like * pageContext, bodyContent, and parent here -akv
*/ if (propertyDescriptor.getWriteMethod() != null) {
methodMaps.put(propertyDescriptor.getName(), propertyDescriptor.getWriteMethod());
} if (propertyDescriptor.getPropertyEditorClass() != null) {
propertyEditorMaps.put(propertyDescriptor.getName(), propertyDescriptor
.getPropertyEditorClass());
}
}
} catch (IntrospectionException ie) { // Likely unreachable code // When last checked (May 2021), current versions of Java only // throw IntrospectionException for the 2-arg version of // getBeanInfo if the stop class is not a super class of the // bean class. That does not apply here.
err.jspError(n, ie, "jsp.error.introspect.taghandler", tagHandlerClass.getName());
}
}
public Method getSetterMethod(String attrName) { return methodMaps.get(attrName);
}
/** * A class for generating codes to a buffer. Included here are some support * for tracking source to Java lines mapping.
*/ privatestaticclass GenBuffer {
/* * For a CustomTag, the codes that are generated at the beginning of the * tag may not be in the same buffer as those for the body of the tag. * Two fields are used here to keep this straight. For codes that do not * corresponds to any JSP lines, they should be null.
*/ private Node node;
private Node.Nodes body;
private java.io.CharArrayWriter charWriter;
protected ServletWriter out;
GenBuffer() { this(null, null);
}
GenBuffer(Node n, Node.Nodes b) {
node = n;
body = b; if (body != null) {
body.setGeneratedInBuffer(true);
}
charWriter = new java.io.CharArrayWriter();
out = new ServletWriter(new java.io.PrintWriter(charWriter));
}
public ServletWriter getOut() { return out;
}
@Override public String toString() { return charWriter.toString();
}
/** * Adjust the Java Lines. This is necessary because the Java lines * stored with the nodes are relative the beginning of this buffer and * need to be adjusted when this buffer is inserted into the source. * * @param offset The offset to apply to the start line and end line of * and Java lines of nodes in this buffer
*/ publicvoid adjustJavaLines(finalint offset) {
if (node != null) {
adjustJavaLine(node, offset);
}
if (body != null) { try {
body.visit(new Node.Visitor() {
@Override publicvoid visit(Node.CustomTag n) throws JasperException {
Node.Nodes b = n.getBody(); if (b != null && !b.isGeneratedInBuffer()) { // Don't adjust lines for the nested tags that // are also generated in buffers, because the // adjustments will be done elsewhere.
b.visit(this);
}
}
});
} catch (JasperException ex) { // Ignore
}
}
}
privatestaticvoid adjustJavaLine(Node n, int offset) { if (n.getBeginJavaLine() > 0) {
n.setBeginJavaLine(n.getBeginJavaLine() + offset);
n.setEndJavaLine(n.getEndJavaLine() + offset);
}
}
}
/** * Keeps track of the generated Fragment Helper Class
*/ privatestaticclass FragmentHelperClass {
privatestaticclass Fragment { private GenBuffer genBuffer;
public String getClassName() { returnthis.className;
}
publicboolean isUsed() { returnthis.used;
}
publicvoid generatePreamble() {
ServletWriter out = this.classBuffer.getOut();
out.println();
out.pushIndent(); // Note: cannot be static, as we need to reference things like // _jspx_meth_*
out.printil("private class " + className);
out.printil(" extends "
+ "org.apache.jasper.runtime.JspFragmentHelper");
out.printil("{");
out.pushIndent();
out.printil("private jakarta.servlet.jsp.tagext.JspTag _jspx_parent;");
out.printil("private int[] _jspx_push_body_count;");
out.println();
out.printil("public " + className
+ "( int discriminator, jakarta.servlet.jsp.JspContext jspContext, "
+ "jakarta.servlet.jsp.tagext.JspTag _jspx_parent, "
+ "int[] _jspx_push_body_count ) {");
out.pushIndent();
out.printil("super( discriminator, jspContext, _jspx_parent );");
out.printil("this._jspx_parent = _jspx_parent;");
out.printil("this._jspx_push_body_count = _jspx_push_body_count;");
out.popIndent();
out.printil("}");
}
public Fragment openFragment(ChildInfoBase parent, int methodNesting) {
Fragment result = new Fragment(fragments.size(), parent);
fragments.add(result); this.used = true;
parent.setInnerClassName(className);
ServletWriter out = result.getGenBuffer().getOut();
out.pushIndent();
out.pushIndent(); // XXX - Returns boolean because if a tag is invoked from // within this fragment, the Generator sometimes might // generate code like "return true". This is ignored for now, // meaning only the fragment is skipped. The JSR-152 // expert group is currently discussing what to do in this case. // See comment in closeFragment() if (methodNesting > 0) {
out.printin("public boolean invoke");
} else {
out.printin("public void invoke");
}
out.println(result.getId() + "( " + "jakarta.servlet.jsp.JspWriter out ) ");
out.pushIndent(); // Note: Throwable required because methods like _jspx_meth_* // throw Throwable.
out.printil("throws java.lang.Throwable");
out.popIndent();
out.printil("{");
out.pushIndent();
generateLocalVariables(out, parent);
return result;
}
publicvoid closeFragment(Fragment fragment, int methodNesting) {
ServletWriter out = fragment.getGenBuffer().getOut(); // XXX - See comment in openFragment() if (methodNesting > 0) {
out.printil("return false;");
} else {
out.printil("return;");
}
out.popIndent();
out.printil("}");
}
publicvoid generatePostamble() {
ServletWriter out = this.classBuffer.getOut(); // Generate all fragment methods: for (Fragment fragment : fragments) {
fragment.getGenBuffer().adjustJavaLines(out.getJavaLine() - 1);
out.printMultiLn(fragment.getGenBuffer().toString());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.