/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.interpreter; import static org.apache.el.parser.ELParserTreeConstants.JJTAND; import static org.apache.el.parser.ELParserTreeConstants.JJTBRACKETSUFFIX; import static org.apache.el.parser.ELParserTreeConstants.JJTCHOICE; import static org.apache.el.parser.ELParserTreeConstants.JJTCOMPOSITEEXPRESSION; import static org.apache.el.parser.ELParserTreeConstants.JJTDEFERREDEXPRESSION; import static org.apache.el.parser.ELParserTreeConstants.JJTDIV; import static org.apache.el.parser.ELParserTreeConstants.JJTDOTSUFFIX; import static org.apache.el.parser.ELParserTreeConstants.JJTDYNAMICEXPRESSION; import static org.apache.el.parser.ELParserTreeConstants.JJTEMPTY; import static org.apache.el.parser.ELParserTreeConstants.JJTEQUAL; import static org.apache.el.parser.ELParserTreeConstants.JJTFALSE; import static org.apache.el.parser.ELParserTreeConstants.JJTFLOATINGPOINT; import static org.apache.el.parser.ELParserTreeConstants.JJTFUNCTION; import static org.apache.el.parser.ELParserTreeConstants.JJTGREATERTHAN; import static org.apache.el.parser.ELParserTreeConstants.JJTGREATERTHANEQUAL; import static org.apache.el.parser.ELParserTreeConstants.JJTIDENTIFIER; import static org.apache.el.parser.ELParserTreeConstants.JJTINTEGER; import static org.apache.el.parser.ELParserTreeConstants.JJTLESSTHAN; import static org.apache.el.parser.ELParserTreeConstants.JJTLESSTHANEQUAL; import static org.apache.el.parser.ELParserTreeConstants.JJTLITERALEXPRESSION; import static org.apache.el.parser.ELParserTreeConstants.JJTMETHODPARAMETERS; import static org.apache.el.parser.ELParserTreeConstants.JJTMINUS; import static org.apache.el.parser.ELParserTreeConstants.JJTMOD; import static org.apache.el.parser.ELParserTreeConstants.JJTMULT; import static org.apache.el.parser.ELParserTreeConstants.JJTNEGATIVE; import static org.apache.el.parser.ELParserTreeConstants.JJTNOT; import static org.apache.el.parser.ELParserTreeConstants.JJTNOTEQUAL; import static org.apache.el.parser.ELParserTreeConstants.JJTNULL; import static org.apache.el.parser.ELParserTreeConstants.JJTOR; import static org.apache.el.parser.ELParserTreeConstants.JJTPLUS; import static org.apache.el.parser.ELParserTreeConstants.JJTSTRING; import static org.apache.el.parser.ELParserTreeConstants.JJTTRUE; import static org.apache.el.parser.ELParserTreeConstants.JJTVALUE; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import javax.servlet.jsp.tagext.TagAttributeInfo; import javax.servlet.jsp.tagext.TagInfo; import org.apache.el.lang.ELArithmetic; import org.apache.el.parser.AstCompositeExpression; import org.apache.el.parser.Node; import org.apache.el.parser.SimpleNode; import org.apache.jasper.JspCompilationContext; import org.apache.jasper.compiler.JspUtil; /** * EL Code generation * * 1.Simple EL, only contains one part of expression, for example ${elemId}. This kind of EL will be generated as * * this.getJspContext().findAttribute("elemId") * * 2.EL with two parts of expression and type of first part is specified by attribute in Tag File. * For example, ${model.location} . "model" is specified on the top of tag file, * * <%@ attribute name="model" required="true" type="org.apache.jasper.model.results.ItemModel" %>. * * It is generated as (getModel() != null ? getModel().getLocation() : null) * * 3.EL with logic or arithmetic and the value part can be generated. * For example: ${(intlExpansion eq 'true' && not empty model.location) || sortType==7} * * It is generated as * (org.apache.el.lang.ELSupport.equals(getIntlExpansion(), "true")&&(!org.apache.jasper.runtime.ELRuntimeUtil.isEmpty((getModel() != null ? getModel().getLocation() : null)))) * * @author Sheldon Shao */ public class ELCodegen { public static String generate(JspCompilationContext context, boolean isTagFile, Class expectedType, Node node, boolean coerceType) { if (node instanceof SimpleNode) { SimpleNode sn = (SimpleNode) node; int nodeId = sn.getId(); String first, second, condition; String code = null; Class returnType = Object.class; switch (nodeId) { case JJTCOMPOSITEEXPRESSION: return genAstCompositeExpression(context, isTagFile, expectedType, (AstCompositeExpression) sn); case JJTLITERALEXPRESSION: case JJTDOTSUFFIX: code = quote(sn.getImage()); returnType = String.class; break; case JJTDEFERREDEXPRESSION: case JJTDYNAMICEXPRESSION: case JJTBRACKETSUFFIX: return generate(context, isTagFile, expectedType, node.jjtGetChild(0), coerceType); case JJTCHOICE: condition = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(0)); first = generate(context, isTagFile, expectedType, node.jjtGetChild(1), false); second = generate(context, isTagFile, expectedType, node.jjtGetChild(2), false); code = "(" + condition + "?" + first + ":" + second + ")"; break; case JJTOR: first = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(0)); second = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(1)); code = "(" + first + "||" + second + ")"; returnType = Boolean.TYPE; break; case JJTAND: first = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(0)); second = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(1)); code = "(" + first + "&&" + second + ")"; returnType = Boolean.TYPE; break; case JJTEQUAL: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELSupport.equals(" + first + ", " + second + ")"; returnType = Boolean.TYPE; break; case JJTNOTEQUAL: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "(!org.apache.el.lang.ELSupport.equals(" + first + ", " + second + "))"; returnType = Boolean.TYPE; break; case JJTLESSTHAN: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.lessThan(" + first + ", " + second + ")"; returnType = Boolean.TYPE; break; case JJTGREATERTHAN: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.greaterThan(" + first + ", " + second + ")"; returnType = Boolean.TYPE; break; case JJTLESSTHANEQUAL: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.lessThanEqual(" + first + ", " + second + ")"; returnType = Boolean.TYPE; break; case JJTGREATERTHANEQUAL: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.greaterThanEqual(" + first + ", " + second + ")"; returnType = Boolean.TYPE; break; case JJTPLUS: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELArithmetic.add(" + first + ", " + second + ")"; break; case JJTMINUS: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELArithmetic.subtract(" + first + ", " + second + ")"; break; case JJTMULT: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELArithmetic.multiply(" + first + ", " + second + ")"; returnType = Number.class; break; case JJTDIV: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELArithmetic.divide(" + first + ", " + second + ")"; returnType = Number.class; break; case JJTMOD: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); second = generate(context, isTagFile, Object.class, node.jjtGetChild(1), false); code = "org.apache.el.lang.ELArithmetic.mod(" + first + ", " + second + ")"; returnType = Number.class; break; case JJTNEGATIVE: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.negative(" + first + ")"; returnType = Number.class; break; case JJTNOT: first = generateBoolean(context, isTagFile, Boolean.class, node.jjtGetChild(0)); code = "(!" + first + ")"; returnType = Boolean.TYPE; break; case JJTEMPTY: first = generate(context, isTagFile, Object.class, node.jjtGetChild(0), false); code = "org.apache.jasper.runtime.ELRuntimeUtil.isEmpty(" + first + ")"; returnType = Boolean.TYPE; break; case JJTMETHODPARAMETERS: case JJTFUNCTION: throw new IllegalStateException("Unsupported"); case JJTTRUE: code = "true"; returnType = Boolean.TYPE; break; case JJTFALSE: code = "false"; returnType = Boolean.TYPE; break; case JJTFLOATINGPOINT: first = node.getImage(); code = "org.apache.jasper.runtime.ELRuntimeUtil.toFloatingPoint(" + first + ")"; returnType = Number.class; break; case JJTINTEGER: first = node.getImage(); if (first != null && first.length() < 10) { boolean simpleNumber = true; char ch; for (int i = 0, len = first.length(); i < len; i++) { ch = first.charAt(i); if (!(ch >= '0' && ch <= '9')) { simpleNumber = false; break; } } if (simpleNumber) { return first; } } code = "org.apache.jasper.runtime.ELRuntimeUtil.toInteger(" + first + ")"; returnType = Number.class; break; case JJTSTRING: String str = node.getImage(); if (str.startsWith("'") && str.endsWith("'")) { code = quote(str.substring(1, str.length() - 1)); } else if (str.startsWith("\"") && str.endsWith("\"")) { code = quote(str.substring(1, str.length() - 1)); } else { code = str; } returnType = String.class; break; case JJTNULL: code = "null"; returnType = null; break; case JJTIDENTIFIER: return genSingleNode(context, isTagFile, node.getImage(), expectedType, false, false); case JJTVALUE: int count = node.jjtGetNumChildren(); if (count > 0) { first = node.jjtGetChild(0).getImage(); if (count == 1) { return genSingleNode(context, isTagFile, first, expectedType, false, coerceType); } else if (count == 2 && isTagFile) { second = node.jjtGetChild(1).getImage(); if (second != null) { return genCoupleNodes(context, isTagFile, first, second, null, expectedType, false, coerceType); } } } default: throw new IllegalStateException("Unsupported"); } if (coerceType) { return withCoerceType(expectedType, code, returnType); } return code; } throw new IllegalStateException("Unsupported"); } private static String withCoerceType(Class expectedType, String code, Class returnType) { if (expectedType == null) { return code; } String targetType = ELCodegen.getTargetType(expectedType); if (returnType == expectedType) { return code; } if (returnType == Boolean.TYPE && expectedType == Boolean.class) { return code; } else if (expectedType == String.class) { if (returnType == Boolean.TYPE) { return "Boolean.toString(" + code + ")"; } else { return "org.apache.el.lang.ELSupport.coerceToString(" + code + ")"; } } boolean isNumber = false, isEnum = false; if (ELArithmetic.isNumberType(expectedType)) { isNumber = true; } else if (Character.class.equals(expectedType) || Character.TYPE == expectedType) { return "org.apache.el.lang.ELSupport.coerceToCharacter(" + code + ")"; } else if (expectedType.isEnum()) { isEnum = true; } StringBuilder call = new StringBuilder("(" + targetType + ") "); if (expectedType != null && expectedType != Object.class) { if (isNumber) { call.append("org.apache.el.lang.ELSupport.coerceToNumber("); } else if (isEnum) { call.append("org.apache.el.lang.ELSupport.coerceToEnum("); } else { call.append("org.apache.jasper.runtime.ELRuntimeUtil.toType("); } } call.append(code); if (expectedType != null && expectedType != Object.class) { call.append(", ").append(targetType).append(".class)"); } return call.toString(); } public static String genCoerceType(Class expectedType, String code, Node node) { if (Boolean.class.equals(expectedType) || Boolean.TYPE == expectedType) { if (node != null && node instanceof SimpleNode) { int id = ((SimpleNode) node).getId(); switch (id) { case JJTEMPTY: case JJTOR: case JJTAND: case JJTNOT: case JJTEQUAL: case JJTNOTEQUAL: case JJTLESSTHAN: case JJTGREATERTHAN: case JJTLESSTHANEQUAL: case JJTGREATERTHANEQUAL: return code; } } return "org.apache.el.lang.ELSupport.coerceToBoolean(" + code + ")"; } else if (expectedType == null || Object.class.equals(expectedType)) { return code; } if (String.class.equals(expectedType)) { if (node != null && node instanceof SimpleNode) { int id = ((SimpleNode) node).getId(); switch (id) { case JJTCOMPOSITEEXPRESSION: case JJTLITERALEXPRESSION: case JJTDOTSUFFIX: case JJTSTRING: return code; case JJTDEFERREDEXPRESSION: case JJTDYNAMICEXPRESSION: case JJTBRACKETSUFFIX: case JJTCHOICE: case JJTPLUS: case JJTMINUS: case JJTMULT: case JJTDIV: case JJTMOD: case JJTNEGATIVE: case JJTFLOATINGPOINT: case JJTINTEGER: return "org.apache.el.lang.ELSupport.coerceToString(" + code + ")"; case JJTOR: case JJTAND: case JJTEQUAL: case JJTNOTEQUAL: case JJTLESSTHAN: case JJTGREATERTHAN: case JJTLESSTHANEQUAL: case JJTGREATERTHANEQUAL: case JJTNOT: case JJTEMPTY: case JJTTRUE: case JJTFALSE: return "Boolean.toString(" + code + ")"; case JJTNULL: return ""; } } return "org.apache.el.lang.ELSupport.coerceToString(" + code + ")"; } boolean isNumber = false, isEnum = false; if (ELArithmetic.isNumberType(expectedType)) { isNumber = true; } else if (Character.class.equals(expectedType) || Character.TYPE == expectedType) { return "org.apache.el.lang.ELSupport.coerceToCharacter(" + code + ")"; } else if (expectedType.isEnum()) { isEnum = true; } /* * * 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 = ELCodegen.getTargetType(expectedType); StringBuilder call = new StringBuilder("(" + targetType + ") "); if (expectedType != null && expectedType != Object.class) { if (isNumber) { call.append("org.apache.el.lang.ELSupport.coerceToNumber("); } else if (isEnum) { call.append("org.apache.el.lang.ELSupport.coerceToEnum("); } else { call.append("org.apache.jasper.runtime.ELRuntimeUtil.toType("); } } call.append(code); if (expectedType != null && expectedType != Object.class) { call.append(", ").append(targetType).append(".class)"); } return call.toString(); } public static String generateBoolean(JspCompilationContext context, boolean isTagFile, Class expectedType, Node node) { String code = generate(context, isTagFile, null, node, false); if (node != null && node instanceof SimpleNode) { int id = ((SimpleNode) node).getId(); switch (id) { case JJTEMPTY: case JJTOR: case JJTAND: case JJTNOT: case JJTEQUAL: case JJTNOTEQUAL: case JJTLESSTHAN: case JJTGREATERTHAN: case JJTLESSTHANEQUAL: case JJTGREATERTHANEQUAL: return code; } } return "org.apache.el.lang.ELSupport.coerceToBoolean(" + code + ")"; } /** * @param s * the input string * @return quoted and escaped string, per Java rule */ protected static String quote(String s) { if (s == null) return "null"; return '"' + escape(s) + '"'; } /** * @param s * the input string * @return escaped string, per Java rule */ protected static String escape(String s) { if (s == null) return ""; StringBuilder b = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '"') b.append('\\').append('"'); else if (c == '\\') b.append('\\').append('\\'); else if (c == '\n') b.append('\\').append('n'); else if (c == '\r') b.append('\\').append('r'); else b.append(c); } return b.toString(); } private static String genAstCompositeExpression( JspCompilationContext context, boolean isTagFile, Class expectedType, AstCompositeExpression ace) { StringBuilder sb = new StringBuilder(16); sb.append("(\"\""); Node node = null; int count = ace.jjtGetNumChildren(); if (count > 0) { for (int i = 0; i < count; i++) { node = ace.jjtGetChild(i); if (node != null) { String code = generate(context, isTagFile, String.class, node, false); sb.append('+'); sb.append(code); } } } sb.append(")"); return sb.toString(); } public static String genSingleNode(JspCompilationContext context, boolean isTagFile, String node, Class expectedType, boolean xmlEscape, boolean coerceType) { /* * Determine which context object to use. */ String jspCtxt = null; if (isTagFile) { jspCtxt = "this.getJspContext()"; } else { jspCtxt = "_jspx_page_context"; } String code = jspCtxt + ".findAttribute(" + quote(node) + ")"; return coerceType ? genCoerceType(expectedType, code, null) : code; } protected static String[] getTargetTypeAndPrimitiveConverterMethod( Class expectedType) { if (expectedType == null) { expectedType = Object.class; } /* * 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.getCanonicalName(); 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"; } } /* * 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 = JspUtil.toJavaSourceType(targetType); return new String[] { targetType, primitiveConverterMethod }; } protected static String getTargetType(Class expectedType) { if (expectedType == null) { expectedType = Object.class; } /* * 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.getCanonicalName(); if (expectedType.isPrimitive()) { if (expectedType.equals(Boolean.TYPE)) { targetType = Boolean.class.getName(); } else if (expectedType.equals(Byte.TYPE)) { targetType = Byte.class.getName(); } else if (expectedType.equals(Character.TYPE)) { targetType = Character.class.getName(); } else if (expectedType.equals(Short.TYPE)) { targetType = Short.class.getName(); } else if (expectedType.equals(Integer.TYPE)) { targetType = Integer.class.getName(); } else if (expectedType.equals(Long.TYPE)) { targetType = Long.class.getName(); } else if (expectedType.equals(Float.TYPE)) { targetType = Float.class.getName(); } else if (expectedType.equals(Double.TYPE)) { targetType = Double.class.getName(); } } return JspUtil.toJavaSourceType(targetType); } static String genCoupleNodes(Class type, String firstNode, String secondNode, Class expectedType, boolean xmlEscape, boolean coerceType) { StringBuilder call = new StringBuilder(); call.append("("); call.append(toGetterMethod(firstNode)).append("() != null ? ") .append(toGetterMethod(firstNode)).append("()"); if (Map.class.isAssignableFrom(type)) { call.append(".get(").append(quote(secondNode)).append(")"); } else { String methodName = toGetterMethod(secondNode); Method method = null; try { method = type.getMethod(methodName); } catch (SecurityException e) { } catch (NoSuchMethodException e) { } if (method == null) { if (expectedType == Boolean.TYPE || Boolean.class.equals(expectedType)) { methodName = toIsMethod(secondNode); try { method = type.getMethod(methodName); } catch (SecurityException e1) { return null; } catch (NoSuchMethodException e1) { return null; } } else { return null; } } if (method != null) { coerceType = coerceType && expectedType != null && !expectedType.isPrimitive() && method.getReturnType().getName() .equals(expectedType.getName()) ? false : coerceType; call.append(".").append(methodName).append("()"); } else { return null; } } call.append(" : null)"); return coerceType ? ELCodegen.genCoerceType(expectedType, call.toString(), null) : call.toString(); } /* * Generates the getter method for the given attribute name. */ protected static String toGetterMethod(String attrName) { char[] attrChars = attrName.toCharArray(); attrChars[0] = Character.toUpperCase(attrChars[0]); return "get" + new String(attrChars); } protected static String toIsMethod(String attrName) { char[] attrChars = attrName.toCharArray(); attrChars[0] = Character.toUpperCase(attrChars[0]); return "is" + new String(attrChars); } private static final String[] SCOPE_NAMES = new String[] { "applicationScope", "cookie", "header", "headerValues", "initParam", "pageContext", "pageScope", "param", "paramValues", "requestScope", "sessionScope" }; protected static boolean isScope(String node) { return Arrays.binarySearch(SCOPE_NAMES, node) >= 0; } public static String genCoupleNodes(JspCompilationContext context, boolean isTagFile, String firstNode, String secondNode, String expression, Class expectedType, boolean xmlEscape, boolean coerceType) { if (isScope(firstNode)) { if (expression == null) { throw new IllegalStateException("Unsupported"); } return JspUtil.interpreterCall(isTagFile, expression, expectedType, null, xmlEscape); } TagInfo tagInfo = context.getTagInfo(); ClassLoader loader = context.getClassLoader(); TagAttributeInfo attr = getAttribute(tagInfo, firstNode); if (attr != null && attr.getTypeName() != null) { Class t; try { t = JspUtil.toClass(attr.getTypeName(), loader); } catch (ClassNotFoundException e) { if (expression == null) { throw new IllegalStateException("Unsupported"); } return JspUtil.interpreterCall(isTagFile, expression, expectedType, null, xmlEscape); } String code = genCoupleNodes(t, firstNode, secondNode, expectedType, xmlEscape, coerceType); if (code != null) { return code; } else { if (expression == null) { throw new IllegalStateException("Unsupported"); } return JspUtil.interpreterCall(isTagFile, expression, expectedType, null, xmlEscape); } } if (expression == null) { throw new IllegalStateException("Unsupported"); } return JspUtil.interpreterCall(isTagFile, expression, expectedType, null, xmlEscape); } private static TagAttributeInfo getAttribute(TagInfo tagInfo, String firstNode) { TagAttributeInfo[] attrs = tagInfo.getAttributes(); if (attrs != null && attrs.length > 0) { for (TagAttributeInfo attr : attrs) { if (firstNode.equals(attr.getName())) { return attr; } } } return null; } }