| :xrefstyle: full | |
| :sectnums!: | |
| == Jakarta Expression Language, Version 6.0 | |
| Copyright (c) 2013, 2023 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 6.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/jakartaee/expression-language | |
| 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 implemented the transition from the `javax` namespace to the | |
| `jakarta` namespace. | |
| EL 5.0 made Java 11 the minimum version and implemented a number of | |
| enhancement requests from the user community. Deprecated code was removed | |
| and use of FeatureDescriptor was deprecated with a view to removing the | |
| dependency on the `java.desktop` module. | |
| ==== 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. | |
| The method must be identified using the same process as that described in | |
| section 15.12.2 of the Java Language Specification, version 11 with the | |
| addition that coercion resolution is considered to be less specific than | |
| overload resolution. | |
| ==== 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')`. | |
| === Arrays | |
| In addition to accessing array elements by index, the unified EL also supports | |
| the property `length` for arrays which will return the length of the array as an | |
| integer. | |
| === 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. | |
| . The fields and/or methods must be in an accessible module. | |
| . 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. | |
| ==== Additional Default Imports | |
| When EL is used in conjunction with another specification, for example Jakarta | |
| Server Pages, that specification may define further packages, in addition to | |
| `java.lang.*`, that are imported by default into the EL environment. | |
| === 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 | |
| * Otherwise, error | |
| ==== Coerce `A` to an array of Type `T` | |
| * If `A` is `null`, return `null` | |
| * If `A` is assignable to an array of type `T`, coerce quietly | |
| * If `A` is an array, return a copy of the array where each element has been coerced to type `T` using these rules | |
| * Otherwise, error | |
| Note: If coercion of any element in the array fails, the coercion of the array fails | |
| ==== Coerce `A` to functional interface method invocation | |
| * If `A` is a `LambdaExpression` then: | |
| ** Return the result of invoking the `LambdaExpression` with the parameters | |
| (coerced if necessary) that were passed to the Functional Interface method | |
| invocation | |
| * Otherwise, apply the rules in <<Coerce `A` to Any Other Type `T`>> | |
| Note: A Type is only considered to be a functional interface it it is annotated | |
| with `java.lang.FunctionalInterface`. | |
| ==== 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 6.0 and 5.0 | |
| * Remove deprecated code. | |
| * Update to reflect new GitHub organisation and project names. | |
| * https://github.com/jakartaee/expression-language/issues/175[#175] | |
| Add support for a new property, `length`, for arrays. | |
| * https://github.com/jakartaee/expression-language/issues/188[#188] | |
| Allow for module visibility when accessing class methods. | |
| * https://github.com/jakartaee/expression-language/issues/192[#192] | |
| Removal all references to the Java SecurityManager and associated APIs. | |
| === Changes between 5.0 and 4.0 | |
| * The EL API requires Java 11 as a minimum. | |
| * https://github.com/jakartaee/expression-language/issues/43[#43] | |
| The `BeanELResolver` is updated to also consider default method | |
| implementations when looking for property getters, property setters and | |
| methods. | |
| * https://github.com/jakartaee/expression-language/issues/45[#45] | |
| Add support for coercing a `LambdaExpression` instance to a functional | |
| interface method invocation | |
| * https://github.com/jakartaee/expression-language/issues/47[#47] | |
| The `ELResolver` method `getFeatureDescriptors()` has been deprecated with | |
| removal planed for EL 6.0. This is to remove the dependency on the | |
| `java.desktop` module from the EL API. | |
| * https://github.com/jakartaee/expression-language/issues/117[#117] | |
| Remove the deprecated, mis-spelt method | |
| `MethodExpression.isParmetersProvided()` from the API | |
| * https://github.com/jakartaee/expression-language/issues/154[#154] | |
| Add the `MethodReference` class to provide access to details of the method to | |
| which a `MethodExpression` resolves, including any annotations present on the | |
| method. | |
| * https://github.com/jakartaee/expression-language/issues/157[#157] | |
| Generics are now used where appropriate throughout the API | |
| * https://github.com/jakartaee/expression-language/issues/159[#159] | |
| Clarify expected behaviour when matching expressions to methods based on | |
| parameter types | |
| * https://github.com/jakartaee/expression-language/issues/167[#167] | |
| Add a default implementation that returns `null` for the `ELResolver` method | |
| `getFeatureDescriptors()` since it has been deprecated so that custom | |
| `ELResolver` implementations do not need to implement the method. | |
| * https://github.com/jakartaee/expression-language/issues/168[#168] | |
| Explicitly document that `ELResolver.getType()` must return null if either the | |
| ELResolver or the resolved property is read-only. This changes the documented | |
| behaviour of the `StaticFieldELResolver`. | |
| * Explicitly state that specifications that depend on this specification may | |
| define further packages, in additional to `java.lang.*` that are imported by | |
| default into the EL environment. | |
| * https://github.com/jakartaee/expression-language/issues/172[#172] | |
| Add support for coercing arrays. | |
| * Add a JPMS module descriptor that defines the module name as `jakarta.el`. | |
| * https://github.com/jakartaee/expression-language/issues/175[#175] | |
| Add a special case for the property `length` to the `ArrayELResolver` that | |
| returns the length of the array. | |
| * https://github.com/jakartaee/expression-language/issues/183[#183] | |
| Avoid a `NullPointerException` when passing `null` for varargs. | |
| === Changes between 4.0 and JSR 341 | |
| * The API has moved from the `javax.el` package to the `jakarta.el` | |
| package. |