SPL:自定义函数

SPL中,除了使用系统提供的各类函数,还可以编写实现某种特殊功能的自定义函数,登记注册以后就可以象系统函数一样直接在SPL脚本中使用。

1.   定义、部署及注册

每个自定义函数都用一个Java类来进行定义,它必须满足以下条件:

1、  类是公开的,即public class

2、  类继承自函数基类com.scudata.expression.Function

3、  实现基类中的抽象方法public Object calculate(Context ctx),在此方法中进行函数参数、选项的解析,完成函数应有的功能,返回函数的计算结果。ctx是函数运行的上下文环境,包括有系统配置文件中的各种配置及SPL脚本文件的参数等。

自定义函数类编写好以后,要部署在SPL的类路径中。通常可以放在jar包里,然后把jar放在安装目录下的lib目录中,或者将此类的class文件放在安装目录下的classes目录中。如果是在web应用里,那么就部署到web应用的类路径中。

编写的自定义函数需要在SPL中登记注册以后,才能使用。注册的办法是编写一个自定义函数配置文件,文件格式如下:

..

文件中每行表示一个函数定义,等号前面部分是函数名,函数名必须是系统里唯一的,不能与系统函数同名或其它自定义函数同名。等号后面分2部分,用逗号隔开。第1部分的值为010表示是普通函数,1表示是某对象的成员函数,例如字符串对象的函数s.len()。第2部分是自定义函数的完整Java类名。

在应用启动的时候,还要调用下面的方法来登记注册自定义函数:

com.scudata.expression.FunctionLib.loadCustomFunctions("d:/myapplication/customFunction.properties");

参数就是编写好的自定义函数配置文件的全路径名。

特别地,如果是在esProc IDE中使用,只要把自定义函数配置在安装目录下的config目录中的customFunctions.properties文件中,IDE启动时就会自动注册它们了。或者在 IDE 的配置窗口中进行设置,如下图所示:

 

2.  参数及选项的解析

2.1   基类 Function

自定义函数Java类是从基类com.scudata.expression.Function继承的,所以可以直接使用Function类中的属性,有以下常用属性:

protected String functionName     函数名

protected String option                函数选项,用户在函数表达式中添加的所有选项串,比如f@tcq()option的值就是"tcq"

protected com.scudata.expression.IParam param  函数参数对象

 

2.2   参数类 IParam

函数参数会被系统解析以后保存在基类Function中的param里,它是我们熟悉的树形层次结构,param就是参数树的根节点,可以用com.scudata.expression.IParam接口的方法来获取、分析函数参数,接口方法如下:

char getType()       返回参数节点的类型,即分隔符的类型,有3种:IParam.Semicolon(分号)IParam.Comma(逗号)IParam.Colon(冒号)。例如f(p1,p2;a1)的参数节点类型是分号,f(p1,p2)的节点类型是逗号,f(p1:p2)的节点类型是冒号。

boolean isLeaf()     返回是否是叶子节点(没有子节点)。例如f(p1,p2)中根节点有p1p2两个子节点,不是叶子节点。p1节点没有子节点,它是叶节点。

int getSubSize()     返回子节点数。例如f(p1,p2;a1;k:n)的根节点有3个子节点,第3个子节点k:n又有2个子节点,分别是kn

IParam getSub(int index)      返回第index个子节点,编号从0开始。

Expression getLeafExpression()   返回当前叶子节点的表达式

 

2.3   简单参数解析实例

先看一看参数很简单的系统函数abs的源代码,学习参数的解析。其完整的函数语法如下:

abs(number)

此函数的功能是求某数值number的绝对值,它对应的Java类是com.scudata.expression.fn.math.Abs,源代码如下:

..

12行判断参数如果为空或者不是叶子节点,则报告参数错误。第17行用getLeafExpression()方法取得参数的表达式,调用calculate(ctx)计算出表达式的值,即为参数值。如果参数值是数值型,调用Variant.abs(result)计算出参数的绝对值并返回,如果参数是null则返回null,如果是非空的其它数据类型,则报告参数数据类型错误。

 

2.4   复杂参数解析实例

再看一看参数比较复杂的系统函数httpfile的源代码,学习复杂参数的解析。其完整的函数语法如下:

httpfile(url:cs, post:cs, contenttype; header:value,....)

此函数的功能是访问一个http页面,取得页面的内容。url是要访问的http页面地址,cs是本页面返回内容的字符集编码。post是以POST方式传送的访问url时的参数串,它后面的cs是此参数串的字符集编码。contenttype是传送的申请参数的格式。分号后面的是一些申请头RequestHeader属性,可以有多个属性。

此函数对应的Java类是com.scudata.expression.fn.CreateHttpFile,先看看前面部分源代码:

..

this.param就是代表函数参数树的根节点。在httpfile函数中,不允许参数为空,至少要有url参数,所以35行检查参数是否为空,为空则报告错误信息。此函数可以没有分号后的申请头参数,38行检查根节点的类型是否是分号,如果是,再检查子节点数是不是2,如果不是则报告函数参数不正确,如果子节点是2,则设申请头参数节点headerParam为第2个子节点,param为第1个子节点,如果param为空则报告函数参数错误。

接着再看下面的源代码:

..

定义url节点变量urlParampost参数子节点变量postParam55行判断param的类型是否是逗号,如果是,检查它的子节点数,如果大于3则报告函数参数错误。取param的第1个子节点为urlParam,为空则报参数错误。取param2个子节点为postParam。如果param子节点数大于2,则取param3个子节点为post参数格式typeParam,当它不为空时,用getLeafExpression()方法取得它的表达式,调用calculate(ctx)计算出表达式的值,如果值为字符串对象,则赋值给变量type,否则报告参数数据类型错误。

如果param的类型不是逗号,说明param节点只有url参数,没有postcontenttype参数,所以设paramurlParampostParam为空。

..

86行检查urlParam是不是叶子节点,如果是,说明url参数没有字符集属性,用urlParam.getLeafExpression().calculate(ctx)取得表达式并计算出url参数值,值不是字符串时报错。

如果urlParam不是叶子节点,说明还有字符集属性参数,检查子节点个数是否是2,取第1个子节点表达式计算出url路径,检查是否非空、是否字符串值。取第2个子节点表达式计算出字符集参数值,检查是否为字符串值。

源代码中还有其它参数的解析,基本方法都与上述类似了,读者可以自行去阅读源码。

 

3.   参数及返回值数据类型

函数参数表达式计算后所得的参数值都是一个Object对象,函数返回值也是一个Object对象,ObjectJava中所有对象的super类,编程中应检查参数计算值实例是否是需要的数据类型,若是则进行转换后使用,否则应报参数数据类型错误。

下表是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 []

序列

com.scudata.dm.Sequence

序表

com.scudata.dm.Table

游标

com.scudata.dw.Cursor

普通的数据类型使用起来都比较简单,序列、序表和游标的使用请参阅《SPL:调用 Java 函数》中的相关内容。不过,与invoke不同的是,编写自定义函数时不能再把序列转换成Object[]了,而必须使用com.scudata.dm.Sequence对象