Referencing fields within an object safely
Controlling the numeric precision of floating point numbers
In Entuity, you can develop Groovy scripts for use with the Event Management System (EMS), Reports and Configuration Management. The Entuity server includes a Groovy installation that runs these scripts when they are loaded to the server.
This article identifies Groovy concepts that are useful when developing Groovy scripts. For a full introduction to Groovy, please refer to http://groovy-lang.org/.
You can also refer to the online Groovy console facility at https://groovyconsole.appspot.com/, which allows you to experiment and test Groovy syntax without having to install anything locally on your own system.
Statement delimiters
If you place two Groovy statements on the same line, they require a delimiter (semi-colon) to separate them, e.g.:
println("Hello "); println("World!");
The second delimiter is not required, but for consistency you might want to include it.
Statements placed on their own line do not require an end of statement delimiter, but you can use one. For example, the following two lines are both valid and functionally identical:
println("hello")
println("hello");
Entuity recommends that you place each Groovy statement on its own line, because this will make the code easier to read.
Fundamental data types
There are many standard data types. The most relevant are as follows:
- string.
- character.
- Boolean.
- integer.
- long.
- float.
- double.
Variables
To instantiate a variable (i.e., create a variable at runtime), the def keyword is used. Variables should also be initialized during instantiation:
- def a = "Hello world!"; … a String
- def b = false; … a Boolean
- def c = 13; … an Integer
- def d = 23L; … a Long
- def e = 12.34F … a Float
- def f = 23.45 … a Double
- def g = 23.45D … a Double
To avoid confusion between Floats and Doubles, the standard Entuity reports force all floating point variables to be Floats.
Print to screen
When debugging a script, it can be useful to print arbitrary information to a screen, for example the value of a variable at an intermediate stage of its processing. To print to screen, use either of the following:
- println()
- print()
These display the supplied string as either with or without an appended newline, respectively.
Boolean evaluation
The following Boolean statements are valid:
- A < B
- A <= B
- A == B
- A != B
- A >= B
- A > B
The value of a Boolean variable can be inverted by preceding with with a "!".
Operators
Entuity supports the following standard operators:
- +
- -
- *
- /
plus the following:
- --
auto-decrement - !
not - %
modulo - &
numerical and - ++
auto-increment - ^
numerical xor - |
numerical or - 1.~
2.Bitwise complement
Control structures
Note, Statement can be a single statement or multiple statements separated either by semicolons and/or new line, all enclosed in {}:
if (Boolean) Statement
if(Boolean) Statement else Statement
while (Boolean) Statement
for (Variable in List) Statement
switch (Variable) {
case Value: Statement; break;
case Value: Statement; break;
default: Statement;
}
In a switch structure, the Variable may be any data type including Strings. The case may also be followed by a closure rather than just a static value, so more sophisticated testing can be performed in a Java switch structure.
Ternary operator
A ternary operator is a shorcut expression that is equivalent to an if/else branch assigning some value to a variable.
When a decision needs to be made as to what to write to a variable, a ternary operator can often be used to shorten the syntax. For example, a very common requirement when writing reports is to protect against empty/null values being returned by the Data API queries. If the mean utilization of a port were requested by a query for a period of time during which the device were totally unreachable, the mean utilization would be returned as an empty string. If an attempt were to be made to convert this empty string to a Float using the toFloat() method, then a runtime error would be raised and the report would crash. This can be handled using the following syntax:
def a = "12.34";
def b = (a != "")?a.toFloat():null;
If variable a were a null string, then b would be set to a null rather than raising a runtime error.
ArrayLists
You can collect data together using an ArrayList, which is a linear list of variables. Technically, it is not essential that every element of an ArrayList have the same data type, but in most applications there is uniformity across the elements.
The Entuity Data API returns a collection of components as an ArrayList. The records returned from the Report Query to the main report are usually in the form of an ArrayList.
You can instantiate an empty ArrayList as follows:
def a = [];
An ArrayList can be populated during instantiation:
def a = [1,2,3,4];
An ArrayList can have elements added after instantiation:
def a = [1,2];
a.add(3); … the result would be an ArrayList containing [1,2,3]
a.add(0,9); … this syntax specifies where to add the new element, in this case at the beginning
the result would therefore be [9,1,2,3]
One array list can be appended to another:
def a = [1,2];
def b = [3,4];
a.addAll(b); … the result would be [1,2,3,4]
An element of an ArrayList can be obtained using its index:
def a = ["a","b","c","d"];
println(a[3]); … this would display "d"
Maps
A map, also known as an associative array, contains a list of data types that can be indexed by a key, which in turn can be any fundamental data type. For example, this means it can be used to hold the values associated with enumerations, e.g. if an enumeration for a port state might be 1=Up, 2=Down, 3=Testing, 4=Failed. If the results returned from a Data API call were to be a String with the meaning of the status, and there was a requirement to convert this to the corresponding numeric value, the following map syntax could be used:
def meaning = [Up:1,Down:2,Testing:3,Failed:4];
def status = "Down";
println(meaning[status]); … the result would be 2
Maps are also useful when two separate Data API queries have been used to pull back different information about the same components, and the two separate datasets need to be merged. You can place the results from the first query into a map using either the StormWorks ID or component name as key. You can then insert the results from the second query into the map using the same key format. Note, if a corresponding map element does not already exist for a given key, the entry must be created using an add() method. This means that there must be an explicit test for the existence of an element performed before it is written to, and that a suitable element must be created if it does not already exist. This test can take advantage of a null return when a nonexistent element is referenced.
Converting between data types
Groovy supports automatic data type conversions using implied rules. In the following example, the Float data type is automatically converted to a string, before it is appended to the previous string using the + operator:
def a = 12.34F;
println("The average utilization is " + a);
Automatic type conversions between numeric types happens implicitly:
def a = 12.34F;
def b = 20;
println(a + b); … this would actually display the result as a Double
Typecasting allow one numeric type to be explicitly converted to another:
def a = 12.34F;
println((Integer)a); … this would display "12" as the Float as been cast to an Integer
Numeric Coercion
The resulting data type from various numeric operations needs to be understood and accommodated. The result of performing addition, subtraction or multiplication between common numeric data types is as follows:
+ - * | Integer | Long | Float | Double |
---|---|---|---|---|
Integer | Interger | Long | Double | Double |
Long | Long | Long | Double | Double |
Float | Double | Double | Double | Double |
Double | Double | Double | Double | Double |
However, the coercion rules for division are as follows:
/ | Integer | Long | Float | Double |
---|---|---|---|---|
Integer | BigDecimal | BigDecimal | Double | Double |
Long | BigDecimal | BigDecimal | Double | Double |
Float | Double | Double | Double | Double |
Double | Double | Double | Double | Double |
For most practical purposes, BigDecimal and Double data types can be thought of as synonymous. This means that 1/3 in Groovy evaluates to 0.75, whereas in Java it would evaluate to 0 because the Java result would also have been an Integer.
If you want to explicitly divide on Interger by another and get an Integer result, use the intdiv() method, so a/b would be expressed as a.intdiv(b).
String operators and methods
Strings are one of the most important data types used within report queries. All the data returns from Flex queries via the Data API is in the form Strings, regardless of whether it actually represents an Integer, Float, or any other data type. Unlike in Java, Strings can be enlarged after their instantiation. Strings can be concatenated using the + operator:
def a = "Hello" + " " + "World!"; … this would result in the String "Hello World!"
There are many String methods available, including all of those inherited from java:
def a = "12.34";
def b = "56.78";
println(a.toFloat() + b.toFloat()); … this would display 69.12
def c = "39";
c.toInteger(); … convert a String to an Integer … must be an integer format string to succeed
… float format strings would fail to convert
def d = " 12.34 "; … a numeric string with white space on either or both ends
d.trim().toFloat(); … this could strip off the white space to allow the float to be created
def e = "00123:xyz";
println(e.substring(6)); … displays xyz … the entire string from character 6 onwards
println(e.substring(6,8)); … display xy … the substring between characters 6 and 8
def g = "abcDEF";
println(g.toUpperCase()); … displays ABCDEF
println(g.toLowerCase()); … displays abcdef
Closures
A Groovy closure is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope.
Iteration
All the elements of an ArrayList or similar collection can be iterated over with each element being separately processed by a closure. For example, the numeric contents of a simple ArrayList could be totaled as follows:
def a = [3,9,5,18,23,12];
def total = 0;
a.each {
total += it;
}
println(total); … displays 70
For each iteration through the ArrayList the special variable it is set to the next value of the element of the ArrayList. A modification to this syntax would be:
def a = [3,9,5,18,23,12];
def total = 0;
a.each { b ->
total += b;
}
println(total); … displays 70
This shows that instead of using it, a new variable b is being set to the value of the next element instead.
If the index of each element is also required to be know the eachWithIndex technique can be used:
def a = [3,9,5,18,23,12];
a.eachWithIndex {val,index ->
println(index + ":" + val);
}
This would display:
0:3
1:9
2:5
3:18
4:23
5:12
A similar iteration technique can be used with a map:
def meaning = [Up:1,Down:2,Testing:3,Failed:4];
meaning.each {key,val ->
println("Key:" + key + ", Value:" + val);
}
The result of which would be to display:
Key:Up, Value:1
Key:Down, Value:2
Key:Testing, Value:3
Key:Failed, Value:4
Sorting
An ArrayList can be sorted:
def a = [3,9,5,18,23,12];
println(a.sort {it}); … displays [3, 5, 9, 12, 18, 23]
println(a.sort {-it}); … displays [23, 18, 12, 9, 5, 3]
Filtering
An ArrayList can be filtered:
def a = [3,9,5,18,23,12];
println(a.grep {it<10}); … displays [3, 5, 9]
Referencing fields within an object safely
To access the ok field in a simple object, the syntax will be:
obj.ok
However, if obj had a null value, then this reference would result in a Null Pointer Exception (NPE). This is a problem when iterating over objects in an ArrayList, where it is possible that one or more of the objects might actually have a null value. The problem becomes compounded when the objects are nested several levels deep, as is the case with the results of a Flex query performed through the Entuity Data API. A typical iteration through devices might look as follows:
query.result.content.devices.each {}
If any of the levels in this nested sequence has a null value, an NPE will be raised and, unless there is explicit protection around the operation, the query will fail. You can use a simple Groovy syntax to test each and every level of the nest before referencing the full sequence, using the safe dereferencing operator ?. The same iteration could be safely specified using:
query?.result?.content?.devices.each {}
If any level of the sequence returns a null, then the whole result is null, which safely results in zero loops of the closure being iterated around.
Controlling the numeric precision of floating point numbers
There are two main ways to specify the number of decimal places and other formatting aspects of floating point numbers in reports:
When the number is displayed on its own, and not as part of a longer string:
- If the number is passed to the report from the report query as either a Float or Double, it will automatically configure the Text Field to that data type when dragged into a band on the report.
- Even if passed as a String, it can still be converted to a Float or Double using a suitable expression within the Text Field. If this approach is taken, then you must ensure that the data type setting of the Text Field matches the resulting data type of the expression.
When the floating point number is mixed with other text:
- The simple formatting controls within a Text Field cannot be used.
- An alternative approach is to convert the Float or Double into a string using the DecimalFormat class, which also allows control over its numeric precision display. An example of the required syntax is as follows:
def val=12.34567F;
def str = new java.text.DecimalFormat ("#.##").format(val);
In this case, the value of the String variable str would be set 12.35, because it would also perform rounding.
Handling dates and times
Timestamps used within Entuity are of the UNIX format, which means they are integers representing the number of seconds since 00:00 1st Jan 1970 GMT. The Java standard for a timestamp, which is inherited by Groovy, is a long containing the number of milliseconds since 00:00 1st Jan 1970 GMT which means it is a factor of 1000 greater than the UNIX version. This can become relevant when converting a timestamp that was obtained from Flex queries via the Data API into a descriptive string.
Comments
0 comments
Please sign in to leave a comment.