| :xrefstyle: full |
| :sectnums!: |
| == Jakarta Expression Language, Version 4.0 |
| |
| Copyright (c) 2013, 2020 Oracle and/or its affiliates and others. |
| All rights reserved. |
| |
| Eclipse is a registered trademark of the Eclipse Foundation. Jakarta |
| is a trademark of the Eclipse Foundation. Oracle and Java are |
| registered trademarks of Oracle and/or its affiliates. Other names |
| may be trademarks of their respective owners. |
| |
| The Jakarta Expression Language Team - {revdate} |
| |
| Comments to: el-dev@eclipse.org |
| |
| == Preface |
| |
| This is the Expression Language specification |
| version 4.0, developed by the Jakarta Expression Language Team under the Eclipse |
| Foundation Specification Process. |
| |
| === Historical Note |
| |
| The Expression Language (EL) was originally inspired by both |
| ECMAScript and the XPath expression languages. During its inception, the |
| experts involved were very reluctant to design yet another expression |
| language and tried to use each of these languages, but they fell short |
| in different areas. |
| |
| The JSP Standard Tag Library (JSTL) version 1.0 |
| (based on JSP 1.2) was therefore first to introduce an Expression |
| Language to make it easy for page authors to access and manipulate |
| application data without having to master the complexity associated with |
| programming languages such as Java and JavaScript. |
| |
| Given its success, the EL was subsequently moved |
| into the JSP specification (JSP 2.0/JSTL 1.1), making it generally |
| available within JSP pages (not just for attributes of JSTL tag |
| libraries). |
| |
| JavaServer Faces 1.0 defined a standard |
| framework for building User Interface components, and was built on top |
| of JSP 1.2 technology. Because JSP 1.2 technology did not have an |
| integrated expression language and because the JSP 2.0 EL did not meet |
| all of the needs of Faces, an EL variant was developed for Faces 1.0. |
| The Faces expert group (EG) attempted to make the language as compatible |
| with JSP 2.0 as possible but some differences were necessary. |
| |
| It was obviously desirable to have a single, |
| unified expression language that meets the needs of the various web-tier |
| technologies. The Faces and JSP EGs therefore worked together on the |
| specification of a unified expression language, defined in JSR 245, and |
| which took effect for the JSP 2.1 and Faces 1.2 releases. |
| |
| The JSP/JSTL/Faces expert groups also |
| acknowledged that the EL is useful beyond their own |
| specifications. The 3.0 specification was the first JSR that defined the |
| Expression Language as an independent specification, with no |
| dependencies on other technologies. |
| |
| This specification is now developed under the Eclipse |
| Foundation Specification Process. Together with the Test Compatibility |
| Kit (TCK) which tests that a given implementation meets the requirements |
| of the specification, and Compatible Implementations (CIs) that |
| implement this specification and which pass the TCK, this specification |
| defines the Jakarta standard for Expression Language. |
| |
| <<< |
| === Typographical Conventions |
| |
| [width="100%",cols="50%,50%",options="header",] |
| |=== |
| |Font Style |Uses |
| | _Italic_ | _Emphasis, definition of term._ |
| | `Monospace` | `Syntax, code examples, attribute names, Java language |
| types, API, enumerated attribute values.` |
| |=== |
| |
| === Comments |
| |
| We are interested in improving this |
| specification and welcome your comments and suggestions. We have a |
| GitHub project with an issue tracker and a mailing list for comments |
| and discussions about this specification. |
| |
| Project: https://github.com/eclipse-ee4j/el-ri |
| |
| Mail alias for comments: el-dev@eclipse.org |
| |
| :sectnums: |
| == Language Syntax and Semantics |
| |
| The syntax and semantics of the Expression |
| Language are described in this chapter. |
| |
| === Overview |
| |
| The EL was originally designed as a simple |
| language to meet the needs of the presentation layer in web |
| applications. It features: |
| |
| * A simple syntax restricted to the evaluation of expressions |
| |
| * Variables and nested properties |
| |
| * Relational, logical, arithmetic, conditional, and empty operators |
| |
| * Functions implemented as static methods on Java classes |
| |
| * Lenient semantics where appropriate default values and type |
| conversions are provided to minimize exposing errors to end users |
| |
| as well as |
| |
| * A pluggable API for resolving variable references into Java objects |
| and for resolving the properties applied to these Java objects |
| |
| * An API for deferred evaluation of expressions that refer to either |
| values or methods on an object |
| |
| * Support for lvalue expressions (expressions a value can be assigned |
| to) |
| |
| These last three features are key additions |
| to the JSP 2.0 EL resulting from the EL alignment work done in the JSP |
| 2.1 and Faces 1.2 specifications. |
| |
| EL 3.0 added features to enable EL to be used |
| as a stand-alone tool. It introduced APIs for direct evaluation of EL |
| expressions and manipulation of EL environments. It also added some |
| powerful features to the language, such as the support of operations for |
| collection objects. |
| |
| EL 4.0 implements the transition from the `javax` namespace to the |
| `jakarta` namespace. |
| |
| ==== EL in a nutshell |
| |
| The syntax is quite simple. Model objects are |
| accessed by name. A generalized `[]` operator can be used to access |
| maps, lists, arrays of objects and properties of a JavaBeans object, and |
| to invoke methods in a JavaBeans object; the operator can be nested |
| arbitrarily. The `.` operator can be used as a convenient shorthand for |
| property access when the property name follows the conventions of Java |
| identifiers, but the `[]` operator allows for more generalized access. |
| Similarly, the `.` operator can also be used to invoke methods, when the |
| method name is known, but the `[]` operator can be used to invoke methods |
| dynamically. |
| |
| Relational comparisons are allowed using the |
| standard Java relational operators. Comparisons may be made against |
| other values, or against boolean (for equality comparisons only), |
| string, integer, or floating point literals. Arithmetic operators can be |
| used to compute integer and floating point values. Logical operators are |
| available. |
| |
| The EL features a flexible architecture where |
| the resolution of model objects (and their associated properties and |
| methods), functions, and variables are all performed through a pluggable |
| API, making the EL easily adaptable to various environments. |
| |
| === EL Expressions |
| |
| An EL expression is specified either as an |
| _eval-expression_, or as a _literal-expression_. The EL also supports |
| _composite expressions_, where multiple EL expressions |
| (eval-expressions and literal-expressions) are grouped together. |
| |
| An EL expression is parsed as either a _value |
| expression_ or a _method expression_. A value expression refers to a value, |
| whereas a method expression refers to a method on an object. Once |
| parsed, the expression can optionally be evaluated one or more times. |
| |
| Each type of expression (eval-expression, |
| literal-expression, and composite expression) is described in its own |
| section below. |
| |
| ==== Eval-expression |
| |
| An eval-expression is formed by using the |
| constructs `$\{expr}` or `#\{expr}`. Both constructs are parsed and |
| evaluated in exactly the same way by the EL, even though they might |
| carry different meanings in the technology that is using the EL. |
| |
| For instance, by convention the Jakarta EE web |
| tier specifications use the `$\{expr}` construct for immediate |
| evaluation and the `\#{expr}` construct for deferred evaluation. This |
| difference in delimiters points out the semantic differences between the |
| two expression types in the Jakarta EE web tier. Expressions delimited by |
| `#{}` are said to use “deferred evaluation” because the expression is |
| not evaluated until its value is needed by the system. Expressions |
| delimited by `${}` are said to use “immediate evaluation” because the |
| expression is compiled when the JSP page is compiled and it is executed |
| when the JSP page is executed. More on this in |
| <<Syntax restrictions>>. |
| |
| Other technologies may choose to use the same |
| convention. It is up to each technology to enforce its own restrictions |
| on where each construct can be used. |
| |
| In some EL APIs, especially those introduced |
| in EL 3.0 to support stand-alone use, the EL expressions are specified |
| without `${}` or `#{}` delimiters. |
| |
| *Nested eval-expressions, such as `${item[$\{i}]}`, are illegal.* |
| |
| ===== Eval-expressions as value expressions |
| |
| When parsed as a value expression, an |
| eval-expression can be evaluated as either an _rvalue_ or an _lvalue_. An |
| _rvalue_ is an expression that would typically appear on the right side of |
| the assignment operator. An _lvalue_ would typically appear on the left |
| side. |
| |
| For instance, all EL expressions in JSP 2.0 |
| are evaluated by the JSP engine immediately when the page response is |
| rendered. They all yield rvalues. |
| |
| In the following JSTL action: |
| |
| * `<c:out value="${customer.name}"/>` |
| |
| the expression `${customer.name}` is |
| evaluated by the JSP engine and the returned value is fed to the tag |
| handler and converted to the type associated with the attribute |
| (`String` in this case). |
| |
| Faces, on the other hand, supports a full UI |
| component model that requires expressions to represent more than just |
| rvalues. It needs expressions to represent references to data structures |
| whose value could be assigned, as well as to represent methods that |
| could be invoked. |
| |
| For example, in the following Faces code sample: |
| |
| [source,html] |
| ---- |
| <h:form> |
| <h:inputText |
| id="email" |
| value="#{checkOutFormBean.email}" |
| size="25" maxlength="125" |
| validator="#{checkOutFormBean.validateEmail}"/> |
| </h:form> |
| ---- |
| |
| when the form is submitted, the “apply |
| request values” phase of Faces evaluates the EL expression |
| `#{checkOutFormBean.email}` as a reference to a data structure whose |
| value is set with the input parameter it is associated with in the form. |
| The result of the expression therefore represents a reference to a data |
| structure, or an lvalue, the left hand side of an assignment |
| operation. |
| |
| When that same expression is evaluated during |
| the rendering phase, it yields the specific value associated with the |
| object (rvalue), just as would be the case with JSP. |
| |
| The valid syntax for an lvalue is a subset of |
| the valid syntax for an rvalue. In particular, an lvalue can only |
| consist of either a single variable (e.g. `$\{name}`) or a property |
| resolution on some object, via the `.` or `[]` operator (e.g. |
| `${employee.name}`). Of course, an EL function or method that returns |
| either an object or a name can be part of an lvalue. |
| |
| When parsing a value expression, an expected |
| type is provided. In the case of an rvalue, the expected type is what |
| the result of the expression evaluation is coerced to. In the case of |
| lvalues, the expected type is ignored and the provided value is coerced |
| to the actual type of the property the expression points to, before that |
| property is set. The EL type conversion rules are defined in |
| <<Type Conversion>>. A few sample |
| eval-expressions are shown in the following table. |
| |
| |
| .Sample eval-expressions |
| [width="100%",cols="34%,33%,33%",options="header",] |
| |=== |
| |Expression |
| |Expected Type |
| |Result |
| |`${customer.name}` |
| |`String` |
| |`Guy Lafleur` |
| |
| _Expression evaluates to a String. No |
| conversion necessary._ |
| |
| |`${book}` |
| |`String` |
| |`Wonders of the World` |
| |
| _Expression evaluates to a `Book` object (e.g. |
| `com.example.Book`). Conversion rules result in the evaluation of |
| `book.toString()`, which could for example yield the book title._ |
| |
| |=== |
| |
| |
| ===== Eval-expressions as method expressions |
| |
| In some cases, it is desirable for an EL |
| expression to refer to a method instead of a model object. |
| |
| For instance, in JSF, a component tag also |
| has a set of attributes for referencing methods that can perform certain |
| functions for the component associated with the tag. To support these |
| types of expressions, the EL defines method expressions (EL class |
| `MethodExpression`). |
| |
| In the above example, the validator attribute |
| uses an expression that is associated with type `MethodExpression`. |
| Just as with ``ValueExpression``s, the evaluation of the expression |
| (calling the method) is deferred and can be processed by the underlying |
| technology at the appropriate moment within its life cycle. |
| |
| A method expression shares the same syntax as |
| an lvalue. That is, it can only consist of either a single variable |
| (e.g. `$\{name}`) or a property resolution on some object, via the `.` |
| or `[]` operator (e.g. `${employee.name}`). Information about the |
| expected return type and parameter types is provided at the time the |
| method is parsed. |
| |
| A method expression is evaluated by invoking |
| its referenced method or by retrieving information about the referenced |
| method. Upon evaluation, if the expected signature is provided at parse |
| time, the EL API verifies that the method conforms to the expected |
| signature, and there is therefore no coercion performed. If the expected |
| signature is not provided at parse time, then at evaluation, the method |
| is identified with the information of the parameters in the expression, |
| and the parameters are coerced to the respective formal types. |
| |
| ==== Literal-expression |
| |
| A literal-expression does not use the |
| `$\{expr}` or `#{expr}` constructs, and simply evaluates to the text of |
| the expression, of type `String`. Upon evaluation, an expected type of |
| something other than `String` can be provided. Sample |
| literal-expressions are shown in the following table. |
| |
| |
| .Sample literal-expressions |
| [width="100%",cols="34%,33%,33%",options="header",] |
| |=== |
| |Expression |
| |Expected Type |
| |Result |
| |`Aloha!` |
| |`String` |
| |`Aloha!` |
| |`true` |
| |`Boolean` |
| |`Boolean.TRUE` |
| |=== |
| |
| To generate literal values that include the |
| character sequence \"``${``" or \"``#{``", the developer can choose to |
| use a composite expression as shown here: |
| |
| * `${'${'}exprA}` |
| |
| * `\#{'#{'}exprB}` |
| |
| The resulting values would |
| then be the strings `${exprA}` and `#\{exprB}`. |
| |
| Alternatively, the escape characters `\$` and |
| `\#` can be used to escape what would otherwise be treated as an |
| eval-expression. Given the literal-expressions: |
| |
| * `\${exprA}` |
| |
| * `\#{exprB}` |
| |
| The resulting values would again be the |
| strings `$\{exprA}` and `#\{exprB}`. |
| |
| A literal-expression can be used anywhere a |
| value expression can be used. A literal-expression can also be used as a |
| method expression that returns a non-void return value. The standard EL |
| coercion rules (see <<Type Conversion>>) |
| then apply if the return type of the method expression is not |
| `java.lang.String`. |
| |
| Note that when EL is integrated into other technologies, such as JSP, |
| that integration may not include Literal Expressions. Where integrations |
| do not include Literal Expressions, those integrating technologies will |
| define their own specification, including escaping rules, for handling |
| text outside of EL and the escaping rules described above will not apply. |
| |
| ==== Composite expressions |
| |
| The EL also supports _composite expressions_, |
| where multiple EL expressions are grouped together. With composite |
| expressions, eval-expressions are evaluated from left to right, coerced |
| to ``String``s (according to the EL type conversion rules), and |
| concatenated with any intervening literal-expressions. |
| |
| For example, the composite expression |
| `"$\{firstName} $\{lastName}"` is composed of three EL expressions: |
| eval-expression `"$\{firstName}"`, literal-expression `" "`, and |
| eval-expression `"$\{lastName}"`. |
| |
| Once evaluated, the resulting `String` is |
| then coerced to the expected type, according to the EL type conversion |
| rules. A sample composite expression is shown in the following table. |
| |
| .Sample composite expression |
| [width="100%",cols="34%,33%,33%",options="header",] |
| |=== |
| |Expression |
| |Expected Type |
| |Result |
| |`Welcome ${customer.name} to our site` |
| |`String` |
| |`Welcome Guy Lafleur to our site` |
| |
| _``${customer.name}`` evaluates to a `String` which |
| is then concatenated with the literal-expressions. No conversion |
| necessary._ |
| |
| |=== |
| |
| |
| *It is illegal to mix `${}` and `\#{}` |
| constructs in a composite expression*. This restriction is imposed to |
| avoid ambiguities should a user think that using `${expr}` or |
| `#{expr}` dictates how an expression is evaluated. For instance, as was |
| mentioned previously, the convention in the Jakarta EE web tier specifications |
| is for `${}` to mean immediate evaluation and for `#{}` to mean |
| deferred evaluation. This means that in EL expressions in the Jakarta EE web |
| tier, a developer cannot force immediate evaluation of some parts of a |
| composite expression and deferred evaluation of other parts. This |
| restriction may be lifted in future versions to allow for more advanced |
| EL usage patterns. |
| |
| For APIs prior to EL 3.0, a composite |
| expression can be used anywhere an EL expression can be used except for |
| when parsing a method expression. Only a single eval-expression can be |
| used to parse a method expression. |
| |
| Some APIs in EL 3.0 onwards use only single |
| eval-expressions, and not the composite expressions. However, there is |
| no loss in functionality, since a composite expression can be specified |
| with a single eval-expressions, by using the string concatenation |
| operators, introduced in EL 3.0. For instance, the composite expression: |
| |
| * `Welcome ${customer.name} to our site` |
| |
| can be written as: |
| |
| * `${'Welcome ' += customer.name += ' to our site'}`. |
| |
| ==== Syntax restrictions |
| |
| While `${}` and `#{}` eval-expressions are |
| parsed and evaluated in exactly the same way by the EL, the underlying |
| technology is free to impose restrictions on which syntax can be used |
| according to where the expression appears. |
| |
| For instance, in JSP, `\#{}` expressions |
| are only allowed for tag attributes that accept deferred expressions. |
| `#{expr}` will generate an error if used anywhere else. |
| |
| === Literals |
| |
| There are literals for boolean, integer, |
| floating point, string, and null in an eval-expression. |
| |
| * Boolean - `true` and `false` |
| |
| * Integer - As defined by the |
| `IntegerLiteral` construct in <<Collected Syntax>> |
| |
| * Floating point - As defined by the |
| `FloatingPointLiteral` construct in <<Collected Syntax>> |
| |
| * String - Enclosed with single or double quotes with the following rules |
| for escaping the enclosed string: |
| ** `\` must be escaped as `\\` |
| ** `"` must be escaped as `\"` when the string is enclosed with `"` |
| ** `"` may be escaped as `\"` when the string is enclosed with `'` |
| ** `'` must be escaped as `\'` when the string is enclosed with `'` |
| ** `'` may be escaped as `\'` when the string is enclosed with `"` |
| ** no other escaping is permitted |
| |
| * Null - `null` |
| |
| === Errors, Warnings, Default Values |
| |
| The Expression Language has been designed with |
| the presentation layer of web applications in mind. In that usage, |
| experience suggests that it is most important to be able to provide as |
| good a presentation as possible, even when there are simple errors in |
| the page. To meet this requirement, the EL does not provide warnings, |
| just default values and errors. Default values are type-correct values |
| that are assigned to a subexpression when there is some problem. An |
| error is an exception thrown (to be handled by the environment where the |
| EL is used). |
| |
| === Resolution of Model Objects and their Properties or Methods |
| |
| A core concept in the EL is the evaluation of |
| a model object name into an object, and the resolution of properties or |
| methods applied to objects in an expression (operators `.` and `[]`). |
| |
| The EL API provides a generalized mechanism, |
| an `ELResolver`, implemented by the underlying technology and which |
| defines the rules that govern the resolution of model object names and |
| their associated properties. |
| |
| The resolution of names and properties is |
| further affected by the presence of: |
| |
| * Functions. See <<Functions>>. |
| |
| * Variables. See <<Variables>>. |
| |
| * Imported names (classes, fields, and |
| methods). See <<Static Field and Method Reference>>. |
| |
| * Lambda expressions and arguments. See <<Lambda Expressions>>. |
| |
| The rules described below are used in |
| resolving names and properties when evaluating identifiers, function |
| calls, and object properties and method calls. |
| |
| ==== Evaluating Identifiers |
| |
| These steps are used for evaluating an identifier: |
| |
| * If the identifier is a lambda argument passed |
| to a lambda expression invocation, its value is returned. |
| |
| * Else if the identifier is a variable, the |
| associated expression is evaluated and returned. |
| |
| * Else if the identifier is resolved by the |
| ``ELResolver``s, the value returned from the ``ELResolver``s is returned. |
| |
| * Else if the identifier is an imported static |
| field, its value is returned. |
| |
| * Else return not resolved. |
| |
| One implication of the explicit search order |
| of the identifiers is that an identifier hides other identifiers (of the |
| same name) that come after it in the list. |
| |
| ==== Evaluating functions |
| |
| The expression with the syntax |
| _func(args...)(args...)..._ can mean any of the following: |
| |
| * A call to an EL fucntion with empty namespace. |
| |
| * A call to a lambda expression. |
| |
| * A call to the constructor of an imported class. |
| |
| * A call to a static method that has been imported statically. |
| |
| Note the above syntax allows the invocation |
| of a lambda expression that returns another lambda expression, which is |
| then invoked. |
| |
| The following steps are used to evaluate the |
| above expression: |
| |
| * Evaluate the name of the function as an identifier: |
| |
| ** If the identifier is a lambda argument passed |
| to a lambda expression invocation, its value is returned. |
| |
| ** Else if the identifier is a variable, the |
| associated expression is evaluated and returned. |
| |
| ** Else if the identifier is resolved by the ``ELResolver``s, |
| the value returned from the ``ELResolver``s is returned. |
| |
| * If the result of evaluating the function name is a `LambdaExpression`, |
| the `LambdaExpression` is invoked with the supplied |
| arguments. If the result of evaluating the `LambdaExpression` is another |
| `LambdaExpression`, and the syntax contains repeated function invocations, |
| such as _func()()..._, then the resultant `LambdaExpression` is in turn |
| evaluated, and so on. |
| |
| * Else if the function has been mapped |
| previously in a `FunctionMapper`, the mapped method is invoked with the |
| supplied arguments. |
| |
| * Else if the function name is the name of an |
| imported class, the constructor for this class is invoked with the |
| supplied arguments. |
| |
| * Else if the function name is the name of an |
| imported static method, the method is invoked with the supplied |
| arguments. |
| |
| * Else error. |
| |
| ==== Evaluating objects with properties |
| |
| The steps for evaluating an expression with |
| `[]` or `.` operators (property reference and method call) are described in |
| <<Operators `[]` and `.`>>. However, the |
| syntax for `.` operator is also used to reference a static field, or to |
| invoke a static method. Therefore if the expression with a `.` operator is |
| not resolved by the ``ELResolver``s, and if the identifier for the base |
| object is the name of an imported class, the expression becomes a |
| reference to a static field, or an invocation of a static method, of the |
| imported class. |
| |
| ==== Invoking method expressions |
| |
| A method expression can consist of either a |
| single variable (e.g. `${name}`) or a property resolution on some |
| object, via the `.` or `[]` operator (e.g. `${employee.getName}`). |
| <<Operators `[]` and `.`>> describes how to |
| invoke a method of an object. This form of method expressions allows |
| arguments to the method to be specified in the EL expression (e.g. |
| `${employee.getName()}`). |
| |
| To invoke a method expression of a single |
| variable, the identifier is first evaluated, as described in |
| <<Evaluating Identifiers>>. If the |
| identifier evaluates to a `jakarta.el.MethodExpression`, the method |
| expression is invoked and the result returned, otherwise an error is |
| raised. This form of method expression does not allow arguments to be |
| specified in the EL expression. |
| |
| === Operators `[]` and `.` |
| |
| The EL follows ECMAScript in unifying the |
| treatment of the `.` and `[]` operators. |
| |
| `expr-a.identifier-b` is equivalent to |
| `expr-a["identifier-b"]`; that is, the identifier `identifier-b` is |
| used to construct a literal whose value is the identifier, and then the |
| `[]` operator is used with that value. |
| |
| Similarly, `expr-a.identifier-b(params)` is |
| equivalent to `expr-a["identifier-b"](params).` |
| |
| The expression |
| `expr-a["identifier-b"](params)` denotes a method invocation |
| with parameters, where `params` is a comma-separated list of expressions |
| denoting the parameters for the method call. |
| |
| To evaluate `expr-a[expr-b] or expr-a[expr-b](params)`: |
| |
| * Evaluate `expr-a` into `value-a`. |
| |
| * If `value-a` is `null`: |
| |
| ** If `expr-a[expr-b]` is the last property being resolved: |
| |
| *** If the expression is a value expression and |
| `ValueExpression.getValue(context)` was called to initiate this |
| expression evaluation, return `null`. |
| |
| *** Otherwise, throw `PropertyNotFoundException`. + |
| _[trying to de-reference null for an lvalue]_ |
| |
| ** Otherwise, return `null`. |
| |
| * Evaluate `expr-b` into `value-b`. |
| |
| * If `value-b` is `null`: |
| |
| ** If `expr-a[expr-b]` is the last property being resolved: |
| |
| *** If the expression is a value expression and |
| `ValueExpression.getValue(context)` was called to initiate this |
| expression evaluation, return `null`. |
| |
| *** Otherwise, throw `PropertyNotFoundException`. + |
| _[trying to de-reference null for an lvalue]_ |
| |
| ** Otherwise, return `null`. |
| |
| * If the expression is a value expression: |
| |
| ** If `expr-a[expr-b]` is the last property being resolved: |
| |
| *** If `ValueExpression.getValue(context)` was |
| called to initiate this expression evaluation: |
| |
| **** If the expression is a parametered method |
| call, evaluate `params` into `param-values`, and invoke |
| `elResolver.invoke(context, value-a, value-b, null, param-values)`. |
| |
| **** Otherwise, invoke `elResolver.getValue(value-a, value-b)`. |
| |
| *** If `ValueExpression.getType(context)` was |
| called, invoke `elResolver.getType(context, value-a, value-b)`. |
| |
| *** If `ValueExpression.isReadOnly(context)` was |
| called, invoke `elResolver.isReadOnly(context, value-a, value-b)`. |
| |
| *** If `ValueExpression.setValue(context, val)` was called, |
| invoke `elResolver.setValue(context, value-a, value-b, val)`. |
| |
| ** Otherwise: |
| |
| *** If the expression is a parametered method |
| call, evaluate `params` into `param-values`, and invoke |
| `elResolver.invoke(context, value-a, value-b, null, params)`. |
| |
| *** Otherwise, invoke `elResolver.getValue(value-a, value-b)`. |
| |
| * Otherwise, the expression is a method expression: |
| |
| ** If `expr-a[expr-b]` is the last property being resolved: |
| |
| *** Coerce `value-b` to `String`. |
| |
| *** If the expression is not a parametered method |
| call, find the method on object `value-a` with name `value-b` and with |
| the set of expected parameter types provided at parse time. If the |
| method does not exist, or the return type does not match the expected |
| return type provided at parse time, throw `MethodNotFoundException`. |
| |
| *** If `MethodExpression.invoke(context, params)` was called: |
| |
| **** If the expression is a parametered method |
| call, evaluate `params` into `param-values`, and invoke |
| `elResolver.invoke(context, value-a, value-b, paramTypes, param-values)` |
| where `paramTypes` is the parameter types, if provided at parse time, and |
| is `null` otherwise. |
| |
| **** Otherwise, invoke the found method with the parameters passed to |
| the invoke method. |
| |
| *** If `MethodExpression.getMethodInfo(context)` |
| was called, construct and return a new `MethodInfo` object. |
| |
| ** Otherwise: |
| |
| *** If the expression is a parametered method call, evaluate `params` |
| into `param-values`, and invoke |
| `elResolver.invoke(context, value-a, value-b, null, params)`. |
| |
| *** Otherwise, invoke `elResolver.getValue(value-a, value-b)`. |
| |
| |
| |
| === Arithmetic Operators |
| |
| Arithmetic is provided to act on integer (`BigInteger` and `Long`) and |
| floating point (`BigDecimal` and `Double`) values. There are 5 operators: |
| |
| * Addition: `+` |
| |
| * Substraction: `-` |
| |
| * Multiplication: `*` |
| |
| * Division: `/` and `div` |
| |
| * Remainder (modulo): `%` and `mod` |
| |
| The last two operators are available in both |
| syntaxes to be consistent with XPath and ECMAScript. |
| |
| The evaluation of arithmetic operators is |
| described in the following sections. `A` and `B` are the evaluation of |
| subexpressions. |
| |
| ==== Binary operators - `A {+,-,*} B` |
| |
| * If `A` and `B` are `null`, return `(Long) 0` |
| |
| * If `A` or `B` is a `BigDecimal`, coerce both to `BigDecimal` and |
| then: |
| |
| ** If operator is `+`, return `A.add(B)` |
| |
| ** If operator is `-`, return `A.subtract(B)` |
| |
| ** If operator is `*`, return `A.multiply(B)` |
| |
| * If `A` or `B` is a `Float`, `Double`, or `String` containing `.`, |
| `e`, or `E`: |
| |
| ** If `A` or `B` is `BigInteger`, coerce both `A` and `B` to `BigDecimal` |
| and apply operator |
| |
| ** Otherwise, coerce both `A` and `B` to `Double` and apply operator |
| |
| * If `A` or `B` is `BigInteger`, coerce both to `BigInteger` and then: |
| |
| ** If operator is `+`, return `A.add(B)` |
| |
| ** If operator is `-`, return `A.subtract(B)` |
| |
| ** If operator is `*`, return `A.multiply(B)` |
| |
| * Otherwise coerce both `A` and `B` to `Long` and apply operator |
| |
| * If operator results in exception, error |
| |
| ==== Binary operator - `A {/,div} B` |
| |
| * If `A` and `B` are `null`, return `(Long) 0` |
| |
| * If `A` or `B` is a `BigDecimal` or a `BigInteger`, coerce both to |
| `BigDecimal` and return `A.divide(B, BigDecimal.ROUND_HALF_UP)` |
| |
| * Otherwise, coerce both `A` and `B` to `Double` and apply operator |
| |
| * If operator results in exception, error |
| |
| ==== Binary operator - `A {%,mod} B` |
| |
| * If `A` and `B` are `null`, return `(Long) 0` |
| |
| * If `A` or `B` is a `BigDecimal`, `Float`, `Double`, or `String` |
| containing `.`, `e`, or `E`, coerce both `A` and `B` to `Double` |
| and apply operator |
| |
| * If `A` or `B` is a `BigInteger`, coerce both to `BigInteger` and |
| return `A.remainder(B)` |
| |
| * Otherwise coerce both `A` and `B` to `Long` and apply operator |
| |
| * If operator results in exception, error |
| |
| ==== Unary minus operator - `-A` |
| |
| * If `A` is `null`, return `(Long) 0` |
| |
| * If `A` is a `BigDecimal` or `BigInteger`, return `A.negate()` |
| |
| * If `A` is a `String`: |
| |
| ** If `A` contains `.`, `e`, or `E`, coerce to a `Double` and apply |
| operator |
| |
| ** Otherwise, coerce to a `Long` and apply operator |
| |
| ** If operator results in exception, error |
| |
| * If `A` is `Byte`, `Short`, `Integer`, `Long`, `Float`, `Double` |
| |
| ** Retain type, apply operator |
| |
| ** If operator results in exception, error |
| |
| * Otherwise, error |
| |
| === String Concatenation Operator - `A += B` |
| |
| To evaluate `A += B`: |
| |
| * Coerce `A` and `B` to String |
| |
| * Return the concatenated string of `A` and `B` |
| |
| === Relational Operators |
| |
| The relational operators are: |
| |
| * `==` and `eq` |
| |
| * `!=` and `ne` |
| |
| * `<` and `lt` |
| |
| * `>` and `gt` |
| |
| * `\<=` and `le` |
| |
| * `>=` and `ge` |
| |
| The second versions of the last 4 operators |
| are made available to avoid having to use entity references in XML |
| syntax and have the exact same behavior, i.e. `<` behaves the same as |
| `lt` and so on. |
| |
| The evaluation of relational operators is |
| described in the following sections. |
| |
| ==== `A {<,>,\<=,>=,lt,gt,le,ge} B` |
| |
| * If `A==B`, if operator is `\<=`, `le`, `>=`, or `ge` return `true` |
| |
| * If `A` is `null` or `B` is `null`, return `false` |
| |
| * If `A` or `B` is `BigDecimal`, coerce both `A` and `B` to `BigDecimal` |
| and use the return value of `A.compareTo(B)` |
| |
| * If `A` or `B` is `Float` or `Double` coerce both `A` and `B` to |
| `Double` apply operator |
| |
| * If `A` or `B` is `BigInteger`, coerce both `A` and `B` to `BigInteger` |
| and use the return value of `A.compareTo(B)` |
| |
| * If `A` or `B` is `Byte`, `Short`, `Character`, `Integer`, or `Long` |
| coerce both `A` and `B` to `Long` and apply operator |
| |
| * If `A` or `B` is `String` coerce both `A` and `B` to `String`, compare |
| lexically |
| |
| * If `A` is `Comparable`, then: |
| |
| ** If `A.compareTo(B)` throws exception, error |
| |
| ** Otherwise use result of `A.compareTo(B)` |
| |
| * If `B` is `Comparable`, then: |
| |
| ** If `B.compareTo(A)` throws exception, error |
| |
| ** Otherwise use result of `B.compareTo(A)` |
| |
| * Otherwise, error |
| |
| ==== `A {==,!=,eq,ne} B` |
| |
| * If `A==B`, apply operator |
| |
| * If `A` is `null` or `B` is `null` return `false` for `==` or `eq`, `true` |
| for `!=` or `ne` |
| |
| * If `A` or `B` is `BigDecimal`, coerce both `A` and `B` to |
| `BigDecimal` and then: |
| |
| ** If operator is `==` or `eq`, return `A.equals(B)` |
| |
| ** If operator is `!=` or `ne`, retur `!A.equals(B)` |
| |
| * If `A` or `B` is `Float` or `Double` coerce both `A` and `B` to |
| `Double`, apply operator |
| |
| * If `A` or `B` is `BigInteger`, coerce both `A` and `B` to |
| `BigInteger` and then: |
| |
| ** If operator is `==` or `eq`, return `A.equals(B)` |
| |
| ** If operator is `!=` or `ne`, return `!A.equals(B)` |
| |
| * If `A` or `B` is `Byte`, `Short`, `Character`, `Integer`, or `Long` |
| coerce both `A` and `B` to `Long`, apply operator |
| |
| * If `A` or `B` is `Boolean` coerce both `A` and `B` to `Boolean`, |
| apply operator |
| |
| * If `A` or `B` is an enum, coerce both `A` and `B` to enum, apply |
| operator |
| |
| * If `A` or `B` is `String` coerce both `A` and `B` to `String`, compare |
| lexically |
| |
| * Otherwise if an error occurs while calling `A.equals(B)`, error |
| |
| * Otherwise, apply operator to result of `A.equals(B)` |
| |
| === Logical Operators |
| |
| The logical operators are: |
| |
| * `&&` and `and` |
| |
| * `||` and `or` |
| |
| * `!` and `not` |
| |
| The evaluation of logical operators is described in the following |
| sections. |
| |
| ==== Binary operator - `A {&&,||,and,or} B` |
| |
| * Coerce both `A` and `B` to `Boolean`, apply operator |
| |
| The operator stops as soon as the expression can be determined, i.e., |
| `A and B and C and D` – if `B` is false, then only `A and B` is |
| evaluated. |
| |
| ==== Unary not operator - `{!,not} A` |
| |
| * Coerce `A` to `Boolean`, apply operator |
| |
| |
| |
| === Empty Operator - `empty A` |
| |
| The `empty` operator is a prefix operator that can be used to determine |
| if a value is `null` or empty. |
| |
| To evaluate `empty A`: |
| |
| * If `A` is `null`, return `true` |
| |
| * Otherwise, if `A` is the empty string, then return `true` |
| |
| * Otherwise, if `A` is an empty array, then return `true` |
| |
| * Otherwise, if `A` is an empty `Map`, return `true` |
| |
| * Otherwise, if `A` is an empty `Collection`, return `true` |
| |
| * Otherwise return `false` |
| |
| === Conditional Operator - `A ? B : C` |
| |
| Evaluate `B` or `C`, depending on the result of the evaluation of `A`. |
| |
| Coerce `A` to `Boolean`: |
| |
| * If `A` is `true`, evaluate and return `B` |
| |
| * If `A` is `false`, evaluate and return `C` |
| |
| === Assignment Operator - `A = B` |
| |
| Assign the value of `B` to `A`. `A` must be an _lvalue_, otherwise, a |
| `PropertyNotWritableException` will be thrown. |
| |
| The assignment operator is right-associative. For instance, `A=B=C` is |
| the same as `A=(B=C)`. |
| |
| To evaluate `expr-a` = `expr-b`: |
| |
| * Evaluate `expr-a`, up to the last property resolution, to (`base-a`, |
| `prop-a`) |
| |
| * If `base-a` is `null`, and `prop-a` is a `String`: |
| |
| ** If `prop-a` is a Lambda parameter, throw a |
| `PropertyNotWritableException` |
| |
| ** If prop-a is an EL variable (see <<Variables>>), |
| evaluate the `ValueExpression` the variable was set to, to obtain the |
| new (`base-a`, `prop-a`) |
| |
| * Evaluate `expr-b`, to `value-b` |
| |
| * Invoke `ELResolver.setValue(base-a, prop-a, value-b)` |
| |
| * Return `value-b` |
| |
| The behavior of the assignment operator is determined by the |
| `ELResolver`. For instance, in a stand-alone environment, the class |
| `StandardELContext` contains a default `ELResolver` that allows the |
| assignment of an expression to a non-existing name, resulting in the |
| creation of a bean with the given name in the local bean repository. A |
| JSP container may use the `ScopeAttributeELResolver` to assign values |
| to scope attributes, or to create attributes in the page scope. |
| |
| === Semicolon Operator - `A ; B` |
| |
| The semicolon operator behaves like the comma operator in C. |
| |
| To evaluate `A;B, A` is first evaluated, and its value is discarded. |
| `B` is then evaluated and its value is returned. |
| |
| === Parentheses |
| |
| Parentheses can be used to change precedence, |
| as in: `${(a*(b+c))}` |
| |
| === Operator Precedence |
| |
| Highest to lowest, left-to-right. |
| |
| * `[] .` |
| |
| * `()` |
| |
| * `-` (unary) `not ! empty` |
| |
| * `* / div % mod` |
| |
| * `+ -` (binary) |
| |
| * `+=` |
| |
| * `< > \<= >= lt gt le ge` |
| |
| * `== != eq ne` |
| |
| * `&& and` |
| |
| * `|| or` |
| |
| * `? :` |
| |
| * `\->` (Lambda Expression) |
| |
| * `=` |
| |
| * `;` |
| |
| Qualified functions with a namespace prefix have precedence over the |
| operators. Thus the expression `${c?b:f()}` is illegal because `b:f()` |
| is being parsed as a qualified function instead of part of a |
| conditional expression. As usual, `()` can be used to make |
| the precedence explicit, e.g `${c?b:(f())}`. |
| |
| The symbol `\->` in a Lambda Expression behaves like an operator for |
| the purpose of ordering the operator precedence, and it has a higher |
| precedence than the assignment and semicolon operators. The following |
| examples illustrates when `()` is and is not needed. |
| |
| * `v = x\->x+1` |
| |
| * `x\-> (a=x)` |
| |
| * `x\-> c?x+1:x+2` |
| |
| All operators are left associative except for the `?:`, `=`, and `\->` |
| operators, which are right associative. For instance, `a=b=c` is the |
| parsed as `a=(b=c)`, and `x\->y\->x+y` is parsed as `x\->(y\->x+y)`. |
| |
| === Reserved Words |
| |
| The following words are reserved for the |
| language and must not be used as identifiers. |
| |
| [cols=5] |
| |=== |
| |and |
| |eq |
| |gt |
| |true |
| |instanceof |
| |or |
| |ne |
| |le |
| |false |
| |empty |
| |not |
| |lt |
| |ge |
| |null |
| |div |
| |mod|||| |
| |=== |
| |
| Note that many of these words are not in the |
| language now, but they may be in the future, so developers must avoid |
| using these words. |
| |
| === Functions |
| |
| The EL has qualified functions, reusing the |
| notion of qualification from XML namespaces (and attributes), XSL |
| functions, and JSP custom actions. Functions are mapped to public static |
| methods in Java classes. |
| |
| The full syntax is that of qualified n-ary |
| functions: |
| |
| [subs="normal"] |
| ---- |
| [ns:]f([a~1~[,a~2~[,...[,a~n~]]]]) |
| ---- |
| |
| Where `ns` is the namespace prefix, `f` is |
| the name of the function, and `a` is an argument. |
| |
| EL functions are mapped, resolved and bound |
| at parse time. It is the responsibility of the `FunctionMapper` class to |
| provide the mapping of namespace-qualified functions to static methods |
| of specific classes when expressions are created. If no `FunctionMapper` |
| is provided (by passing in `null`), functions are disabled. |
| |
| === Variables |
| |
| Just like `FunctionMapper` provides a |
| flexible mechanism to add functions to the EL, `VariableMapper` provides |
| a flexible mechanism to support the notion of EL variables. An EL |
| variable does not directly refer to a model object that can then be |
| resolved by an `ELResolver`. Instead, an EL variable refers to an EL |
| expression. The evaluation of that EL expression yields the value |
| associated with the EL variable. |
| |
| EL variables are mapped, resolved and bound |
| at parse time. It is the responsibility of the `VariableMapper` class to |
| provide the mapping of EL variables to ``ValueExpression``s when |
| expressions are created. If no `VariableMapper` is provided (by passing |
| in `null`), variable mapping is disabled. |
| |
| See the `jakarta.el` package description for more details. |
| |
| === Lambda Expressions |
| |
| A lambda expression is a `ValueExpression` with |
| parameters. The syntax is similar to the lambda expression in the Java |
| Language, except that in EL, the body of the lambda expression is an EL |
| expression. These are some examples: |
| |
| * `x\->x+1` |
| |
| * `(x,y)\->x+y` |
| |
| * `()\->64` |
| |
| The identifiers to the left of `\->` are lambda |
| parameters. The parenthesis is optional if and only if there is one |
| parameter. |
| |
| A lambda expression behaves like a function. |
| It can be invoked immediately: |
| |
| * `((x,y)\->x+y)(3,4)` evaluates to `7` |
| |
| When a lambda expression is assigned, it can |
| be referenced and invoked indirectly: |
| |
| * `v = (x,y)\->x+y; v(3,4)` evaluates to `7` |
| |
| * `fact = n \-> n==0? 1: n*fact(n-1); fact(5)` evaluates to `120` |
| |
| It can also be passed as an argument to a |
| method, and be invoked in the method, by invoking |
| `jakarta.el.LambdaExpression.invoke()`, such as: |
| |
| * `employees.where(e\->e.firstName == 'Bob')` |
| |
| When a lambda expression is invoked, the |
| expression in the body is evaluated, with its formal parameters replaced |
| by the arguments supplied at the invocation. The number of arguments |
| must be equal to or more than the number the formal parameters. Any |
| extra arguments are ignored. |
| |
| A lambda expression can be nested within another lambda expression, like: |
| |
| * `customers.select(c\->[c.name, c.orders.sum(o\->o.total)])` |
| |
| The scope of a lambda argument is the body of |
| the lambda expression. A lambda argument hides other EL variables, |
| identifiers or arguments of the nesting lambda expressions, of the same |
| name. |
| |
| Note that in the case of nested lambda |
| expressions where the body of the inner lambda expression contains |
| references to parameters of outer lambda expressions, such as: |
| |
| * `x\->y\->x+y` |
| |
| the scope of the outer lambda parameters |
| extends to cover the inner body. For instance, with the above example, |
| the argument `x` must be in scope when `x+y` is evaluated, even though the |
| body of the outer lambda expression has already been executed. |
| |
| === Enums |
| |
| The Unified EL supports Java enumerated |
| types. Coercion rules for dealing with enumerated types are included in |
| the following section. Also, when referring to values that are instances |
| of an enumerated type from within an EL expression, use the literal |
| string value to cause coercion to happen via the below rules. For |
| example, let’s say we have an enum called `Suit` that has members `Heart`, |
| `Diamond`, `Club`, and `Spade`. Furthermore, let’s say we have a reference in |
| the EL, `mySuit`, that is a `Spade`. If you want to test for equality with |
| the `Spade` enum, you would say `${mySuit == 'Spade'}`. The type of the |
| `mySuit` will trigger the invocation of `Enum.valueOf(Suit.class, 'Spade')`. |
| |
| === Static Field and Method Reference |
| |
| A static field or static method of a Java |
| class can be referenced with the syntax _classname.field_, such as: |
| |
| * `Boolean.TRUE` |
| |
| the classname is the name of a class, without |
| the package name. |
| |
| An enum constant is a public static field, so |
| the same syntax can be used to refer to an enum constant, like the |
| following: |
| |
| * `RoundingMode.FLOOR` |
| |
| ==== Access Restrictions and Imports |
| |
| For security, the following restrictions are |
| enforced. |
| |
| . Only the public static fields and methods can be referenced. |
| |
| . Static fields cannot be modified. |
| |
| . Except for classes with `java.lang.*` package |
| names, a class has to be explicitly imported before its static fields or |
| methods can be referenced. |
| |
| ==== Imports of Packages, Classes, and Static Fields |
| |
| Either a class or a package can be explicitly |
| imported into the EL evaluation environment. Importing a package imports |
| all the public, concrete classes in the package. The classes that can be |
| imported are restricted to the classes that can be loaded by the current |
| class loader. |
| |
| By default, the following packages are |
| imported by the EL environment: |
| |
| * `java.lang.*` |
| |
| A static field can also be imported |
| statically. A statically imported static field can be referenced by the |
| field name, without the class name. |
| |
| The imports of packages, classes, and static |
| fields are handled by the `ImportHandler` in the `ELContext`. |
| |
| ==== Constructor Reference |
| |
| A class name reference, followed by arguments in parenthesis, such as: |
| |
| * `Boolean(true)` |
| |
| denotes the invocation of the constructor of |
| the class with the supplied arguments. The same restrictions (the class |
| must be public and has already been imported) for static methods apply |
| to the constructor calls. |
| |
| === Type Conversion |
| |
| Every expression is evaluated in the context |
| of an expected type. The result of the expression evaluation may not |
| match the expected type exactly, so the rules described in the following |
| sections are applied. |
| |
| Custom type conversions can be specified in |
| an `ELResolver` by implementing the method `convertToType`. More than one |
| `ELResolver` can be specified for performing custom conversions, and they |
| are selected and applied in the order of their positions in the |
| `ELResolver` chain, as usual. |
| |
| During expression evaluations, the custom |
| type converters are first selected and applied. If there is no custom |
| type converter for the conversion, the default conversions specified in |
| the following sections are used. |
| |
| ==== To Coerce a Value `X` to Type `Y` |
| |
| * If `X` is `null` and `Y` is not a primitive type and also not a `String`, |
| return `null` |
| |
| * If `X` is of a primitive type, Let `X’` be the equivalent "boxed form" |
| of `X` + |
| Otherwise, Let `X’` be the same as `X` |
| |
| * If `Y` is of a primitive type, Let `Y’` be the equivalent "boxed form" |
| of `Y` + |
| Otherwise, Let `Y’` be the same as `Y` |
| |
| * Apply the rules in Sections |
| <<Coerce `A` to `String`>> to <<Coerce `A` to Any Other Type `T`>> |
| for coercing `X’` to `Y’` |
| |
| * If `Y` is a primitive type, then the result |
| is found by "unboxing" the result of the coercion. If the result of the |
| coercion is `null`, then error |
| |
| * If `Y` is not a primitive type, then the |
| result is the result of the coercion |
| |
| For example, if coercing an `int` to a `String`, "box" the `int` into an |
| `Integer` and apply the rule for coercing an `Integer` to a `String`. Or |
| if coercing a `String` to a `double`, apply the rule for coercing a |
| `String` to a `Double`, then "unbox" the resulting `Double`, making sure |
| the resulting `Double` isn’t actually `null`. |
| |
| ==== Coerce `A` to `String` |
| |
| * If `A` is `null` , return `""` |
| |
| * Otherwise, if `A` is `String`, return `A` |
| |
| * Otherwise, if `A` is `Enum`, return `A.name()` |
| |
| * Otherwise, if `A.toString()` throws an exception, error |
| |
| * Otherwise, return `A.toString()` |
| |
| ==== Coerce `A` to `Number` type `N` |
| |
| * If `A` is `null` and `N` is not a primitive type, return `null` |
| |
| * If `A` is `null` or `""`, return `0` |
| |
| * If `A` is `Character`, convert `A` to |
| `new Short\((short)a.charValue())`, and apply the following rules |
| |
| * If `A` is `Boolean`, then error |
| |
| * If `A` is `Number` type `N`, return `A` |
| |
| * If `A` is `Number`, coerce quietly to type |
| `N` using the following algorithm: |
| |
| ** If `N` is `BigInteger`: |
| |
| *** If `A` is a `BigDecimal`, return `A.toBigInteger()` |
| |
| *** Otherwise, return `BigInteger.valueOf(A.longValue())` |
| |
| ** If `N` is `BigDecimal`: |
| |
| *** If `A` is a `BigInteger`, return `new BigDecimal(A)` |
| |
| *** Otherwise, return `new BigDecimal(A.doubleValue())` |
| |
| ** If `N` is `Byte`, return `new Byte(A.byteValue())` |
| |
| ** If `N` is `Short`, return `new Short(A.shortValue())` |
| |
| ** If `N` is `Integer`, return `new Integer(A.intValue())` |
| |
| ** If `N` is `Long`, return `new Long(A.longValue())` |
| |
| ** If `N` is `Float`, return `new Float(A.floatValue())` |
| |
| ** If `N` is `Double`, return `new Double(A.doubleValue())` |
| |
| ** Otherwise, error |
| |
| * If `A` is `String`, then: |
| |
| ** If `N` is `BigDecimal` then: |
| |
| *** If `new BigDecimal(A)` throws an exception then error |
| |
| *** Otherwise, return `new BigDecimal(A)` |
| |
| ** If `N` is `BigInteger` then: |
| |
| *** If `new BigInteger(A)` throws an exception then error |
| |
| *** Otherwise, return `new BigInteger(A)` |
| |
| ** If `N.valueOf(A)` throws an exception, then error |
| |
| ** Otherwise, return `N.valueOf(A)` |
| |
| * Otherwise, error |
| |
| ==== Coerce `A` to `Character` or `char` |
| |
| * If `A` is `null` and the target type is not the primitive type `char`, |
| return `null` |
| |
| * If `A` is `null` or `""`, return `(char)0` |
| |
| * If `A` is `Character`, return `A` |
| |
| * If `A` is `Boolean`, error |
| |
| * If `A` is `Number`, coerce quietly to type |
| `Short`, then return a `Character` whose numeric value is equivalent to |
| that of a `Short` |
| |
| * If `A` is `String`, return `A.charAt(0)` |
| |
| * Otherwise, error |
| |
| |
| |
| ==== Coerce `A` to `Boolean` or `boolean` |
| |
| * If `A` is `null` and the target type is not the |
| primitive type `boolean`, return `null` |
| |
| * If `A` is `null` or `""`, return `false` |
| |
| * Otherwise, if `A` is a `Boolean`, return `A` |
| |
| * Otherwise, if `A` is a `String`, and |
| `Boolean.valueOf(A)` does not throw an exception, return it |
| |
| * Otherwise, error |
| |
| ==== Coerce `A` to an `Enum` Type `T` |
| |
| * If `A` is `null`, return `null` |
| |
| * If `A` is assignable to `T`, coerce quietly |
| |
| * If `A` is `""`, return `null` |
| |
| * If `A` is a `String` call `Enum.valueOf(T.getClass(), A)` and return |
| the result |
| |
| ==== Coerce `A` to Any Other Type `T` |
| |
| * If `A` is `null`, return `null` |
| |
| * If `A` is assignable to `T`, coerce quietly |
| |
| * If `A` is a `String`, and `T` has no `PropertyEditor:` |
| |
| ** If `A` is `""`, return `null` |
| |
| ** Otherwise error |
| |
| * If `A` is a `String` and ``T``'s `PropertyEditor` throws an exception: |
| |
| ** If `A` is `""`, return `null` |
| |
| ** Otherwise, error |
| |
| * Otherwise, apply ``T``'s `PropertyEditor` |
| |
| * Otherwise, error |
| |
| |
| |
| === Collected Syntax |
| |
| The following is a javaCC grammar with syntax |
| tree generation. It is meant to be used as a guide and reference only. |
| |
| [source] |
| ---- |
| /* == Option Declaration == */ |
| options |
| { |
| STATIC=false; |
| NODE_PREFIX="Ast"; |
| VISITOR_EXCEPTION="ELException"; |
| VISITOR=false; |
| MULTI=true; |
| NODE_DEFAULT_VOID=true; |
| JAVA_UNICODE_ESCAPE=false; |
| UNICODE_INPUT=true; |
| BUILD_NODE_FILES=true; |
| } |
| |
| /* == Parser Declaration == */ |
| PARSER_BEGIN( ELParser ) |
| package com.sun.el.parser; |
| import java.io.StringReader; |
| import ELException; |
| public class ELParser |
| { |
| public static Node parse(String ref) throws ELException |
| { |
| try { |
| return (new ELParser(new StringReader(ref))).CompositeExpression(); |
| } catch (ParseException pe) { |
| throw new ELException(pe.getMessage()); |
| } |
| } |
| } |
| PARSER_END( ELParser ) |
| |
| /* |
| * CompositeExpression |
| * Allow most flexible parsing, restrict by examining |
| * type of returned node |
| */ |
| AstCompositeExpression CompositeExpression() #CompositeExpression : {} |
| { |
| (DeferredExpression() | DynamicExpression() | LiteralExpression())* <EOF> |
| { |
| return jjtThis; |
| } |
| } |
| |
| /* |
| * LiteralExpression |
| * Non-EL Expression blocks |
| */ |
| void LiteralExpression() #LiteralExpression : { Token t = null; } |
| { |
| t=<LITERAL_EXPRESSION> { jjtThis.setImage(t.image); } |
| } |
| |
| /* |
| * DeferredExpression |
| * #{...} Expressions |
| */ |
| void DeferredExpression() #DeferredExpression : {} |
| { |
| <START_DEFERRED_EXPRESSION> Expression() <RCURL> |
| } |
| |
| /* |
| * DynamicExpression |
| * ${...} Expressions |
| */ |
| void DynamicExpression() #DynamicExpression : {} |
| { |
| <START_DYNAMIC_EXPRESSION> Expression() <RCURL> |
| } |
| |
| /* |
| * Expression |
| * EL Expression Language Root |
| */ |
| void Expression() : {} |
| { |
| SemiColon() |
| } |
| |
| /* |
| * SemiColon |
| */ |
| void SemiColon() : {} |
| { |
| Assignment() (<SEMICOLON> Assignment() #SemiColon(2) )* |
| } |
| |
| /* |
| * Assignment |
| * For '=', right associatve, then LambdaExpression or Choice or Assignment |
| */ |
| void Assignment() : {} |
| { |
| LOOKAHEAD(4) LambdaExpression() | |
| Choice() ( LOOKAHEAD(2) <ASSIGN> Assignment() #Assign(2) )* |
| } |
| |
| /* |
| * LambdaExpression |
| */ |
| void LambdaExpression() #LambdaExpression : {} |
| { |
| LambdaParameters() <ARROW> |
| (LOOKAHEAD(3) LambdaExpression() | Choice() ) |
| } |
| |
| void LambdaParameters() #LambdaParameters: {} |
| { |
| Identifier() |
| | <LPAREN> |
| (Identifier() (<COMMA> Identifier())*)? |
| <RPAREN> |
| } |
| |
| /* |
| * Choice |
| * For Choice markup a ? b : c, right associative |
| */ |
| void Choice() : {} |
| { |
| Or() (<QUESTIONMARK> Choice() <COLON> Choice() #Choice(3))? |
| } |
| |
| /* |
| * Or |
| * For 'or' '||', then And |
| */ |
| void Or() : {} |
| { |
| And() ((<OR0>|<OR1>) And() #Or(2))* |
| } |
| |
| /* |
| * And |
| * For 'and' '&&', then Equality |
| */ |
| void And() : {} |
| { |
| Equality() ((<AND0>|<AND1>) Equality() #And(2))* |
| } |
| |
| /* |
| * Equality |
| * For '==' 'eq' '!=' 'ne', then Compare |
| */ |
| void Equality() : {} |
| { |
| Compare() |
| ( |
| ((<EQ0>|<EQ1>) Compare() #Equal(2)) |
| | |
| ((<NE0>|<NE1>) Compare() #NotEqual(2)) |
| )* |
| } |
| |
| /* |
| * Compare |
| * For a bunch of them, then Math |
| */ |
| void Compare() : {} |
| { |
| Concatenation() |
| ( |
| ((<LT0>|<LT1>) Concatenation() #LessThan(2)) |
| | |
| ((<GT0>|<GT1>) Concatenation() #GreaterThan(2)) |
| | |
| ((<LE0>|<LE1>) Concatenation() #LessThanEqual(2)) |
| | |
| ((<GE0>|<GE1>) Concatenation() #GreaterThanEqual(2)) |
| )* |
| } |
| |
| /* |
| * Concatenation |
| * For '&', then Math() |
| */ |
| void Concatenation() : {} |
| { |
| Math() ( <CONCAT> Math() #Concat(2) )* |
| } |
| |
| /* |
| * Math |
| * For '+' '-', then Multiplication |
| */ |
| void Math() : {} |
| { |
| Multiplication() |
| ( |
| (<PLUS> Multiplication() #Plus(2)) |
| | |
| (<MINUS> Multiplication() #Minus(2)) |
| )* |
| } |
| |
| /* |
| * Multiplication |
| * For a bunch of them, then Unary |
| */ |
| void Multiplication() : {} |
| { |
| Unary() |
| ( |
| (<MULT> Unary() #Mult(2)) |
| | |
| ((<DIV0>|<DIV1>) Unary() #Div(2)) |
| | |
| ((<MOD0>|<MOD1>) Unary() #Mod(2)) |
| )* |
| } |
| |
| /* |
| * Unary |
| * For '-' '!' 'not' 'empty', then Value |
| */ |
| void Unary() : {} |
| { |
| <MINUS> Unary() #Negative |
| | |
| (<NOT0>|<NOT1>) Unary() #Not |
| | |
| <EMPTY> Unary() #Empty |
| | |
| Value() |
| } |
| |
| /* |
| * Value |
| * Defines Prefix plus zero or more Suffixes |
| */ |
| void Value() : {} |
| { |
| (ValuePrefix() (ValueSuffix())*) #Value(>1) |
| } |
| |
| /* |
| * ValuePrefix |
| * For Literals, Variables, and Functions |
| */ |
| void ValuePrefix() : {} |
| { |
| Literal() | NonLiteral() |
| } |
| |
| /* |
| * ValueSuffix |
| * Either dot or bracket notation |
| */ |
| void ValueSuffix() : {} |
| { |
| DotSuffix() | BracketSuffix() |
| } |
| |
| /* |
| * DotSuffix |
| * Dot Property and Dot Method |
| */ |
| void DotSuffix() #DotSuffix : { Token t = null; } |
| { |
| <DOT> t=<IDENTIFIER> { jjtThis.setImage(t.image); } |
| (MethodArguments())? |
| } |
| |
| /* |
| * BracketSuffix |
| * Sub Expression Suffix |
| */ |
| void BracketSuffix() #BracketSuffix : {} |
| { |
| <LBRACK> Expression() <RBRACK> |
| (MethodArguments())? |
| } |
| |
| |
| /* |
| * MethodArguments |
| */ |
| void MethodArguments() #MethodArguments : {} |
| { |
| <LPAREN> (Expression() (<COMMA> Expression())*)? <RPAREN> |
| } |
| |
| /* |
| * Parenthesized Lambda Expression, with optional invokation |
| */ |
| void LambdaExpressionOrCall() #LambdaExpression : {} |
| |
| { |
| <LPAREN> |
| LambdaParameters() <ARROW> |
| (LOOKAHEAD(3) LambdaExpression() | Choice() ) |
| <RPAREN> |
| (MethodArguments())* |
| } |
| |
| /* |
| * NonLiteral |
| * For Grouped Operations, Identifiers, and Functions |
| */ |
| void NonLiteral() : {} |
| { |
| LOOKAHEAD(5) LambdaExpressionOrCall() // check beyond the arrow |
| | <LPAREN> Expression() <RPAREN> |
| | LOOKAHEAD(4) Function() |
| | Identifier() |
| | MapData() |
| | ListData() |
| } |
| |
| void MapData() #MapData: {} |
| { |
| <START_MAP> |
| ( MapEntry() ( <COMMA> MapEntry() )* )? |
| <RCURL> |
| } |
| |
| void MapEntry() #MapEntry: {} |
| { |
| Expression() (<COLON> Expression())? |
| } |
| |
| void ListData() #ListData: {} |
| { |
| <LBRACK> |
| ( Expression() ( <COMMA> Expression() )* )? |
| <RBRACK> |
| } |
| |
| /* |
| * Identifier |
| * Java Language Identifier |
| */ |
| void Identifier() #Identifier : { Token t = null; } |
| { |
| t=<IDENTIFIER> { jjtThis.setImage(t.image); } |
| } |
| |
| /* |
| * Function |
| * Namespace:Name(a,b,c) |
| */ |
| void Function() #Function : |
| { |
| Token t0 = null; |
| Token t1 = null; |
| } |
| { |
| t0=<IDENTIFIER> (<COLON> t1=<IDENTIFIER>)? |
| { |
| if (t1 != null) { |
| jjtThis.setPrefix(t0.image); |
| jjtThis.setLocalName(t1.image); |
| } else { |
| jjtThis.setLocalName(t0.image); |
| } |
| } |
| (MethodArguments())+ |
| } |
| |
| |
| /* |
| * Literal |
| * Reserved Keywords |
| */ |
| void Literal() : {} |
| { |
| Boolean() |
| | FloatingPoint() |
| | Integer() |
| | String() |
| | Null() |
| } |
| |
| /* |
| * Boolean |
| * For 'true' 'false' |
| */ |
| void Boolean() : {} |
| { |
| <TRUE> #True |
| | <FALSE> #False |
| } |
| |
| /* |
| * FloatingPoint |
| * For Decimal and Floating Point Literals |
| */ |
| void FloatingPoint() #FloatingPoint : { Token t = null; } |
| { |
| t=<FLOATING_POINT_LITERAL> { jjtThis.setImage(t.image); } |
| } |
| |
| /* |
| * Integer |
| * For Simple Numeric Literals |
| */ |
| void Integer() #Integer : { Token t = null; } |
| { |
| t=<INTEGER_LITERAL> { jjtThis.setImage(t.image); } |
| } |
| |
| /* |
| * String |
| * For Quoted Literals |
| */ |
| void String() #String : { Token t = null; } |
| { |
| t=<STRING_LITERAL> { jjtThis.setImage(t.image); } |
| } |
| |
| /* |
| * Null |
| * For 'null' |
| */ |
| void Null() #Null : {} |
| { |
| <NULL> |
| } |
| |
| |
| /* ========================================================================== */ |
| TOKEN_MGR_DECLS: |
| { |
| java.util.Stack<Integer> stack = new java.util.Stack<Integer>(); |
| } |
| |
| <DEFAULT> TOKEN : |
| { |
| < LITERAL_EXPRESSION: |
| ((~["\\", "$", "#"]) |
| | ("\\" ("\\" | "$" | "#")) |
| | ("$" ~["{", "$", "#"]) |
| | ("#" ~["{", "$", "#"]) |
| )+ |
| | "$" |
| | "#" |
| > |
| | |
| < START_DYNAMIC_EXPRESSION: "${" > {stack.push(DEFAULT);}: IN_EXPRESSION |
| | |
| < START_DEFERRED_EXPRESSION: "#{" > {stack.push(DEFAULT);}: IN_EXPRESSION |
| } |
| |
| <DEFAULT> SKIP : { "\\" } |
| |
| <IN_EXPRESSION, IN_MAP> SKIP: |
| { " " | "\t" | "\n" | "\r" } |
| |
| <IN_EXPRESSION, IN_MAP> TOKEN : |
| { |
| < START_MAP : "{" > {stack.push(curLexState);}: IN_MAP |
| | < RCURL: "}" > {SwitchTo(stack.pop());} |
| | < INTEGER_LITERAL: ["0"-"9"] (["0"-"9"])* > |
| | < FLOATING_POINT_LITERAL: (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? |
| | "." (["0"-"9"])+ (<EXPONENT>)? |
| | (["0"-"9"])+ <EXPONENT> |
| > |
| | < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > |
| | < STRING_LITERAL: ("\"" ((~["\"","\\"]) |
| | ("\\" ( ["\\","\""] )))* "\"") |
| | ("\'" ((~["\'","\\"]) |
| | ("\\" ( ["\\","\'"] )))* "\'") |
| > |
| | < BADLY_ESCAPED_STRING_LITERAL: ("\"" (~["\"","\\"])* ("\\" ( ~["\\","\""] ))) |
| | ("\'" (~["\'","\\"])* ("\\" ( ~["\\","\'"] ))) |
| > |
| | < TRUE : "true" > |
| | < FALSE : "false" > |
| | < NULL : "null" > |
| | < DOT : "." > |
| | < LPAREN : "(" > |
| | < RPAREN : ")" > |
| | < LBRACK : "[" > |
| | < RBRACK : "]" > |
| | < COLON : ":" > |
| | < COMMA : "," > |
| | < SEMICOLON : ";" > |
| | < GT0 : ">" > |
| | < GT1 : "gt" > |
| | < LT0 : "<" > |
| | < LT1 : "lt" > |
| | < GE0 : ">=" > |
| | < GE1 : "ge" > |
| | < LE0 : "\<=" > |
| | < LE1 : "le" > |
| | < EQ0 : "==" > |
| | < EQ1 : "eq" > |
| | < NE0 : "!=" > |
| | < NE1 : "ne" > |
| | < NOT0 : "!" > |
| | < NOT1 : "not" > |
| | < AND0 : "&&" > |
| | < AND1 : "and" > |
| | < OR0 : "||" > |
| | < OR1 : "or" > |
| | < EMPTY : "empty" > |
| | < INSTANCEOF : "instanceof" > |
| | < MULT : "*" > |
| | < PLUS : "+" > |
| | < MINUS : "-" > |
| | < QUESTIONMARK : "?" > |
| | < DIV0 : "/" > |
| | < DIV1 : "div" > |
| | < MOD0 : "%" > |
| | < MOD1 : "mod" > |
| | < CONCAT : "+=" > |
| | < ASSIGN : "=" > |
| | < ARROW : "->" > |
| | < IDENTIFIER : (<LETTER>|<IMPL_OBJ_START>) (<LETTER>|<DIGIT>)* > |
| | < #IMPL_OBJ_START: "#" > |
| | < #LETTER: |
| [ |
| "\u0024", |
| "\u0041"-"\u005a", |
| "\u005f", |
| "\u0061"-"\u007a", |
| "\u00c0"-"\u00d6", |
| "\u00d8"-"\u00f6", |
| "\u00f8"-"\u00ff", |
| "\u0100"-"\u1fff", |
| "\u3040"-"\u318f", |
| "\u3300"-"\u337f", |
| "\u3400"-"\u3d2d", |
| "\u4e00"-"\u9fff", |
| "\uf900"-"\ufaff" |
| ] |
| > |
| | < #DIGIT: |
| [ |
| "\u0030"-"\u0039", |
| "\u0660"-"\u0669", |
| "\u06f0"-"\u06f9", |
| "\u0966"-"\u096f", |
| "\u09e6"-"\u09ef", |
| "\u0a66"-"\u0a6f", |
| "\u0ae6"-"\u0aef", |
| "\u0b66"-"\u0b6f", |
| "\u0be7"-"\u0bef", |
| "\u0c66"-"\u0c6f", |
| "\u0ce6"-"\u0cef", |
| "\u0d66"-"\u0d6f", |
| "\u0e50"-"\u0e59", |
| "\u0ed0"-"\u0ed9", |
| "\u1040"-"\u1049" |
| ] |
| > |
| | < ILLEGAL_CHARACTER: (~[]) > |
| } |
| ---- |
| |
| Notes |
| |
| * `*` = 0 or more, `+` = 1 or more, `?` = 0 or 1 |
| |
| * An identifier is constrained to be a Java identifier - e.g., no `-`, |
| no `/`, etc. |
| |
| * A `String` only recognizes a limited set of escape sequences, and `\` |
| may not appear unescaped |
| |
| * The relational operator for equality is `==` (double equals) |
| |
| * The value of an `IntegerLiteral` ranges from `Long.MIN_VALUE` to |
| `Long.MAX_VALUE` |
| |
| * The value of a `FloatingPointLiteral` ranges from |
| `Double.MIN_VALUE` to `Double.MAX_VALUE` |
| |
| * It is illegal to nest `${` or `\#{` inside |
| an outer `${` or `#{` |
| |
| == Operations on Collection Objects |
| |
| This chapter describes how collection objects |
| and literals can be constructed in the EL expression, and how collection |
| objects can be manipulated and processed by applying operations in a |
| pipeline. |
| |
| === Overview |
| |
| To provide support for collection objects, EL |
| includes syntaxes for constructing sets, lists, and maps dynamically. |
| Any EL expressions, not just literals, can be used in the construction. |
| |
| EL also includes a set of operations that can |
| be applied on collections. By design, the methods supporting these |
| operations have names and semantics very similar to those in Java SE 8 |
| libraries. Since EL and Java have different syntaxes and capabilities, |
| they are not identical, but they are similar enough that users should |
| have no problem switching from one to the other. |
| |
| Since the methods supporting the collection |
| operations do not exist in Java SE 7, they are implemented in the |
| Expression Language with ``ELResolver``s. In an EL expression, collection |
| operations are carried out by invoking methods, and no special syntaxes |
| are introduced for them. Strictly speaking, these operations are not |
| part of the expression language, and can be taken as examples of what |
| can be achieved with the expression language. The specification |
| specifies the syntaxes and behaviors of a standard set of collection |
| operations. However, an user can easily add, extend and modify the |
| behavior of the operations by providing customized ``ELResolver``s. |
| |
| Compared to Java SE 8, the collection support |
| in EL has a much smaller and simpler scope. Although EL does not |
| disallow collections of infinite size, it works best when the collection |
| objects are created in memory, with known sizes. It also does not |
| address the performance issue in a multi-threaded environment, and does |
| not provide explicit controls for evaluating collection operations in |
| parallel. A future version of EL will likely include functionalities |
| from Java SE 8. |
| |
| Central to the implementation is the use of |
| lambda expressions, now supported in EL. A lambda expression in the Java |
| language is used to specify a method in an anonymous implementation of a |
| functional interface. The concept of a lambda expression in EL is much |
| simpler: it is just an anonymous function that can be passed as an |
| argument to a method, to be evaluated in the method when needed. In the |
| collection operations, lambda expressions are specified as arguments to |
| the methods supporting the operations. Usually when the lambda |
| expressions are invoked, an element from stream of the collection is |
| passed as an argument to the lambda expression. For instance, the |
| argument to the `filter` method is a lambda expression which acts as a |
| predicate function to determine if an element should be included in the |
| resulting stream. |
| |
| === Construction of Collection Objects |
| |
| EL allows the construction of sets, lists, |
| and maps dynamically. Any EL expressions, including nested collection |
| constructions, can be used in the construction. These expressions are |
| evaluated at the time of the construction. |
| |
| ==== Set Construction |
| |
| Construct an instance of `java.util.Set<Object>`. |
| |
| ===== Syntax |
| |
| `SetData := '{' DataList '}'` |
| |
| `DataList := (expression (',' expression)* )?` |
| |
| ===== Example |
| |
| `{1, 2, 3}` |
| |
| ==== List Construction |
| |
| Construct an instance of `java.util.List<Object>`. |
| |
| ===== Syntax |
| |
| `ListData := '[' DataList ']'` |
| |
| `DataList := (expression (',' expression)* )?` |
| |
| ===== Example |
| |
| `[1, "two", [foo, bar]]` |
| |
| ==== Map Construction |
| |
| Construct an instance of `java.util.Map<Object,Object>`. |
| |
| ===== Syntax |
| |
| `Map := '{' MapEntries '}'` |
| |
| `MapEntries := (MapEntry (',' MapEntry)* )?` |
| |
| `MapEntry := expression ':' expression` |
| |
| ===== Example |
| |
| `{"one":1, "two":2, "three":3}` |
| |
| === Collection Operations |
| |
| ==== Stream and Pipeline |
| |
| The operations on a collection object are |
| realized as method calls to the stream of elements derived from the |
| collection. The method `stream` can be used to obtain a `Stream` from a |
| `java.util.Collection` or a Java array. |
| |
| To obtain a `Stream` from a `Map`, the collection view of a `Map`, |
| such as `MapEntry` can be used as the source of `Stream`. |
| |
| Some operations return another `Stream`, which |
| allows other operations. Therefore the operations can be chained |
| together to form a pipeline. For example, to get a list of titles of |
| history books, one can write in EL: |
| |
| ---- |
| books.stream().filter(b->b.category == 'history’) |
| .map(b->b.title) |
| .toList() |
| ---- |
| |
| A stream pipeline consists of: |
| |
| * the source; |
| |
| * intermediate operations; and |
| |
| * a terminal operation. |
| |
| The source of a pipeline is the `Stream` object. |
| |
| An intermediate operation is a method in |
| `Stream` that returns a `Stream`. A pipeline may contain zero or more |
| intermediate operations. |
| |
| A pipeline ends in a terminal operation. A terminal operation is a |
| method in `Stream` that does not return a `Stream`. |
| |
| The execution of a pipeline only begins when |
| the terminal operation starts its execution. Most of the intermediate |
| operations are evaluated lazily: they only yield as many elements in the |
| stream as are required by the downstream operations. Because of this, |
| they need not keep intermediate results of the operations. For instance, |
| the filter operation does not keep a collection of the filtered |
| elements. |
| |
| A notable exception is the sorted operation, |
| since all elements are needed for sorting. |
| |
| The specification specifies the behavior of |
| the operations in a pipeline, and does not specify the implementation of |
| a pipeline. The operations must not modify the source collection. The |
| user must also make sure that the source collection is not modified |
| externally during the execution of the pipeline, otherwise the behavior |
| of the collection operations will be undefined. |
| |
| The behavior of the operations are undefined |
| if the collection contains null elements. Null elements in a collection |
| should be removed by a filter to obtain consistent results. |
| |
| The source stream in a pipeline that has |
| already started its execution cannot be used in another pipeline, |
| otherwise the behavior is undefined. |
| |
| ==== Operation Syntax Description |
| |
| The implementation of `Stream` that contains |
| the methods supporting the operations are not part of the API. The |
| syntax and the behavior of the operations are described in this chapter. |
| |
| For documentation purposes, pseudo method |
| declarations are used in this chapter for the operations. A method |
| includes: |
| |
| * The return type |
| |
| * The type of the source stream |
| |
| * The method name |
| |
| * The method parameters |
| |
| A typical method declaration would looks like: |
| |
| * `returnT Stream<T>.method(T1 arg1, T2 arg2)` |
| |
| Some methods have optional parameters. The |
| declarations of the methods with all possible combinations of the |
| parameters are listed in the syntax sections, as if they are overloaded. |
| Any `null` parameter will result in a `NullPointerException` at run-time. |
| |
| Some of the parameters are lambda |
| expressions, also known as functions. A lambda expression can have its |
| own parameters and can return a value. To describe the parameter types |
| and the return type of a lambda expression, the following is an example |
| of the notation that is used: |
| |
| * `(p1,p2)\->returnT` |
| |
| For instance, the declaration for the operation filter is: |
| |
| * `Stream<S> Stream<S>.filter((S\->boolean) predicate)` |
| |
| From this we know that the source object is a `Stream` of `S`, and the |
| return object is also a `Stream`, of the same type. |
| The operator takes a predicate function (lambda expression) as an |
| argument. The argument of the function is an element of the source, and |
| the function returns a boolean. |
| |
| The generic types in the declaration are used |
| only to help the readers to identify the type relationships among |
| various parts of the declaration, and do not have the same meaning as |
| used in the Java language. At runtime, EL deals with Objects, and does |
| not track generic types. |
| |
| ==== Implementation Classes |
| |
| The specification makes references to some |
| implementation classes that are not in the API. They contains methods |
| whose behaviors are specified in this section. |
| |
| ===== Stream |
| |
| An instance of `Stream` is obtained by calling the method `stream()` of |
| a `java.util.Collection` object or a Java array. |
| The methods in this class support the stream operations and are |
| described in <<filter,2.3.5>> to <<findFirst,2.3.26>>. |
| |
| ===== Optional |
| |
| An `Optional` is used to represent a value that |
| may not exist. Instead of using `null` as a default value, the use of |
| `Optional` allows the user to specify a default. |
| |
| A non-existing or empty value is represented |
| by an empty `Optional`. |
| |
| An `Optional` is usually the result of a computation over the elements |
| of a `Stream`, where an empty `Stream` results |
| in an empty `Optional`. See for example, <<max>>. |
| |
| The following are methods in `Optional<T>`. |
| |
| * `T get()` + |
| Returns the value held by the `Optional`, or |
| throws an `ELException` if the `Optional` is empty. |
| |
| * `void ifPresent((x)\->void) consumer)` + |
| The value held by the `Optional` is processed |
| by the function consumer if it is not empty. See also |
| <<consumer>>. |
| |
| * `T orElse(T other)` + |
| Returns the value held by the `Optional`, or |
| the value other if the `Optional` is empty. |
| |
| * `T orElseGet((()\->T) other)` + |
| Returns the value held by the `Optional`, or the value returned by the |
| lambda expression `other` if the `Optional` is empty. |
| |
| ==== Functions |
| |
| Some operations takes functions (lambda |
| expressions) as parameters. Again, we use the notation: |
| |
| `(arg1Type, ...)\->returnType` |
| |
| to describe the argument types and the return type of a function. |
| |
| ===== predicate |
| |
| `S \-> boolean` |
| |
| This function takes the input argument, usually the element of the |
| source stream, and determines if it satisfies some criteria. |
| |
| ===== mapper |
| |
| `S \-> R` |
| |
| This function maps, or transforms the input |
| argument, usually the element of the source stream, to the result. |
| |
| ===== comparator |
| |
| `(S, S) \-> int` |
| |
| This function compares two arguments, usually |
| the elements of the source stream, and returns a negative integer, zero, |
| or a positive integer, if the first argument is respectively less than, |
| equal to, or greater than the second argument. |
| |
| ===== consumer |
| |
| `S \-> void` |
| |
| This function processes the input argument, |
| usually the element of the source stream, and returns nothing. |
| |
| ===== binaryOperator |
| |
| `(S, S) \-> S` |
| |
| This function applies a binary operation to |
| the input arguments, and returns the result. The first argument is |
| usually an internal accumulator value, and the second argument is |
| usually the element of the source stream. |
| |
| The arguments and the result are of the same |
| type. |
| |
| ==== filter |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.filter((S\->boolean) predicate)` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| source stream elements for which the `predicate` function returns `true`. |
| The argument of `predicate` function represents the element to test. |
| |
| ===== See |
| |
| <<predicate>> |
| |
| ===== Example |
| |
| To find the products whose price is greater than or equal to 10: |
| |
| `products.stream().filter(p\->p.unitPrice >= 10).toList()` |
| |
| ==== map |
| |
| ===== Syntax |
| |
| `Stream<R> Stream<S>.map((S\->R) mapper)` |
| |
| ===== Description |
| |
| This method produces a stream by applying the |
| `mapper` function to the elements of the source stream. The argument of |
| `mapper` function represents the element to process, and the result of the |
| `mapper` function represents the element of the resulting `Stream`. |
| |
| ===== See |
| |
| <<mapper>> |
| |
| ===== Examples |
| |
| To get the list of the names of all products: |
| |
| `products.stream().map(p\->p.name).toList()` |
| |
| To create a list of product names and prices |
| for products with a price greater than or equal to 10: |
| |
| ---- |
| products.stream().filter(p->p.unitPrice >= 10) |
| .map(p->[p.name, p.unitPrice]) |
| .toList() |
| ---- |
| |
| ==== flatMap |
| |
| ===== Syntax |
| |
| `Stream<R> Stream<S>.flatMap((S\->Stream<R>) mapper)` |
| |
| ===== Description |
| |
| This method produces a stream by mapping each |
| of the source elements to another stream and then concatenating the |
| mapped streams. If the mapper function does not return a `Stream`, the |
| behavior is undefined. |
| |
| ===== See |
| |
| <<mapper>> |
| |
| ===== Examples |
| |
| To list all orders of US customers: |
| ---- |
| customers.stream().filter(c->c.country == 'USA') |
| .flatMap(c->c.orders.stream()) |
| .toList() |
| ---- |
| |
| To obtain an alphabetical list of characters used in a list |
| of words: |
| ---- |
| words.stream().flatMap(w->w.toCharArray().stream()) |
| .sorted() |
| .distinct() |
| .toList() |
| ---- |
| |
| ==== distinct |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.distinct()` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| elements of the source stream that are distinct, according to |
| `Object.equals`. |
| |
| ===== Example |
| |
| To remove the duplicate element b: |
| |
| `['a', 'b', 'b', 'c'].stream().distinct().toArray()` |
| |
| ==== sorted |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.sorted()` |
| |
| `Stream<S> Stream<S>.sorted(((p,q)\->int) comparator)` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| elements of the source stream in sorted order. If no `comparator` is |
| specified, the elements are sorted in natural order. The behavior is |
| undefined if no `comparator` is specified, and the elements do not |
| implement `java.lang.Comparable`. If a `comparator` is specified, the |
| elements are sorted with the provided comparator. |
| |
| The source collection is unaffected by this |
| operation. |
| |
| ===== See |
| |
| <<comparator>> |
| |
| ===== Examples |
| |
| To sort a list of integers |
| |
| `[1,3,2,4].stream().sorted().toList()` |
| |
| To sort a list of integers in reversed order |
| |
| `[1,3,2,4].stream().sorted((i,j)\->j-i).List()` |
| |
| To sort a list of words in the order of word |
| length; and then for words of the same length, in alphabetical order: |
| ---- |
| words.stream().sorted( |
| (s,t)->(s.length()==t.length() ? s.compareTo(t) |
| : s.length() - t.length())) |
| .toList() |
| ---- |
| |
| To sort the products by name: |
| |
| `products.stream().sorted\((p,q)\->p.name.compareTo(p.name)).toList()` |
| |
| Or by defining a comparing function, this can |
| be rewritten as: |
| ---- |
| comparing = map->(x,y)->map(x).compareTo(map(y)); |
| products.stream().sorted(comparing(p->p.name)).toList() |
| ---- |
| |
| ==== forEach |
| |
| ===== Syntax |
| |
| `Object stream<S>.forEach(((S)\->void)consumer)` |
| |
| ===== Description |
| |
| This method invokes the `consumer` function for |
| each element in the source stream. |
| |
| This method always returns `null`. |
| |
| ===== See |
| |
| <<consumer>> |
| |
| ===== Example |
| |
| To print a list of customer names: |
| |
| `customers.stream().forEach(c\->printer.print(c.name))` |
| |
| ==== peek |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.peek(((S)\->void)consumer)` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| elements of the source stream, and invokes the `consumer` function for |
| each element in the stream. The primary purpose of this method is for |
| debugging, where one can take a peek at the elements in the stream at |
| the place where this method is inserted. |
| |
| ===== See |
| |
| <<consumer>> |
| |
| ===== Example |
| |
| To print a list of integers before and after a filter: |
| ---- |
| [1,2,3,4,5].stream().peek(i->print(i)) |
| .filter(i-> i%2 == 0) |
| .peek(i->print(i)) |
| .toList() |
| ---- |
| |
| ==== iterator |
| |
| ===== Syntax |
| |
| `Iterator<S> Stream<S>.iterator()` |
| |
| ===== Description |
| |
| This method returns an iterator for the |
| source stream, suitable for use in Java codes. |
| |
| ==== limit |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.limit(Number count)` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| first `count` number of elements of the source stream. |
| |
| If `count` is greater than the number of source |
| elements, all the elements are included in the returned stream. If the |
| `count` is less than or equal to zero, an empty stream is returned. |
| |
| ===== Example |
| |
| To list the 3 least expensive products: |
| ---- |
| products.stream().sorted(p->p.unitPrice) |
| .limit(3) |
| .toList() |
| ---- |
| |
| ==== substream |
| |
| ===== Syntax |
| |
| `Stream<S> Stream<S>.substream(Number start)` |
| |
| `Stream<S> Stream<S>.substream(Number start, Number end)` |
| |
| ===== Description |
| |
| This method produces a stream containing the |
| source elements, skipping the first `start` elements, and including the |
| rest of the elements in the stream if `end` is not specified, or the next |
| `(end - start)` elements in the stream if end is specified. |
| |
| If the elements in the source stream has |
| fewer than `start` elements, nothing is included. If `start` is less than or |
| equal to zero, no elements are skipped. |
| |
| ===== Example |
| |
| The example |
| |
| `[1,2,3,4,5].stream().substream(2,4).toArray()` |
| |
| produces the array `[3,4]`. |
| |
| ==== toArray |
| |
| ===== Syntax |
| |
| `S[] Stream<S>.toArray()` |
| |
| ===== Description |
| |
| This method returns an array containing the |
| elements of the source stream. |
| |
| ==== toList |
| |
| ===== Syntax |
| |
| `List Stream<S>.toList()` |
| |
| ===== Description |
| |
| This method returns a List containing the |
| elements of the source stream. |
| |
| |
| ==== reduce |
| |
| ===== Syntax |
| |
| `Optional<S> Stream<S>.reduce(((S,S)\->S) binaryOperator)` |
| |
| `S Stream<S>.reduce(S seed, ((S,S)\->S) binaryOperator))` |
| |
| ===== Description |
| |
| The method with a `seed` value starts by |
| assigning the `seed` value to an internal accumulator. Then for each of |
| the elements in the source stream, the next accumulator value is |
| computed, by invoking the `binaryOperator` function, with the current |
| accumulator value as the first argument and the current element as the |
| second argument. The final accumulator value is returned. |
| |
| The method without a `seed` value uses the |
| first element of the source elements as the `seed` value. If the source |
| stream is empty, an empty `Optional` is returned, otherwise an `Optional` |
| with the final accumulator value is returned. |
| |
| ===== See |
| |
| <<Optional>> |
| |
| <<binaryOperator>> |
| |
| ===== Example |
| |
| To find tallest student in a class: |
| |
| `students.stream().reduce((p,q)\->(p.height>q.height? p: q).get()` |
| |
| ==== max |
| |
| ===== Syntax |
| |
| `Optional<S> Stream<S>.max()` |
| |
| `Optional<S> Stream<S>.max(((p,q)\->int) comparator)` |
| |
| ===== Description |
| |
| This method computes the maximum of the |
| elements in the source stream. If the `comparator` function is specified, |
| it is used for comparisons. If no `comparator` function is specified, the |
| elements themselves are compared, and must implement `Comparable`, |
| otherwise an `ELException` is thrown. |
| |
| This method returns an empty `Optional` for an |
| empty stream. |
| |
| ===== See |
| |
| <<comparator>> |
| |
| ===== Examples |
| |
| To find tallest student in a class: |
| |
| `students.stream().max((p,q)\->p.height-q.height)` |
| |
| To find the maximum height of the students in a class: |
| |
| `students.stream().map(s\->s.height).max()` |
| |
| ==== min |
| |
| ===== Syntax |
| |
| `Optional<S> Stream<S>.min()` |
| |
| `Optional<S> Stream<S>.min(((p,q)\->int) comparator)` |
| |
| ===== Description |
| |
| This method computes the minimum of the |
| elements in the source stream. If the `comparator` function is specified, |
| it is used for comparisons. If no `comparator` function is specified, the |
| elements themselves are compared, and must implement `Comparable`, |
| otherwise an `ELException` is thrown. |
| |
| This method returns an empty `Optional` for an |
| empty stream. |
| |
| ===== See |
| |
| <<comparator>> |
| |
| ==== average |
| |
| ===== Syntax |
| |
| `Optional<S> Stream<S>.average()` |
| |
| ===== Description |
| |
| This method computes the average of all |
| elements in the source stream by first computes the sum of the elements |
| and then divides the sum by the number of elements. The elements are |
| coerced to Number types according to <<Coerce `A` to `Number` type `N`>> |
| during the computation. |
| |
| This method returns an empty `Optional` for an |
| empty stream. |
| |
| ==== sum |
| |
| ===== Syntax |
| |
| `Number Stream<S>.sum()` |
| |
| ===== Description |
| |
| This method computes the sum of all elements |
| in the source stream. The elements are coerced to Number types according |
| to <<Coerce `A` to `Number` type `N`>> during |
| the computation. |
| |
| This method returns zero for an empty stream. |
| |
| ==== count |
| |
| ===== Syntax |
| |
| `Long Stream<S>.count()` |
| |
| ===== Description |
| |
| This method returns the count of elements in |
| the source stream. |
| |
| ==== anyMatch |
| |
| ===== Syntax |
| |
| `Optional<boolean> Stream<S>.anyMatch((S\->boolean) predicate)` |
| |
| ===== Description |
| |
| This method returns an `Optional` of `true` if |
| any element in the source stream satisfies the test given by the |
| `predicate`. It returns an empty `Optional` if the stream is empty. |
| |
| ===== See |
| |
| <<predicate>> |
| |
| ===== Example |
| |
| To determine if the list of integers contains |
| any negative numbers: |
| |
| `integers.stream().anyMatch(i\->i<0).orElse(false)` |
| |
| Note the use of `orElse` to set a default value |
| for the empty list. |
| |
| ==== allMatch |
| |
| ===== Syntax |
| |
| `Optional<boolean> Stream<S>.allMatch((S\->boolean) predicate)` |
| |
| ===== Description |
| |
| This method returns an `Optional` of `true` if |
| all elements in the source stream satisfy the test given by the |
| `predicate`. It returns an empty `Optional` if the stream is empty. |
| |
| ===== See |
| |
| <<predicate>> |
| |
| ==== noneMatch |
| |
| ===== Syntax |
| |
| `Optional<boolean> Stream<S>.noneMatch((S\->boolean) predicate)` |
| |
| ===== Description |
| |
| This method returns an `Optional` of `true` if |
| none of the elements in the source stream satisfies the test given by |
| the `predicate`. It returns an empty `Optional` if the stream is empty. |
| |
| ===== See |
| |
| <<predicate>> |
| |
| |
| |
| ==== findFirst |
| |
| ===== Syntax |
| |
| `Optional<S> Stream<S>.findFirst()` |
| |
| ===== Description |
| |
| This method returns an `Optional` containing |
| the first element in the stream, or an empty `Optional` if the stream is |
| empty. |
| |
| ===== See |
| |
| <<Optional>> |
| |
| |
| [appendix] |
| == Changes |
| |
| This appendix lists the changes in the EL specification. |
| This appendix is non-normative. |
| |
| === Changes between 4.0 and JSR 341 |
| |
| * The API has moved from the `javax.el` package to the `jakarta.el` |
| package. |