SPL:调用 Java 函数
在SPL中,除了使用系统提供的各类函数,还可以调用用户在Java类中编写的方法,用来处理一些比较特殊的运算,或者封装好的某些计算过程。
1. 调用方法
能被SPL调用的Java类方法必须满足以下条件:
1、 被调用的类是公开的,即public class,被调用的方法是类中公有的静态方法,即方法被声称为public static。
2、 被调用的方法名称是唯一的。这一点与Java不同,Java中同名方法可以用不同参数类型或个数进行区别,而SPL只根据名称寻找对应的方法,所以在此类中不能存在与它同名的其它方法。
3、 被调用的类要部署在SPL的类路径中。通常可以放在jar包里,然后把jar放在安装目录下的lib目录中,或者将此类的class文件放在安装目录下的classes目录中。
编写好类并部署以后,就可以在SPL脚本中用invoke方法来调用它了。
例如下面这个Java类:
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);
}
}
在这个简单的类中,静态方法distance1计算给定的一个坐标,计算与原点之间的距离,计算结果保留3位小数。在SPL中调用:
A |
|
1 |
-12.3456 |
2 |
=invoke(test.Calc01.distance1,A1) |
3 |
=invoke(test.Calc01.distance1, -512) |
如例子中所示,使用invoke函数时,先指明所调用类的全路径以及静态方法名称,然后把需要用到的参数依次列出。使用的参数既可以是网格中的格值、网格参数等,也可以直接输入。计算后,A2与A3中结果分别如下:
同一个类中,可以定义多个被调用的静态方法。
2. 返回值与参数的数据类型
被调用的方法可以返回值,也可以不返回值,只完成运算过程或将结果写入文件或输出到控制台。与Java不同的是,参数与返回值都必须是一个Java对象,不能是int、double等数据类型。
下表是SPL常用数据类型对应的Java对象。
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[] |
序列 |
java.lang.Object[] 或 com.scudata.dm.Sequence |
序表 |
com.scudata.dm.Table |
游标 |
com.scudata.dw.Cursor |
普通的数据类型使用起来都比较简单,下面重点讲一下序列、序表和游标的使用。
3. 使用序列
序列是SPL中常用的数据类型,被调用的方法既可以用序列作为参数,也可以返回序列作为结果值。序列对应的Java对象可以是java.lang.Object[],也可以是com.scudata.dm.Sequence。
3.1 用 Object[]
例如在test.Calc01类中再写一个方法:
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;
}
函数distance2计算平面直角坐标系中两个点之间的距离。在调用时,两个点的坐标需用序列类型的参数传入。如:
A |
|
1 |
[2,6] |
2 |
[5,10] |
3 |
=invoke@x(test.Calc01.distance2, A1, A2) |
计算以后A3中的结果是:[[2,6],[5,10],5]
当序列对应的Java对象是java.lang.Object[]时,invoke函数必须加选项@x
3.2 用 Sequence
例如将上面的方法改写为:
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;
}
调用时的脚本如下:
A |
|
1 |
[2,6] |
2 |
[5,10] |
3 |
=invoke(test.Calc01.distance3, A1, A2) |
当序列对应的Java对象是com.scudata.dm.Sequence时,不用加选项。使用Sequence的性能更好,不必做转换,但需要了解一下Sequence类。上节中的Object[]更简单,但会做个转换,性能略差。
4. 使用序表
序表是SPL中的特殊数据结构,相当于是数据库中的一张二维表。序表可以作为被调用方法的参数,也可以作为返回值。
例如有一张数据库表scene里记录了一些生产场景图片,已有图像识别的Java方法,现需要对这些图片作识别,并把识别结果记录在表里。在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"); //读取本行图片字节
String result = Recognizer.parse(b); //识别图片返回结果
r.setNormalFieldValue(r.getFieldIndex( "result"), result ); //把识别结果保存在本行的result字段中
}
return table;
}
在调用时,需要为此方法传入一个序表参数,如:
A |
|
1 |
$select scenedate,pic,result from scene |
2 |
=invoke(test.Calc01.recognizeScene, A1) |
5. 使用游标
用游标作为参数时与使用序表的用法比较类似,不再举例了。下面主要讲一下游标作为返回值的用法。
如果被调用的方法要返回游标值,那么需要返回的类要实现接口com.scudata.dm.ILineInput。如下面的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;
}
}
ILineInput类中需要实现readLine、skipLine和close这3个方法。上面的RandDataCursor比较简单,用来生成由随机整数构成的序列,其中随机数的范围可以用参数指定。readLine返回的每条记录,都由顺次增加的行号和随机数构成,类似于表的两个字段。在skipLine方法中,可以跳过1条记录,并返回游标中是否仍有剩余数据,上面例子中设定这个游标最多返回10000条数据。close方法则用来关闭游标,释放不需要的资源,如数据库连接等。
这样,返回游标的自定义函数即可将RandDataCursor作为返回类,如:
package test;
public class Calc02 {
public static test.RandDataCursor getCursor(Integer range) {
RandDataCursor rdc = new RandDataCursor(range);
return rdc;
}
}
这个自定义函数只需定义一个参数,即随机数的范围,返回的结果就是RandDataCursor,用来生成随机记录的游标。
在调用时,返回的ILineInput类并不能直接作为游标使用,还需要调用com.scudata.dm.UserUtils类中的newCursor方法,如:
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中,返回自定义游标RandDataCursor。A2中,调用UserUtils中的newCursor方法,将A1中的结果作为参数传入,A2中的数据即为SPL中的游标数据了,此时第3个参数是游标的选项,如"t"表示第1行游标数据是标题行。A3中跳过前100条记录后,A4中取出100条数据后关闭游标,A4中结果如下: