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函数时,先指明所调用类的全路径以及静态方法名称,然后把需要用到的参数依次列出。使用的参数既可以是网格中的格值、网格参数等,也可以直接输入。计算后,A2A3中结果分别如下:

..   ..

同一个类中,可以定义多个被调用的静态方法。

 

2.   返回值与参数的数据类型

被调用的方法可以返回值,也可以不返回值,只完成运算过程或将结果写入文件或输出到控制台。与Java不同的是,参数与返回值都必须是一个Java对象,不能是intdouble等数据类型。

下表是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类中需要实现readLineskipLineclose3个方法。上面的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中,返回自定义游标RandDataCursorA2中,调用UserUtils中的newCursor方法,将A1中的结果作为参数传入,A2中的数据即为SPL中的游标数据了,此时第3个参数是游标的选项,如"t"表示第1行游标数据是标题行A3中跳过前100条记录后,A4中取出100条数据后关闭游标,A4中结果如下:

..