SPL: Invoking Java Functions
Besides the built-in functions, SPL allows programmers to call methods written in Java classes to deal with specific computations or to package certain computing processes.
1. Ways of invoking Java methods
To be called by SPL, a Java class method should meet the following conditions:
1. The to-be-invoked Java class is public, and the to-be-invoked method is a public static method.
2. The to-be-invoked method has a unique name. In Java, namesake methods are distinguished using different parameter types or different numbers of parameters. SPL only supports getting the target method according to the name, so there must not be other namesake methods in the current class.
3. The to-be-invoked class is deployed in SPL’s classpath. Generally, it is put into a jar and place the jar under the lib directory in the installation directory. Or you can put this type of class file under classes directory in the installation directory.
After you finish writing and deploying a class, you can invoke it in an SPL script using invoke method.
Below is such a Java class:
package test;
public class Calc01 {
public static Double distance1(Number loc) {
double len = Math.abs(loc.doubleValue());
len = Math.round(len*1000)/1000d;
return Double.valueOf(len);
}
}
In this simple class, the static method distance1 calculates the distance between a given coordinate and the origin and retain three decimal places for the result. Below is the SPL script that invokes the class:
A |
|
1 |
-12.3456 |
2 |
=invoke(test.Calc01.distance1,A1) |
3 |
=invoke(test.Calc01.distance1, -512) |
As the above shows, in the invoke function, the full path of the to-be-invoked and the static method name are first be specified, and after them, necessary parameters are listed in turn. The parameters can be cell values in a cellset, cellset parameters, or passed in values. Below are results of A2 and A3 after execution:
You can define multiple static methods in one class for invocation.
2. Return values and parameter data type
The invocation returns or does not return a value. When it returns no value, it only performs the computation, or writes the result to a target file, or outputs it to the console. Different from Java, both parameters and the return value in SPL must be Java objects; other data types, such as int and double, are not allowed.
Below is a list of Java objects corresponding to common SPL data types:
bool |
java.lang.Boolean |
int |
java.lang.Integer |
long |
java.lang.Long |
float |
java.lang.Double |
decimal |
java.math.BigDecimal |
number |
java.lang.Number |
date |
java.sql.Date |
time |
java.sql.Time |
datetime |
java.sql.Timerstamp |
string |
java.lang.String |
blob |
byte[] |
sequence |
java.lang.Object[] 或 com.scudata.dm.Sequence |
table sequence |
com.scudata.dm.Table |
cursor |
com.scudata.dw.Cursor |
Most of the data types have relatively simple uses, but sequence, table sequence and cursor have special uses.
3. Using sequences
Sequence is a common SPL data type. A to-be-invoked method can use a sequence type parameter or return a sequence type result value. A sequence’s Java object is java.lang.Object[] or com.scudata.dm.Sequence.
3.1 With Object[]
Now we write another method in test.Calc01 class:
public static Object[] distance2(Object[] seq1, Object[] seq2) {
double x1 = ((Number) seq1[0]).doubleValue();
double y1 = ((Number) seq1[1]).doubleValue();
double x2 = ((Number) seq2[0]).doubleValue();
double y2 = ((Number) seq2[1]).doubleValue();
double len = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
len = Math.round(len*1000)/1000d;
Object[] result = new Object[3];
result[0] = seq1;
result[1] = seq2;
result[2] = Double.valueOf(len);
return result;
}
The distance2 function calculates the distance between two points in a rectangular coordinate system. The coordinates of the two points need to be passed in through a sequence type parameter when the class is invoked, as shown below:
A |
|
1 |
[2,6] |
2 |
[5,10] |
3 |
=invoke@x(test.Calc01.distance2, A1, A2) |
After execution, A3’s result is [[2,6],[5,10],5].
The invoke function must work with @x option when the sequence’s corresponding Java object is java.lang.Object[].
3.2 With Sequence
By modifying the above method as follows:
public static com.scudata.dm.Sequence distance3(com.scudata.dm.Sequence seq1, com.scudata.dm.Sequence seq2) {
int len1 = seq1.length();
int len2 = seq2.length();
double x1 = len1 > 0 ? ((Number) seq1.get(1)).doubleValue(): 0;
double x2 = len2 > 0 ? ((Number) seq2.get(1)).doubleValue(): 0;
double y1 = len1 > 1 ? ((Number) seq1.get(2)).doubleValue(): 0;
double y2 = len2 > 1 ? ((Number) seq2.get(2)).doubleValue(): 0;
double len = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
len = Math.round(len*1000)/1000d;
com.scudata.dm.Sequence result = new com.scudata.dm.Sequence();
result.add(seq1);
result.add(seq2);
result.add(Double.valueOf(len));
return result;
}
We have the following script to invoke the class:
A |
|
1 |
[2,6] |
2 |
[5,10] |
3 |
=invoke(test.Calc01.distance3, A1, A2) |
The invoke function can work on its own without an option when the sequence’s Java object is com.scudata.dm.Sequence. The performance is better with Sequence since no conversion is needed, though users need to make themselves familiar with the Sequence class. The Object[] is more simple to use, but it needs a conversion and thus, has a poorer performance.
4. Using table sequence
Table sequences are a special data structure in SPL. They are equivalent to two-dimensional tables in databases. A table sequence can be used as a parameter in an invoked method, or can be a return value.
For instance, we have a database table scene that records production scene pictures, and a Java method for image recognition. We are trying to recognize the pictures and store the results in a table. For this, we add a method to test.Calc01:
public static com.scudata.dm.Table recognizeScene(com.scudata.dm.Table table) {
int rows = table.length();
for(int i = 1; i <= rows; i++) {
com.scudata.dm.Record r = table.getRecord(i);
byte[] b = (byte[])r.getFieldValue("pic"); //Get the picture bytes in the current row
String result = Recognizer.parse(b); // Recognize the picture and return result
r.setNormalFieldValue(r.getFieldIndex( "result"), result ); // Save the recognition result in the result filed of the current row
}
return table;
}
To call the method, we need to pass a table sequence parameter to it, as shown below:
A |
|
1 |
$select scenedate,pic,result from scene |
2 |
=invoke(test.Calc01.recognizeScene, A1) |
5. Using cursor
A cursor parameter has similar uses as a table sequence parameter. So, here we only explain how to return a cursor value.
To make an invoked method return a cursor value, you should realize interface com.scudata.dm.ILineInput in the to-be-returned class, as shown in the following test.RandDataCursor:
package test;
public class RandDataCursor implements com.scudata.dm.ILineInput {
private int rowno = 0;
private int range = 1000;
public RandDataCursor(Integer rg) {
this.range = rg;
}
public Object[] readLine() throws java.io.IOException {
rowno++;
Object[] result = new Object[2];
result[0] = Integer.valueOf(rowno);
result[1] = Integer.valueOf((int) (Math.random() * range ));
return result;
}
public boolean skipLine() throws java.io.IOException {
rowno++;
if (rowno <= 10000) return true;
return false;
}
public void close() throws IOException {
rowno = 10001;
}
}
We need to implement three methods – readLine, skipLine and close in IlineInput class. The RandDataCursor class is simple and used to generate a sequence of random integers. The range of the random integers is defined through a parameter. Each record returned from readLine method is made up of the row number which is increased in turn and a random number. They are similar to fields of a table. The skipLine method skips one record and returns whether there is any remaining data in the cursor. In our instance, we set that 10000 rows at most can be returned from the cursor. The close method is used to exit the cursor and release unnecessary resources, such as database connection.
Now the user-defined function that is going to return a cursor can take RandDataCursor as the return class, as shown below:
package test;
public class Calc02 {
public static test.RandDataCursor getCursor(Integer range) {
RandDataCursor rdc = new RandDataCursor(range);
return rdc;
}
}
The user-defined function only needs to define one parameter, the range of random numbers, and returns cursor RandDataCursor that generates a random record.
At invocation, the returned ILlineInput class cannot used directly as a cursor. It needs to call newCursor method in com.scudata.dm.UserUtils class, as shown below:
A |
|
1 |
=invoke(test.Calc02.getCursor,1000000) |
2 |
=invoke(com.scudata.dm.UserUtils.newCursor,A1,"") |
3 |
>A2.skip(100) |
4 |
=A2.fetch@x(100) |
A1 returns the user-defined cursor RandDataCursor. A2 invokes UserUtils’s newCursor method to pass take A1’s result as a parameter to pass in. Here A2’s data is the cursor data in SPL. The third parameter in A2 is the cursor’s option, such as "t", which means the first row in the cursor is the header row. A3 skips the first 100 records. A4 fetches 100 records from A2’s cursor and closes the cursor. Below is A4’s result:
SPL Official Website 👉 https://www.scudata.com
SPL Feedback and Help 👉 https://www.reddit.com/r/esProcSPL
SPL Learning Material 👉 https://c.scudata.com
SPL Source Code and Package 👉 https://github.com/SPLWare/esProc
Discord 👉 https://discord.gg/cFTcUNs7
Youtube 👉 https://www.youtube.com/@esProc_SPL