2.4 Operators
JSTL offers a small set of operators, listed in Table 2.1.
Table 2.1 Expression Language Operators
Type |
Operators |
Arithmetic |
+ - * / (div) % (mod) |
Grouping |
() |
Identifier Access |
. [] |
Logical |
&& (and) || (or) ! (not) empty |
Relational |
== (eq) != (ne) < (lt) > (gt) <= (le) >= (ge) |
Unary |
- |
You need to know three things about the operators in Table 2.1. First, you need to know the operators' syntax; for example, the + operator is used like this: A + B. That material is not covered here because you use EL expressions just like you use their Java equivalents. The second thing you need to know is operator precedence, so you can deduce that 1 + 3 * 5 is 16 but (1 + 3) * 5 is 20. Operator precedence is discussed in "Operator Precedence" on page 45. The third thing you need to know about the operators listed in Table 2.1 is what data types they prefer and how the EL performs type coercion. The former is briefly discussed here and the latter is discussed in "Type Coercion" on page 62.
All of the binary arithmetic operations prefer Double values and all of them will resolve to 0 if either of their operands is null. The operators + - * % will try to coerce their operands to Long values if they cannot be coerced to Double.
The grouping operators, which are parentheses, can be used to force operator precedence, as discussed above. The identifier access operators are discussed in "The . and [] Operators" on page 45, so that discussion is not repeated here.
The relational operators all have textual equivalents; for example, either == or eq will suffice for the equality operator. Those equivalents are provided for XML generation. Like binary arithmetic operations, all of the relational operators prefer Double values but will make do with Long values if the operands cannot be converted to Double.
The logical operators prefer to work with Boolean operands. You can use the empty operator to see if a value is either null or an empty string (""). That operator comes in handy when you are interpreting request parameters.
Operator Precedence
The precedence for EL operators is listed below:
- [] .
- ()
- - (unary) not ! empty
- * / div % mod
- + - (binary)
- < > <= >= lt gt le ge
- == != eq ne
- && and
- || or =
The operators are listed above from left to right and top to bottom according to precedence; for example, the [] operator has precedence over the . operator, and the modulus (% or mod) operator, which represents a division remainder, has precedence over the logical operators.
The . and [] Operators
The JSTL expression language provides two operators. and []that let you access scoped variables and their properties. The . operator is similar to the Java . operator, but instead of invoking methods, you access bean properties; for example, if you have a Name bean stored in a scoped variable named name and that bean contains firstName and lastName properties, you can access those properties like this:
First Name: <c:out value='${name.firstName}'/> Last Name: <c:out value='${name.lastName}'/>
Assuming that there is a bean named name that has readable properties firstName and lastName in one of the four JSP scopesmeaning methods named getFirstName and getLastNamethe preceding code fragment will display those properties.
You can also use the [] operator to access bean properties; for example, the preceding code fragment could be rewritten like this:
First Name: <c:out value='${name["firstName"]}'/> Last Name: <c:out value='${name["lastName"]}'/>
The [] operator is a generalization of the . operator, which is why the two previous code fragments are equivalent, but the [] operator lets you specify a computed value. "A Closer Look at the [] Operator" on page 56 takes a closer look at how the [] operator works.
You can also use the[] operator to access objects stored in maps, lists, and arrays; for example, the following code fragment accesses the first object in an array:
<% String[] array = { "1", "2", "3" }; pageContext.setAttribute("array", array); %> <c:out value='${array[0]}'/>
The preceding code fragment creates an array of strings and stores it in page scope with a scriptlet. Subsequently, the <c:out> action accesses the first item in the array with ${array[0]}.
The following sections"Accessing JavaBeans Components" and "Accessing Objects Stored in Arrays, Lists, and Maps" on page 52explore in greater detail the use of the . and [] operators to access bean properties and objects stored in collections.
Accessing JavaBeans Components
This section shows you how to use the . and [] operators to access bean properties, including nested beans. Listing 2.1, Listing 2.2, and Listing 2.3 list the implementation of three beans: Name, Address, and UserProfile.
Listing 2.1 WEB-INF/classes/beans/Name.java
package beans; public class Name { private String firstName, lastName; // JavaBean accessors for first name public void setFirstName(String ´firstName) { this.firstName = firstName; } public String getFirstName() { return firstName; } // JavaBean accessors for last name public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName() { return lastName; } }
Listing 2.2 WEB-INF/classes/beans/Address.java
package beans; public class Address { private String streetAddress, city, state; private int zip; // JavaBean accessors for street address public void setStreetAddress(String streetAddress) { this.streetAddress = streetAddress; } public String getStreetAddress() { return streetAddress; } // JavaBean accessors for city public void setCity(String city) { this.city = city; } public String getCity() { return city; } // JavaBean accessors for state public void setState(String state) { this.state = state; } public String getState() { return state; } // JavaBean accessors for zip public void setZip(int zip) { this.zip = zip; } public int getZip() { return zip; } }
Listing 2.3 WEB-INF/classes/beans/UserProfile.java
package beans; public class UserProfile { private Name name; private Address address; // JavaBean accessors for name public void setName(Name name) { this.name = name; } public Name getName() { return name; } // JavaBean accessors for address public void setAddress(Address address) { this.address = address; } public Address getAddress() { return address; } }
The preceding beans are simple JavaBean components. The Name bean has two properties: firstName and lastName. The Address bean has four properties: streetAddress, city, state, and zip. The UserProfile bean has two properties: name and address. UserProfile beans contain references to Name and Address beans.
Figure 21 shows a JSP page that creates a user profile and accesses its properties with EL expressions.
Figure 21 Accessing Beans with the Expression Language
The JSP page shown in Figure 21 is listed in Listing 2.4.
Listing 2.4 Accessing JavaBean Properties
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Accessing Beans with the EL</title> </head> <body> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%-- Create a Name bean and store it in page scope --%> <jsp:useBean id='name' class='beans.Name'> <%-- Set properties with strings --%> <jsp:setProperty name='name' property='firstName' value='Claude'/> <jsp:setProperty name='name' property='lastName' value='Loubier'/> </jsp:useBean> <%-- Create an Address bean and store it in page scope --%> <jsp:useBean id='address' class='beans.Address'> <%-- Set properties with strings --%> <jsp:setProperty name='address' property='streetAddress' value='119342 North Maison'/> <jsp:setProperty name='address' property='city' value='Buffalo'/> <jsp:setProperty name='address' property='state' value='New York'/> <jsp:setProperty name='address' property='zip' value='14214'/> </jsp:useBean> <%-- Create a UserProfile bean and store it in page scope --%> <jsp:useBean id='profile' class='beans.UserProfile'> <%-- Set properties with the name bean and address bean stored in page scope --%> <jsp:setProperty name='profile' property='name' value='<%= (beans.Name) pageContext.getAttribute("name") %>'/> <jsp:setProperty name='profile' property='address' value='<%= (beans.Address) pageContext.getAttribute("address") %>'/> </jsp:useBean> <%-- Show profile information --%> Profile for <%-- Access the name bean's firstName property directly, without specifying scope --%> <c:out value='${name["firstName"]}'/> <%-- Access the name bean's lastName property through the profile bean by explicitly specifying scope --%> <c:out value='${pageScope.profile.name.lastName}'/>: <p> <table> <tr> <%-- Access the UserProfile bean's properties without explicitly specifying scope --%> <td>First Name:</td> <td><c:out value='${profile["name"].firstName}'/></td> </tr><tr> <td>Last Name: <td><c:out value='${profile.name["lastName"]}'/></td> </tr><tr> <td>Street Address: <td><c:out value='${profile.address.streetAddress}'/> </td> </tr><tr> <td>City: <td><c:out value='${profile.address.city}'/></td> </tr><tr> <td>State: <td><c:out value='${profile.address.state}'/></td> </tr><tr> <td>Zip Code: <td><c:out value='${profile.address.zip}'/></td> </tr> </table> </body> </html>
The preceding JSP page creates three beans: a name bean, an address bean, and a user profile bean; the name and address beans are used to create the user profile. All three beans are stored in page scope.
The JSP page listed in Listing 2.4 uses EL expressions to access properties of the user profile. First, the JSP page accesses the name bean's firstName property with the expression ${name["firstName"]}, which is equivalent to this expression: ${name.firstName}.
Next, the JSP page accesses the name bean's lastName property with this expression: ${pageScope.profile.name.lastName}. The expression starts with the pageScope identifier, which is an implicit object that provides access to all page-scoped attributes.8 The user profile beannamed profilethat exists in page scope is accessed by name with an identifier, and its enclosed name bean is also accessed with an identifier. Finally, the lastName property of that name bean is accessed with another identifier.
The rest of the JSP page accesses the profile bean's properties by using the . and [] operators. Remember that the . and [] operators are interchangeable when accessing bean properties, so the expression ${profile["name"]. firstName} is equivalent to ${profile.name.firstName} and ${profile.name["lastName"]} is equivalent to ${profile.name.lastName}.
Now that we've seen how to access bean properties, let's see how to access objects stored in arrays, lists, and maps.
Accessing Objects Stored in Arrays, Lists, and Maps
In "Accessing JavaBeans Components" on page 46 we discussed a Web application that created user profiles and accessed their properties, all in a single JSP page. For a change of pace, this section discusses a Web application that creates user profiles in a servlet and accesses their properties in a JSP page.9 Figure 22 shows the Web application's JSP page.
Figure 22 Accessing Arrays, Lists, and Maps with the JSTL Expression Language
The JSP page shown in Figure 22 is accessed indirectly with the URL /dataCreator. That URL invokes a servlet that creates user profiles and forwards to the JSP page. Listing 2.5 lists the application's deployment descriptor, which maps the /dataCreator URL to the dataCreator servlet.
Listing 2.5 WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"> <web-app> <servlet> <servlet-name>dataCreator</servlet-name> <servlet-class>DataCreatorServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dataCreator</servlet-name> <url-pattern>/dataCreator</url-pattern> </servlet-mapping> </web-app>
The dataCreator servlet is listed in Listing 2.6.
Listing 2.6 WEB-INF/classes/DataCreatorServlet.java
import java.io.IOException; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import beans.*; public class DataCreatorServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Create an array, map, and list UserProfile[] profileArray = createProfiles(); HashMap profileMap = new HashMap(); LinkedList profileList = new LinkedList(); // Populate the list and map for(int i=0; i < profileArray.length; ++i) { UserProfile profile = profileArray[i]; profileList.add(profile); String firstName = profile.getName().getFirstName(), lastName = profile.getName().getLastName(), key = firstName + " " + lastName; profileMap.put(key, profile); } // Store the array, map, and list in request scope request.setAttribute("profileArray", profileArray); request.setAttribute("profileMap", profileMap); request.setAttribute("profileList", profileList); // Forward the request and response to /showData.jsp RequestDispatcher rd = getServletContext().getRequestDispatcher("/showData.jsp"); rd.forward(request, response); } private UserProfile[] createProfiles() { // Create an array of user profiles UserProfile[] userProfiles = { new UserProfile(new Name("James", "Wilson"), new Address("102 Oak St.", "Buffalo", "New York", 14214)), new UserProfile(new Name("Cindy", "Smith"), new Address("29419 Genessee St.", "Buffalo", "New York", 14214)) }; return userProfiles; } }
The preceding servlet creates two user profiles and stores those profiles in an array, a map, and a list. Subsequently, the servlet stores the array, map, and list in request scope and forwards to a JSP page named showData.jsp. That JSP page is shown in Figure 22 and listed in Listing 2.7.
Listing 2.7 showData.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Accessing Arrays, Lists, and Maps with the EL</title> </head> <body> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%-- Access the first and last names stored in the two user profiles through the array, list, and map --%> Profiles are in request scope for <b> <c:out value='${profileArray[0].name.firstName}'/> <c:out value='${profileArray[0].name.lastName}'/></b> and <b> <c:out value='${profileList[1].name.firstName}'/> <c:out value='${profileMap["Cindy Smith"].name.lastName}'/> </b><p> <%-- Store the two profiles in page-scoped variables --%> <c:set var='jamesProfile' value='${profileMap["James Wilson"]}'/> <c:set var='cindyProfile' value='${profileList[1]}'/> <%-- Show address information, through the page-scoped variables --%> <c:out value='${jamesProfile.name.firstName}'/> lives at:<b> <c:out value='${jamesProfile.address.streetAddress}'/> <c:out value='${jamesProfile.address.city}'/>, <c:out value='${jamesProfile.address.state}'/> <c:out value='${jamesProfile.address.zip}'/></b> <p> <c:out value='${cindyProfile.name.firstName}'/> lives at:<b> <c:out value='${cindyProfile.address.streetAddress}'/> <c:out value='${cindyProfile.address.city}'/>, <c:out value='${cindyProfile.address.state}'/> <c:out value='${cindyProfile.address.zip}'/> </body> </html>
As the preceding JSP page illustrates, you access objects in an array with the [] operator, just as you would in Java by specifying a 0-based index into the array; for example, the expression ${profileArray[0].name.firstName} accesses the first name of the first profile stored in the profileArray.
You access objects stored in a list with the same syntax used for accessing objects in arrays; for example, in the preceding JSP page, the expression ${profileList[1].name.firstName} accesses the first name of the second profile stored in the profileList.
The Java class libraries offer quite a few different types of maps, including hash tables, hash maps, attributes, and tree maps. All of those data structures store key/value pairs of objects. To access those objects using the EL, you specify a key, enclosed in double quotes, with the [] operator; for example, the JSP page listed in Listing 2.7 accesses Cindy Smith's last name with this expression: ${profileMap["Cindy Smith"].name.lastName}.
As you can tell from the JSP pages listed in Listing 2.4 on page 49 and Listing 2.7, accessing nested bean properties can be rather verbose, although it's much more succinct than accessing properties with a JSP expression. You can reduce that verbosity by creating page-scoped variables that directly reference beans stored in
data structures. For example, the JSP page listed in Listing 2.7 stores James Wilson's user profile in a page-scoped variable named jamesProfile. That JSP page also creates a page-scoped variable named cindyProfile that directly references the user profile for Cindy Smith. Those page-scoped variables make it easier to access the user profiles; for example, instead of accessing Cindy's first name through the profile map like this: ${profileMap["Cindy Smith"].name.firstName}, you can access it like this: ${cindyProfile.name.firstName}.
In this and the preceding section, we discussed how to use the [] operator to access bean properties and beans stored in arrays, lists, and maps. In the next section, we take a closer look at how the [] operator works and why you might prefer that operator to the . operator when accessing beans.
A Closer Look at the [] Operator
As discussed in "Accessing JavaBeans Components" on page 46 and "Accessing Objects Stored in Arrays, Lists, and Maps" on page 52, you use the [] operator with this syntax: ${identifier[subexpression]}. Here's how expressions with that syntax are evaluated:
Evaluate the identifier and the subexpression; if either resolves to null, the expression is null.
If the identifier is a bean: The subexpression is coerced to a String value and that string is regarded as a name of one of the bean's properties. The expression resolves to the value of that property; for example, the expression ${name.["lastName"]} translates into the value returned by name.getLastName().
If the identifier is an array: The subexpression is coerced to an int valuewhich we'll call subexpression-intand the expression resolves to identifier[subexpression-int]. For example, for an array named colors, colors[3] represents the fourth object in the array. Because the subexpression is coerced to an int, you can also access that color like this: colors["3"]; in that case, JSTL coerces "3" into 3. That feature may seem like a very small contribution to JSTL, but because request parameters are passed as strings, it can be quite handy.
If the identifier is a list: The subexpression is also coerced to an intwhich we will also call subexpression-intand the expression resolves to the value returned from identifier.get(subexpression-int), for example: colorList[3] and colorList["3"] both resolve to the fourth element in the list.
If the identifier is a map: The subexpression is regarded as one of the map's keys. That expression is not coerced to a value because map keys can be any type of object. The expression evaluates to identifier.get(subexpression), for example, colorMap[Red] and colorMap["Red"]. The former expression is valid only if a scoped variable named Red exists in one of the four JSP scopes and was specified as a key for the map named colorMap.
Table 2.2 lists the methods that the EL invokes on your behalf.
Table 2.2 Methods That the EL Invokes for You
Identifier Type |
Example Use |
Method Invoked |
JavaBean component |
${colorBean.red} ${colorBean["red"]} |
colorBean.getRed() |
Array |
${colorArray[2]} ${colorArray["2"]} |
Array.get(colorArray, 2) |
List |
colorList[2] colorList["2"] |
colorList.get(2) |
Map |
colorMap[red] colorMap["red"] |
colorMap.get(pageContext. findAttribute("red")) colorMap.get("red") |
JSTL developers rely heavily on maps because the EL provides 11 indispensable implicit objects, of which 10 are maps. Everything, from request parameters to cookies, is accessed through a map. Because of this reliance on maps, you need to understand the meaning of the last row in Table 2.2. You access a map's values through its keys, which you can specify with the [] operator, for example, in Table 2.2, ${colorMap[red]} and ${colorMap["red"]}. The former specifies an identifier for the key, whereas the latter specifies a string. For the identifier, the PageContext.findAttribute method searches all four JSP scopes for a scoped variable with the name that you specify, in this case, red. On the other hand, if you specify a string, it's passed directly to the map's get method.
The [] Operator's Special Ability
Although it may not be obvious from our discussion so far, the [] operator has a special ability that its counterpart, the . operator, does not haveit can operate on an expression, whereas the . operator can only operate on an identifier. For example, you can do this: ${colorMap[param.selectedColor]}, which uses the string value of the selectedColor request parameter as a key for a map named colorMap.10 That's something that you can't do with the . operator.
Figure 23 shows a Web application that uses the [] operator's special ability to show request header values.
Figure 23 Illustrating an Advantage of the [] Operator
The top picture in Figure 23 shows a JSP page that lets you select a request header. After you activate the Show First Header Value button, the JSP page shown in the bottom picture shows the first value for the request header that was selected.
The JSP page shown in the top picture in Figure 23 is listed in Listing 2.8.
Listing 2.8 Selecting a Request Header <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Using the EL Generalized Access Operator</title> </head> <body> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <form action='show_first_header_value.jsp'> Show the first value for this request header: <select name='headerName'> <c:forEach var='hdr' items='${header}'> <option value='<c:out value="${hdr.key}"/>'> <c:out value='${hdr.key}'/> </option> </c:forEach> </select> <p><input type='submit' value='Show First Header Value'/> </form> </body> </html>
The preceding JSP page uses the header JSTL implicit object to iterate over request headers. The names of those request headers are used to create an HTML select element named headerName. The select element resides in a form whose action is show_first_header_value.jsp, so that the JSP page is loaded when you activate the Show First Header Value button. That JSP page is listed in Listing 2.9.
Listing 2.9 Using the [] Operator with a Request Parameter <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Using the EL Generalized Access Operator</title> </head> <body> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> The first value for the header named <b> <c:out value='${param.headerName}'/></b> is <i> <c:out value='${header[param.headerName]}'/></i> </body> </html>
The preceding JSP page uses two JSTL implicit objects: param, which is a map of request parameters, and header, which is a map of request headers. The subexpression param.headerName accesses the headerName request parameter's value, and the expression ${header[param.headerName]} resolves to the first value for that request header.
The empty Operator
Testing for the existence of request parameters can be tricky because they evaluate to null if they don't exist but they evaluate to an empty string ("") if their value was not specified. Most of the time, when you check for the existence of request parameters, you don't have to distinguish the former from the latter; you just want to know whether a value was specified. For that special task, you can use the empty operator, which tests whether an identifier is null or doesn't exist, as illustrated by the Web application shown in Figure 24.
Figure 24 Using the empty Operator to Test for the Existence of Request Parameters
The Web application shown in Figure 24 consists of two JSP pages, one that lets you enter a name and another that checks for a corresponding request parameter. As illustrated by the top two pictures in Figure 24, if you don't enter anything in the name field, the latter JSP page prints an error message and includes the referring JSP page. The bottom two pictures illustrate successful access to the name request parameter. The JSP page with the name input field is listed in Listing 2.10.
Listing 2.10 index.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Using the Empty Operator</title> </head> <body> <jsp:include page='form.jsp'/> </body> </html>
The preceding JSP page includes form.jsp, which is listed in Listing 2.11.
Listing 2.11 form.jsp
<form action='check_header.jsp'> Name: <input type='text' name='name'/> <p><input type='submit' value='Register'/> </form>
The action for the form in the preceding JSP page is check_header.jsp, which is listed in Listing 2.12.
Listing 2.12 check_header.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Using the Empty Operator</title> </head> <body> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <c:choose> <c:when test='${not empty param.name}'> Hello <c:out value='${param.name}'/>. </c:when> <c:otherwise> <font color='red'> Please enter your name:<p> </font> <jsp:include page='form.jsp'/> </c:otherwise> </c:choose> </body> </html>
The preceding JSP page combines the not and empty operators to see whether the name request parameter was specified; if so it displays a personalized greeting; otherwise, it prints an error message and includes the referring JSP page.