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 of JUEL 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 of JUEL 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. Using JUEL does not require an application to explicitly reference any of the JUEL specific implementation classes.

  • Axional Studio, takes advance of JUEL on all expressions labeled as JUEL, and many other parts like the package [vtable.matches] of the SQL Script
  • Axional Terminal, does also use JUEL 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

Copy
'a' IN ('a', 'b', 'c', 'd')
true

4.3.2 MATCHES

MATCHES operator to match a string in a pattern

Copy
'abab' MATCHES '(ab)*'
true
Copy
'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
  • date: java.util.Date
  • unit: java.lang.Integer
  • type: java.lang.String
    • d: days
    • H: hours
    • m: minutes
    • s: seconds
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:
  • HH:mm:ss
  • HH:mm
  • HH:ss
  • mm:ss
  • HH
  • mm
  • ss
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 throws ArithmeticException. 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 cause BigDecimal can not represent Infinity.
  • 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

Copy
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:

Copy
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  
To solve it, Axional uses auto scaling using a custom 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:

Copy
<call name='myxsqlscript'>
    <args>
        <arg>${adapter('cabid')}</arg>
        <arg>${variable('fecini')}</arg>
        <arg>${field('copycrc')}</arg>
    </args>
</call>