The evaluation of expressions on the server side is often required. As general rule, all Axional products share a common
evaluation of expressions, build on top of JUEL
(Java Unified Expression Language) standards.
1 Introduction
JUEL
is an implementation of the Unified Expression Language (EL),
specified as part of the JSP 2.1 standard (JSR-245), which was introduced in JEE5. Additionally,
JUEL
2.2 implements the JSP 2.2 maintenance release specification for full JEE6 compliance.
JUEL
provides a lightweight and efficient implementation of the Unified Expression Language.
-
High Performance – Parsing expressions is certainly the expected performance bottleneck.
JUEL
uses a hand-coded parser which is up to 10 times faster than the previously used (javacc) generated parser! Once built, expression trees are evaluated at highest speed. -
Pluggable Cache – Even if JUELs parser is fast, parsing expressions is relative expensive. Therefore, it's best to parse an expression string only once.
JUEL
provides a default caching mechanism which should be sufficient in most cases. However,JUEL
allows to plug in your own cache easily. -
Small Footprint –
JUEL
has been carefully designed to minimize memory usage as well as code size. -
Method Invocations –
JUEL
supports method invocations as in ${foo.matches('[0-9]+')}. Methods are resolved and invoked using the EL's resolver mechanism. As ofJUEL
2.2, method invocations are enabled by default. -
VarArg Calls –
JUEL
supports Java 5 VarArgs in function and method invocations. E.g., binding String.format(String, String...) to function format allows for ${format('Hey %s','Joe')}. As ofJUEL
2.2, VarArgs are enabled by default. -
Pluggable –
JUEL
can be configured to be transparently detected as EL implementation by a Java runtime environment or JEE application server. UsingJUEL
does not require an application to explicitly reference any of theJUEL
specific implementation classes.
-
Axional Studio
, takes advance ofJUEL
on all expressions labeled asJUEL
, and many other parts like the package [vtable.matches] of the SQL Script
-
Axional Terminal
, does also useJUEL
to evaluate field expressions
2 Operators
You can use the following operators, which can be used in rvalue expressions only
- Arithmetic: +, - (binary), *, / and div, % and mod, - (unary)
- Logical: and, &&, or, ||, not, !
- Relational: ==, eq, !=, ne, <, lt, >, gt, <=, ge, >=, le. Comparisons can be made against other values or against Boolean, string, integer, or floating-point literals.
- Empty: The empty operator is a prefix operation that can be used to determine whether a value is null or empty.
- Conditional: A ? B : C. Evaluate B or C, depending on the result of the evaluation of A.
Note that the operator for equality is ==, not a single =. Attending the compatibility between SQL expressions that admit a single = as equality, the expressions with single = are transformed to double, before the evaluating step.
2.1 Precedence
The precedence of operators highest to lowest, left to right is as follows:
- () (used to change the precedence of operators)
- - (unary) not ! empty
- * / div % mod
- + - (binary)
- < > <= >= lt gt le ge
- == != eq ne
- && and
- || or
- ? :
3 Reserved words
The following words are reserved for the EL and thus, they should not be used as identifiers:
and | or | not | |||
true | false | null | |||
empty | div | mod | |||
in | matches | ||||
eq | ne | lt | gt | le | ge |
EL defines those words as lower case but Axional
implementation allows to write any case
indistinctly. So AND or And can be used instead of and.
4 Expression syntax
JUEL
is an implementation of the Unified Expression Language (EL), specified as part of the JSP 2.1 standard (JSR-245).
4.1 Expressions
The following tables show a list of expressions.
Expression | Result |
---|---|
Boolean | |
1 > (4/2) | false |
4.0 >= 3 | true |
100.0 == 100 | true |
(10*10) ne 100 | false |
4 > 3 | true |
1 + 2 > 2 - 1 | true |
1 < 2 && 2 > 1 | true |
1 < 2 || 2 > 1 | true |
'a' < 'b' | true |
'hip' gt 'hit' | false |
!empty 'a' | true |
empty '' | true |
Arithmetic | |
1 + 2 | 3 |
1.2E4 + 1.4 | 12001.4 |
3 div 4 | 0.75 |
10 mod 4 | 2 |
4.2 Beans
EL
can access object as beans. For example, with a variable of type date you can access
the date methods directly.
Suposse you have the variable date with Sat Oct 20 00:00:00 CEST 2012
- date.day will return 20
- date.month will return 9 (not 10 for October as Java starts counting months at 0)
- date.year will return 2012
4.3 Extensions
A special set of extensions is included to add functions that are present in SQL languages.
4.3.1 IN
IN operator to search if a string is within a list of strings
'a' IN ('a', 'b', 'c', 'd')
true
4.3.2 MATCHES
MATCHES operator to match a string in a pattern
'abab' MATCHES '(ab)*'
true
'abab' MATCHES '(ba)*'
false
5 Functions
Functions are referenced using a prefix and a local name and they should be referenced via prefix:localname. A set of functions is intrinsically declared.
5.1 Core functions
Returns | Function | Description |
---|---|---|
int | length(java.lang.Object) | Returns the length of an object (string, array, list or map) |
java.lang.Number | number(java.lang.Number) | Returns a number or 0 if object is null |
java.lang.Number | toInt(java.lang.Number) | Returns an int from number or 0 if object is null |
java.lang.Number | toLong(java.lang.Number) | Returns a long from number or 0 if object is null |
java.lang.Number | toFloat(java.lang.Number) | Returns a float from number or 0 if object is null |
java.lang.Number | toDouble(java.lang.Number) | Returns a double from number or 0 if object is null |
java.lang.Number | toDecimal(java.lang.Number) | Returns a BigDecimal from number or 0 if object is null |
java.lang.Object | nvl(obj1, obj2) | Returns obj1 when if obj1 is not null and return obj2 otherwise |
boolean | isNull(obj1) | Returns true when obj2 is null and false otherwise |
boolean | isNullOrEmpty(obj1) | Returns true when obj2 is null or an empty string and false otherwise |
java.lang.String | string:format(format, args ...) | format a string using variable arguments |
5.2 Date functions
Returns | Function | Description |
---|---|---|
java.util.Date | date:add(date, unit, type) |
add a number of indicated type (days, hours, minutes, seconds) to a date
|
java.sql.Timestamp | date:current() | Obtains the current date, including time. |
java.util.Date | date:firstDayOfMonth(date) | Obtains a new Date object corresponding to the first day of month for the received date. |
java.util.Date | date:firstDayOfWeek(date) | Obtains a new Date object corresponding to the first day of week for the received date. |
java.util.Date | date:iso(dateString) | Build a Date object by giving string value in ISO format "2015-03-25" (The International Standard). ISO 8601 is the international standard for the representation of dates and times. ISO dates can be written with added hours, minutes, and seconds (YYYY-MM-DDTHH:MM:SSZ). |
java.util.Date | date:lastDayOfMonth(date) | Obtains a new Date object corresponding to the last day of month for the received date. |
java.util.Date | date:lastDayOfWeek(date) | Obtains a new Date object corresponding to the last day of week for the received date, including the weekend. |
java.util.Date | date:parse(format, date) | parse a date |
java.sql.Date | date:mdy(month, day, year) | Build a Date object by giving integer values of month, day and year. Notice that the return type is java.sql.Date, which not consider hours, minutes, and seconds. It is usefull for comparing with YEAR TO DAY SQL columns, or defining default values for Date types. |
boolean | date:equals(date1, date2) | returns true if date1 is equal date2, otherwise false |
boolean | date:before(date1, date2) | returns true if date1 is before date2, otherwise false |
boolean | date:after(date1, date2) | returns true if date1 is after date2, otherwise false |
int | date:getDay(date1) | returns day of month |
int | date:getMonth(date1) | returns the month |
int | date:getYear(date1) | returns the year |
java.sql.Date | date:today() | Obtains the current date. It does not include time. |
Example | Returns |
---|---|
date:add(date:iso('2012-10-20'), 5, 'd') | Thu, 25 Oct 2012 00:00:00 GMT |
date:parse('dd-MM-yyyy', '20-10-2012') | Sat, 20 Oct 2012 00:00:00 GMT |
date:mdy(10, 20, 2012) | Sat, 20 Oct 2012 00:00:00 GMT |
date:equals(date:iso('2012-10-20'), date:iso('2012-10-20')) | true |
date:before(date:iso('2012-10-20'), date:iso('2012-10-21')) | true |
date:after(date:iso('2012-10-20'), date:iso('2012-10-21')) | false |
date:getDay(date:iso('2012-10-20')) | 20 |
date:getMonth(date:iso('2012-10-20')) | 10 |
date:getYear(date:iso('2012-10-20')) | 2012 |
date:iso('2012-10-20') | Sat, 20 Oct 2012 00:00:00 GMT |
date:iso('2012-10-20T12:35:45Z') | Sat, 20 Oct 2012 12:35:45 GMT |
date:firstDayOfMonth(date:iso('2020-10-20')) | Thu, 01 Oct 2020 00:00:00 GMT |
date:firstDayOfWeek(date:iso('2020-10-20')) | Mon, 19 Oct 2020 00:00:00 GMT |
date:lastDayOfMonth(date:iso('2020-10-20')) | Sat, 31 Oct 2020 00:00:00 GMT |
date:lastDayOfWeek(date:iso('2020-10-20')) | Sun, 25 Oct 2020 00:00:00 GMT |
5.3 Math
Returns | Function | Description |
---|---|---|
double | math:sin(double.class) | Returns the trigonometric sine of an angle. |
double | math:cos(double.class) | Returns the trigonometric cosine of an angle. |
double | math:tan(double.class) | Returns the trigonometric tangent of an angle. |
double | math:exp(double.class) | Returns Euler's number e raised to the power of a double value. |
double | math:log(double.class) | Returns the natural logarithm (base e) of a double value. |
double | math:abs(double.class) | Returns the absolute value of a double value. |
double | math:min(double.class) | Returns the smaller of two double values. |
double | math:max(double.class) | Returns the greater of two double values. |
double | math:pow(double.class) | Returns the value of the first argument raised to the power of the second argument. |
double | math:round(double.class, int.class) | Returns the value rounded to its nearest number. The second parameter is the decimal places. If 0 or null rounded to integer. |
double | math:ceil(double.class, int.class) | Returns the value rounded up to its nearest number. The second parameter is the decimal places. If 0 or null rounded up to integer. |
double | math:floor(double.class, int.class) | Returns the value rounded to its nearest number. The second parameter is the decimal places. If 0 or null rounded down to integer. |
5.4 JSTL functions
Returns | Function | Description |
---|---|---|
boolean | contains( java.lang.String, java.lang.String) | Tests if an input string contains the specified substring. |
boolean | containsIgnoreCase( java.lang.String, java.lang.String) | Tests if an input string contains the specified substring in a case insensitive way. |
boolean | endsWith( java.lang.String, java.lang.String) | Tests if an input string ends with the specified suffix. |
java.lang.String | escapeXml( java.lang.String) | Escapes characters that could be interpreted as XML markup. |
java.lang.String | formatHour( java.lang.String, java.lang.String) |
Converts a text that contains a time in the indicated format to a text that contains a time in format HH:mm:ss. Accepted formats:
|
int | indexOf( java.lang.String, java.lang.String) | Returns the index within a string of the first occurrence of a specified substring. |
java.lang.String | join( java.lang.String[], java.lang.String) | Joins all elements of an array into a string. |
int | length( java.lang.Object) | Returns the number of items in a collection, or the number of characters in a string. |
java.lang.String | replace( java.lang.String, java.lang.String, java.lang.String) | Returns a string resulting from replacing in an input string all occurrences of a "before" string into an "after" substring. |
java.lang.String[] | split( java.lang.String, java.lang.String) | Splits a string into an array of substrings. |
boolean | startsWith( java.lang.String, java.lang.String) | Tests if an input string starts with the specified prefix. |
java.lang.String | substring( java.lang.String, int, int) | Returns a subset of a string. |
java.lang.String | substringAfter( java.lang.String, java.lang.String) | Returns a subset of a string following a specific substring. |
java.lang.String | substringBefore( java.lang.String, java.lang.String) | Returns a subset of a string before a specific substring. |
java.lang.Double | toHours( java.lang.String) | Get the number of hours given a time in text format. |
java.lang.String | toLowerCase( java.lang.String) | Converts all of the characters of a string to lower case. |
java.lang.Double | toMinutes( java.lang.String) | Get the number of minutes given a time in text format. |
java.lang.Double | toSeconds( java.lang.String) | Get the number of seconds given a time in text format. |
java.lang.String | toUpperCase( java.lang.String) | Converts all of the characters of a string to upper case. |
java.lang.String | trim( java.lang.String) | Removes white spaces from both ends of a string. |
java.lang.String | trimToNull( java.lang.String) | Trims the String, if the string becomes empty retunrs null |
Example | Returns |
---|---|
fn:formatHour('15:01', 'mm:ss') | 00:15:01 |
fn:toHours('01:20:01') | 1.333611111111111 |
fn:toMinutes('01:20:01') | 80.01666666666667 |
fn:toSeconds('01:20:01') | 4801.0 |
6 Handling of null and empty Strings
Special care should be applied when handling nulls.
- A null value is not an empty string
- A variable with value null still is an existing variable and can be used for comparison
The following table shows comparison of a string with null
Expression | A='' | A=' ' | A=null |
---|---|---|---|
A == '' | |||
A == ' ' | |||
A == null | |||
A != null | |||
Z == null | ELResolver cannot handle a null base Object with identifier 'Z' |
The following table shows arithmetics with null or strings
Expression | A=0 | A='' | A=null |
---|---|---|---|
A == '' | |||
A == ' ' | |||
A == null | |||
A != null | |||
Z == null | ELResolver cannot handle a null base Object with identifier 'Z' |
7 Arithmetic
7.1 Numeric conversion
In some cases, you may need to properly convert numbers to appropiate type before operating. A set of conversion funcions is provided for your convenience.
Function | Return |
---|---|
toInt(Number) | int |
toLong(Number) | long |
toFloat(Number) | float |
toDouble(Number) | double |
toDecimal(Number) |
BigDecimal
|
7.2 Division by 0 and Infinity
Depending numeric types, Java will generate Infinity
or ArithmeticException
when divide by 0.
-
In case of double/float division, the output is
Infinity
, the basic reason behind that it implements the floating point arithmetic algorithm which specifies a special values like “Not a number” OR “infinity” for “divided by zero cases” as per IEEE 754 standards. -
In case of integer or
BigDecimal
division, it throwsArithmeticException
. This is the major case cause eval on integers uses always double precision numbers.
Expression | Result | Cause |
---|---|---|
toDecimal(1)/0 | null | The BigDecimalWrapper has catched div by 0 |
toDecimal(1)/toDecimal(0) | null | The BigDecimalWrapper has catched div by 0 |
toInt(1)/toInt(0) |
Infinity
|
Even number have been trucated to int, eval is done using double |
0/0 | Exception |
ArithmeticException is throw while attempt to divide by zero. This case never happens in expresions library as it always us double precision or BigDecimal arithmetic. |
0.0/0.0 |
NaN
|
Even number have been trucated to int, eval is done using double |
-
BigDecimal
division by 0 returns null causeBigDecimal
can not representInfinity
. -
Other numeric division by 0 returns
Infinity
or Nan (as double precision arithmetic is used even for integers).
7.2.1 Not a Number
As seen before, division by zero using a double returns NaN
.
An operation that overflows produces a signed infinity, an operation that underflows produces a denormalized value or a signed zero, and an operation that has no mathematically definite result produces NaN
. All numeric operations with NaN
as an operand produce NaN
as a result. As has already been described, NaN
is unordered, so a numeric comparison operation involving one or two NaNs returns false and any != comparison involving NaN
returns true, including x!=x when x is NaN
.
NaN
is by definition not equal to any number including NaN
. This is part of the IEEE 754 standard and implemented by the CPU/FPU.
It is not something the JVM has to add any logic to support.
This produces the following results
public static void main(String a[]) { System.out.println(Double.NaN==Double.NaN); System.out.println(Double.NaN!=Double.NaN); }
false
true
So, to compare for a NaN
you should use the Double.isNan(double)
function.
7.3 Handling BigDecimal comparisons
In Java financial programming, you need the precision BigDecimals afford. However, due to the great precision of this value, comparisons are very error prone.
For example:
0.000000000000000000000000000000001 != 0.0
For most cases, we want to round when the calculations are complete to the desired precision, rather than during intermediate steps.
For BigDecimals equals checks both value and scale giving different results when comparing numbers with different scales.
Source | Operator | Target | Result |
---|---|---|---|
BigDecimal (“0E-8”) |
== |
BigDecimal .ZERO |
|
BigDecimal (“0E-8”) |
compareTo() |
BigDecimal .ZERO |
|
BigDecimal (“0E-8”) |
== |
BigDecimal .ZERO.setScale(8) |
7.3.1 Autoscale before compare
When variables come from BigDecimal
numbers, comparison with constant values or other numeric variables may
be affected by scaling. Lets see the following EL examples.
Source | Operator | Target | Result |
---|---|---|---|
BigDecimal ("0") |
== | 0 | |
BigDecimal (0).setScale(2) |
== | 0 | |
BigDecimal (2.2) |
== | 2.2 |
TypeConverterImpl
. The coerceToBigDecimal
function, scales BigDecimal
values to 10.
7.3.2 Divide by 0
As explained before, a BigDecimal
division by 0 results in ArithmeticException
. To prevent
that, and the need of manually fix all expressions, the coerce function returns a
BigDecimalWrapper that prevents ArithmeticException
.
The BigDecimalWrapper divide function returns null when the divisor is 0.
7.4 Manual scaling
TO DO
This section is incomplete and will be concluded as soon as possible.8 Studio integration
The UEL expression is available in some wic_jrep_object expressions. In this context, special functions are available in order to expose and pass the Object information. The available functions are:
- adapter: This retrieves data belonging to the current row of the adapter.
- parent: This gets the data belonging to the current row of the parent adapter.
- variable: Data comes from variables defined as input of the object. (wic_jrep_colvar)
- field: Data from field buttons and SQL catalogued. It is only available to retrieve data belonging to the statement of the buttons (wic_jrep_form_butt) or the catalogued SQL (wic_jrep_form_query).
Notice that if no modifier is prefixed then the variable asumes the value of the most proper source of result data.
Functions can be invoked directly:
Function | Expression |
---|---|
Adapter |
Copy
adapter('estcab') != 'E' |
Parent Adapter |
Copy
parent('estcab') != 'E' |
Variables (Form input) |
Copy
variable('fecini') > date:parse('dd-MM-yyyy', '20-10-2012') |
Button fields and SQL Catalogued |
Copy
field('fecini') > date:parse('dd-MM-yyyy', '20-10-2012') |
Direct expression (close up source) without function |
Copy
fecini > date:parse('dd-MM-yyyy', '20-10-2012') |
In some cases, UEL expressions are used inside statement expressions, using the syntax ${expr}
. The mentioned functions are also available on those expressions:
<call name='myxsqlscript'> <args> <arg>${adapter('cabid')}</arg> <arg>${variable('fecini')}</arg> <arg>${field('copycrc')}</arg> </args> </call>