一、特殊名词介绍
无
二、背景、场景介绍
帆软报表产品中提供了非常丰富的拓展函数,帮助用户对数据进行二次处理。不过对于很多专业领域或者用户固有业务的特殊性诉求。常规的函数往往不足以满足需要,所以在插件开发中提供了函数扩展的接口。
比较常见的就是AbstractFunction和FunctionDefContainer。AbstractFunction提供了单个函数的扩展能力。对应的FunctionDefContainer提供的是批量函数分组的扩展能力。
简而言之:如果诉求仅仅是扩展单个函数即可满足诉求,那么使用AbstractFunction即可;如果是需要扩展一组专门处理某特定业务的函数,则推荐使用FunctionDefContainer。
三、接口介绍
package com.fr.stable.fun;
import com.fr.stable.fun.mark.Mutable;
import com.fr.stable.script.FunctionDef;
/**
* 接口用于生成算子时初始化函数分组里面的函数
*/
public interface FunctionDefContainer extends Mutable {
int CURRENT_LEVEL = 1;
String MARK_STRING = "FunctionGroup";
/**
* 返回该分组内所有函数的描述
* @return
*/
FunctionDef[] getFunctionDefs();
/**
* 返回该分组的名字
* @return
*/
String getGroupName();
}
package com.fr.stable.script;
import com.fr.plugin.injectable.SpecialLevel;
import com.fr.stable.StringUtils;
import com.fr.stable.fun.FunctionDefineProvider;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLable;
import com.fr.stable.xml.XMLableReader;
/**
* 函数定义的细节,包含了函数名和对于该函数使用的描述
*/
//// TODO: 2017/12/14 待修改 ,用holder封装
public class FunctionDef implements FunctionDefineProvider, XMLable {
public static final String XML_TAG = SpecialLevel.FunctionDef.getTagName();
public static final int CURRENT_LEVEL = 1;
private String name = StringUtils.EMPTY;
private String description = StringUtils.EMPTY;
private String className;
/**
* 默认的构造函数
*/
public FunctionDef() {
this(StringUtils.EMPTY, StringUtils.EMPTY);
}
/**
* @param name 函数的名字
* @param className 函数的类名
*/
public FunctionDef(String name, String className) {
this(name, StringUtils.EMPTY, className);
}
/**
* @param name 函数的名字
* @param description 函数的功能的描述
* @param className 函数的类名
*/
public FunctionDef(String name, String description, String className) {
this.setName(name);
this.setDescription(description);
this.setClassName(className);
}
public int currentAPILevel() {
return CURRENT_LEVEL;
}
/**
* @return 函数的名字
*/
public String getName() {
return this.name;
}
/**
* 设置函数的名字
*/
public void setName(String name) {
this.name = name;
}
/**
* @return 函数功能的描述
*/
public String getDescription() {
return description;
}
/**
* 设置对函数功能的描述
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return 函数的类名
*/
public String getClassName() {
return className;
}
/**
* 设置函数的类名
*/
public void setClassName(String className) {
this.className = className;
}
public void readXML(XMLableReader reader) {
}
public void writeXML(XMLPrintWriter writer) {
}
@Override
public boolean equals(Object obj) {
return obj instanceof FunctionDef && name != null && name.equals(((FunctionDef) obj).name);
}
@Override
public int hashCode() {
return name == null ? 0 : name.hashCode();
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.fr.stable.script;
import com.fr.stable.UtilEvalError;
import java.io.Serializable;
import java.util.Locale;
public interface Function extends Serializable {
Function.Type MATH = new Function.Type();
Function.Type TEXT = new Function.Type();
Function.Type DATETIME = new Function.Type();
Function.Type LOGIC = new Function.Type();
Function.Type ARRAY = new Function.Type();
Function.Type REPORT = new Function.Type();
Function.Type HA = new Function.Type();
Function.Type OTHER = new Function.Type();
Function.Type DELETE = new Function.Type();
void setName(String var1);
CalculatorProvider getCalculatorProvider();
void setCalculator(CalculatorProvider var1);
/** @deprecated */
@Deprecated
String getCN();
/** @deprecated */
@Deprecated
String getEN();
String getDescription(Locale var1);
Function.Type getType();
Object evalExpression(Node[] var1) throws UtilEvalError;
public static final class Type {
private Type() {
}
}
}
package com.fr.script;
import com.fr.common.annotations.Open;
import com.fr.locale.InterProviderFactory;
import com.fr.stable.Primitive;
import com.fr.stable.UtilEvalError;
import com.fr.stable.exception.FormulaException;
import com.fr.stable.script.Node;
/**
* 内置函数/自定义函数的抽象实现类.
*
* @author richie
* created on 2020-09-04
*/
@Open
public abstract class AbstractFunction extends CalculatorEmbeddedFunction {
/**
* 通过指定的参数,计算函数的结果.
*
* @param arguments 参数列表
* @return 函数的结果
* @throws UtilEvalError 如果在计算中出现无法解析的值,则抛出此异常
*/
@Override
public Object evalExpression(Node[] arguments) throws UtilEvalError {
Object returnValue;
if (this.getType() == HA) {
//层次坐标系列的不在这边eval
returnValue = tryRun(arguments);
} else {
Object[] args = new Object[arguments.length];
for (int i = 0; i < arguments.length; i++) {
args[i] = this.getCalculatorProvider().evalValue(arguments[i]);
}
returnValue = tryRun(args);
}
if (returnValue == Primitive.ERROR_VALUE || returnValue == Primitive.ERROR_NAME) {
log(InterProviderFactory.getProvider().getLocText("Fine-Core_Base_NS_Cell_Formula") + toString());
}
return returnValue;
}
/**
* 获取函数所使用的算子.
*
* @return 算子
*/
@Deprecated
public Calculator getCalculator() {
return (Calculator) getCalculatorProvider();
}
private Object tryRun(Object[] args) throws UtilEvalError {
try {
return run(args);
} catch (FormulaException fe) {
throw new UtilEvalError(fe);
}
}
/**
* 计算函数的结果.
*
* @param args 函数的参数,是经过了算子处理了其中特殊参数的
* @return 函数的结果
* @throws FormulaException 函数计算过程中抛出的异常
*/
public abstract Object run(Object[] args) throws FormulaException;
}
四、支持版本
产品线 | 版本 | 支持情况 | 备注 |
---|
FR | 8.0 | 支持 |
|
FR | 9.0 | 支持 |
|
FR | 10.0 | 支持 |
|
BI | 3.6 | 支持 |
|
BI | 4.0 | 支持 |
|
BI | 5.1 | 支持 |
|
BI | 5.1.2 | 支持 |
|
BI | 5.1.3 | 支持 |
|
五、插件注册
<extra-core>
<!-- 注册一组函数的方法 -->
<FunctionGroup class="your class name"/>
<!-- 注册单个函数的方法 -->
<FunctionDefineProvider class="your class name"
name="函数名" description="函数的用法介绍"/>
</extra-core>
六、原理说明
单一函数注册生效是当插件引擎加载插件后,ExtraClassManager通过mountSpecific调用addFunctionDef在其中把xml中的class/name/description 三个属性转换成FunctionDef保存在functions对象中缓存;也就是一般意义的FunctionDefineProvider接口
算子是报表计算过程中的一个核心,函数会通过DefaultNameSpace注册到算子中生效,在DefaultNameSpace#getMethod获取函数时,如果目标函数不再标准函数集内
会先查找自定义函数中的扩展函数。如果找不到,会从ExtraClassManager的functions对象中获取插件函数中的单个函数扩展,如果还是找不到,会继续获从分组函数扩展中获取。最终生效
七、特殊限制说明
无
八、常用链接
demo地址:demo-function-def-container
九、开源案例
免责声明:所有文档中的开源示例,均为开发者自行开发并提供。仅用于参考和学习使用,开发者和官方均无义务对开源案例所涉及的所有成果进行教学和指导。禁止用于任何商业用途,若作为商用一切后果责任由使用者自行承担。
demo-function-fibonacci
open-JSD-7837
open-JSD-7615