├── .gitignore
├── src
├── main
│ ├── resources
│ │ └── META-INF
│ │ │ └── services
│ │ │ └── tk.fishfish.formula.plugin.Plugin
│ └── java
│ │ └── tk
│ │ └── fishfish
│ │ └── formula
│ │ ├── plugin
│ │ ├── Plugin.java
│ │ └── TextPlugin.java
│ │ ├── exception
│ │ ├── DagException.java
│ │ ├── FormulaException.java
│ │ ├── RegistryFormulaException.java
│ │ └── FormulaTaskException.java
│ │ ├── util
│ │ └── StringUtils.java
│ │ ├── annotation
│ │ └── FormulaMapping.java
│ │ ├── script
│ │ ├── BaseScript.java
│ │ ├── VariableFormulaScript.java
│ │ └── FormulaScript.java
│ │ ├── reflect
│ │ └── Invocation.java
│ │ ├── dag
│ │ ├── FormulaData.java
│ │ ├── FormulaTask.java
│ │ └── Dag.java
│ │ ├── Formula.java
│ │ └── DagFormulaEngine.java
└── test
│ ├── resources
│ ├── META-INF
│ │ └── services
│ │ │ └── tk.fishfish.formula.plugin.Plugin
│ └── logback.xml
│ └── java
│ └── tk
│ └── fishfish
│ └── formula
│ ├── plugin
│ └── CustomPlugin.java
│ ├── DagFormulaEngineTest.java
│ └── FormulaTest.java
├── pom.xml
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ### OS X ###
2 | .DS_Store
3 |
4 | ### IDEA ###
5 | .idea/
6 | *.iml
7 |
8 | ### Maven ###
9 | target
10 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/tk.fishfish.formula.plugin.Plugin:
--------------------------------------------------------------------------------
1 | # custom plugin
2 | tk.fishfish.formula.plugin.TextPlugin
--------------------------------------------------------------------------------
/src/test/resources/META-INF/services/tk.fishfish.formula.plugin.Plugin:
--------------------------------------------------------------------------------
1 | # custom plugin
2 | # tk.fishfish.formula.plugin.CustomPlugin
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/plugin/Plugin.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.plugin;
2 |
3 | /**
4 | * 公式扩展
5 | *
6 | * @author 奔波儿灞
7 | * @since 1.0
8 | */
9 | public interface Plugin {
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/exception/DagException.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.exception;
2 |
3 | /**
4 | * DAG异常
5 | *
6 | * @author 奔波儿灞
7 | * @since 1.0
8 | */
9 | public class DagException extends FormulaException {
10 |
11 | public DagException(String message) {
12 | super(message);
13 | }
14 |
15 | public DagException(String message, Throwable cause) {
16 | super(message, cause);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/exception/FormulaException.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.exception;
2 |
3 | /**
4 | * 公式异常
5 | *
6 | * @author 奔波儿灞
7 | * @since 1.0
8 | */
9 | public class FormulaException extends RuntimeException {
10 |
11 | public FormulaException(String message) {
12 | super(message);
13 | }
14 |
15 | public FormulaException(String message, Throwable cause) {
16 | super(message, cause);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/exception/RegistryFormulaException.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.exception;
2 |
3 | /**
4 | * 注册公式异常
5 | *
6 | * @author 奔波儿灞
7 | * @since 1.0
8 | */
9 | public class RegistryFormulaException extends FormulaException {
10 |
11 | public RegistryFormulaException(String message) {
12 | super(message);
13 | }
14 |
15 | public RegistryFormulaException(String message, Throwable cause) {
16 | super(message, cause);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/util/StringUtils.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.util;
2 |
3 | /**
4 | * string工具类
5 | *
6 | * @author 奔波儿灞
7 | * @since 1.0
8 | */
9 | public final class StringUtils {
10 |
11 | private StringUtils() {
12 | throw new IllegalStateException("Utils");
13 | }
14 |
15 | public static boolean isEmpty(String value) {
16 | return value == null || value.isEmpty();
17 | }
18 |
19 | public static boolean isNotEmpty(String value) {
20 | return !isEmpty(value);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %highlight(%-5level) %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %cyan(%-50logger{50}) - %highlight(%msg%n)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/annotation/FormulaMapping.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.annotation;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | /**
10 | * 公式名映射
11 | *
12 | * @author 奔波儿灞
13 | * @since 1.0
14 | */
15 | @Documented
16 | @Target(ElementType.METHOD)
17 | @Retention(RetentionPolicy.RUNTIME)
18 | public @interface FormulaMapping {
19 |
20 | /**
21 | * 公式名,必须唯一
22 | *
23 | * @return 公式名
24 | */
25 | String value();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/script/BaseScript.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.script;
2 |
3 | import groovy.lang.MissingPropertyException;
4 | import groovy.lang.Script;
5 |
6 | /**
7 | * 基础脚本
8 | *
9 | * @author 奔波儿灞
10 | * @since 1.0
11 | */
12 | public abstract class BaseScript extends Script {
13 |
14 | @Override
15 | public Object run() {
16 | return null;
17 | }
18 |
19 | @Override
20 | public Object getProperty(String propertyName) {
21 | try {
22 | return super.getProperty(propertyName);
23 | } catch (MissingPropertyException ignored) {
24 | // 注意,这里为了实现字符串无需引号功能
25 | return propertyName;
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/plugin/TextPlugin.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.plugin;
2 |
3 | import tk.fishfish.formula.annotation.FormulaMapping;
4 |
5 | import java.util.UUID;
6 |
7 | /**
8 | * 文本公式
9 | *
10 | * @author 奔波儿灞
11 | * @since 1.0.0
12 | */
13 | public class TextPlugin implements Plugin {
14 |
15 | /**
16 | * 返回UUID
17 | *
18 | * @return UUID
19 | */
20 | @FormulaMapping("UUID")
21 | public String uuid() {
22 | return UUID.randomUUID().toString();
23 | }
24 |
25 | /**
26 | * 文本转小写
27 | *
28 | * @param text 文本
29 | * @return 小写
30 | */
31 | @FormulaMapping("LOWER")
32 | public String lower(String text) {
33 | return text.toLowerCase();
34 | }
35 |
36 | /**
37 | * 文本转大写
38 | *
39 | * @param text 文本
40 | * @return 大写
41 | */
42 | @FormulaMapping("UPPER")
43 | public String upper(String text) {
44 | return text.toUpperCase();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/exception/FormulaTaskException.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.exception;
2 |
3 | import tk.fishfish.formula.dag.FormulaTask;
4 |
5 | /**
6 | * 公式任务异常
7 | *
8 | * @author 奔波儿灞
9 | * @since 1.0.0
10 | */
11 | public class FormulaTaskException extends FormulaException {
12 |
13 | private final FormulaTask formulaTask;
14 |
15 | public FormulaTaskException(FormulaTask formulaTask, String message) {
16 | super(message);
17 | this.formulaTask = formulaTask;
18 | }
19 |
20 | public FormulaTaskException(FormulaTask formulaTask, Throwable cause) {
21 | super(cause.getMessage(), cause);
22 | this.formulaTask = formulaTask;
23 | }
24 |
25 | public FormulaTaskException(FormulaTask formulaTask, String message, Throwable cause) {
26 | super(message, cause);
27 | this.formulaTask = formulaTask;
28 | }
29 |
30 | public FormulaTask getFormulaTask() {
31 | return formulaTask;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/tk/fishfish/formula/plugin/CustomPlugin.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.plugin;
2 |
3 | import tk.fishfish.formula.annotation.FormulaMapping;
4 |
5 | import java.math.BigDecimal;
6 | import java.util.Arrays;
7 | import java.util.Objects;
8 |
9 | /**
10 | * 自定义公式
11 | *
12 | * @author 奔波儿灞
13 | * @since 1.0
14 | */
15 | public class CustomPlugin implements Plugin {
16 |
17 | /**
18 | * 实现自己的公式,echo
19 | *
20 | * @param name 参数
21 | * @return 结果
22 | */
23 | @FormulaMapping("ECHO")
24 | public String echo(String name) {
25 | return "echo: " + name;
26 | }
27 |
28 | /**
29 | * 返回所有数字相加的和
30 | *
31 | * @param array 数值集合
32 | * @return 相加的和
33 | */
34 | @FormulaMapping("SUM")
35 | public BigDecimal sum(Object... array) {
36 | if (array == null || array.length == 0) {
37 | return null;
38 | }
39 | return Arrays.stream(array)
40 | .filter(Objects::nonNull)
41 | .map(Object::toString)
42 | .map(BigDecimal::new)
43 | .reduce(BigDecimal::add)
44 | .orElse(null);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/reflect/Invocation.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.reflect;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 |
6 | /**
7 | * 调用对象
8 | *
9 | * @author 奔波儿灞
10 | * @since 1.0
11 | */
12 | public class Invocation {
13 |
14 | private final Method method;
15 | private final Object target;
16 | private final int parameterCount;
17 | private final Class>[] parameterTypes;
18 |
19 | public Invocation(Method method, Object target) {
20 | this.method = method;
21 | this.target = target;
22 | this.parameterCount = method.getParameterCount();
23 | this.parameterTypes = method.getParameterTypes();
24 | }
25 |
26 | public Object invoke(Object[] args) throws InvocationTargetException, IllegalAccessException {
27 | // 如果方法没有参数,则忽略传递的参数
28 | if (this.parameterCount == 0) {
29 | return method.invoke(target);
30 | }
31 | // 如果方法只有一个参数
32 | if (this.parameterCount == 1) {
33 | // 如果是数组(不定数组),则作为一个参数传递
34 | if (parameterTypes[0].isArray()) {
35 | return method.invoke(target, new Object[]{args});
36 | }
37 | // 不是数组,则取第一个值传递
38 | return method.invoke(target, args == null || args.length == 0 ? null : args[0]);
39 | }
40 | // 如果方法有多个参数,则将参数数组传递,按位匹配
41 | return method.invoke(target, args);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/tk/fishfish/formula/DagFormulaEngineTest.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula;
2 |
3 | import org.junit.Before;
4 | import org.junit.BeforeClass;
5 | import org.junit.Test;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import tk.fishfish.formula.dag.FormulaData;
9 | import tk.fishfish.formula.plugin.CustomPlugin;
10 | import tk.fishfish.formula.script.FormulaScript;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * @author 奔波儿灞
17 | * @since 1.0.0
18 | */
19 | public class DagFormulaEngineTest {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(FormulaTest.class);
22 |
23 | private DagFormulaEngine engine;
24 |
25 | @BeforeClass
26 | public static void init() {
27 | // 安装自己的公式插件
28 | FormulaScript.installPlugin(CustomPlugin.class);
29 | }
30 |
31 | @Before
32 | public void setup() {
33 | engine = new DagFormulaEngine(new Formula());
34 | }
35 |
36 | @Test
37 | public void run() {
38 | // 批量计算的公式、数据
39 | List formulaDataList = new ArrayList() {{
40 | add(new FormulaData("field1", 0, null, 1));
41 | add(new FormulaData("field1", 1, null, 3));
42 | add(new FormulaData("field1", 2, null, 5));
43 | add(new FormulaData("field2", -1, null, "ABC"));
44 | add(new FormulaData("field3", -1, "LOWER('#{field2}')", null));
45 | add(new FormulaData("field4", -1, "SUM('#{field1}')", 0));
46 | }};
47 | // 计算
48 | engine.run(formulaDataList);
49 | // 结果
50 | formulaDataList.forEach(formulaData ->
51 | LOG.info("name: {}, formula: {}, value: {}",
52 | formulaData.getName(), formulaData.getFormula(), formulaData.getValue())
53 | );
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/dag/FormulaData.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.dag;
2 |
3 | import java.io.Serializable;
4 | import java.util.Objects;
5 |
6 | /**
7 | * 公式数据
8 | *
9 | * @author 奔波儿灞
10 | * @since 1.0
11 | */
12 | public final class FormulaData implements Serializable {
13 |
14 | /**
15 | * 唯一名称
16 | */
17 | private String name;
18 |
19 | /**
20 | * 索引
21 | */
22 | private int index;
23 |
24 | /**
25 | * 公式
26 | */
27 | private String formula;
28 |
29 | /**
30 | * 数据
31 | */
32 | private Object value;
33 |
34 | public FormulaData(String name, int index, String formula, Object value) {
35 | this.name = name;
36 | this.index = index;
37 | this.formula = formula;
38 | this.value = value;
39 | }
40 |
41 | public FormulaData(String name, String formula, Object value) {
42 | this(name, -1, formula, value);
43 | }
44 |
45 | public String getName() {
46 | return name;
47 | }
48 |
49 | public void setName(String name) {
50 | this.name = name;
51 | }
52 |
53 | public int getIndex() {
54 | return index;
55 | }
56 |
57 | public void setIndex(int index) {
58 | this.index = index;
59 | }
60 |
61 | public String getFormula() {
62 | return formula;
63 | }
64 |
65 | public void setFormula(String formula) {
66 | this.formula = formula;
67 | }
68 |
69 | public Object getValue() {
70 | return value;
71 | }
72 |
73 | public void setValue(Object value) {
74 | this.value = value;
75 | }
76 |
77 | @Override
78 | public boolean equals(Object o) {
79 | if (this == o) {
80 | return true;
81 | }
82 | if (o == null || getClass() != o.getClass()) {
83 | return false;
84 | }
85 | FormulaData that = (FormulaData) o;
86 | return index == that.index && name.equals(that.name);
87 | }
88 |
89 | @Override
90 | public int hashCode() {
91 | return Objects.hash(name, index);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/tk/fishfish/formula/FormulaTest.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula;
2 |
3 | import groovy.lang.Binding;
4 | import org.junit.Assert;
5 | import org.junit.Before;
6 | import org.junit.BeforeClass;
7 | import org.junit.Test;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import tk.fishfish.formula.plugin.CustomPlugin;
11 | import tk.fishfish.formula.script.FormulaScript;
12 |
13 | import java.util.Date;
14 |
15 | /**
16 | * 公式测试
17 | *
18 | * @author 奔波儿灞
19 | * @since 1.0
20 | */
21 | public class FormulaTest {
22 |
23 | private static final Logger LOG = LoggerFactory.getLogger(FormulaTest.class);
24 |
25 | private Formula formula;
26 |
27 | @BeforeClass
28 | public static void init() {
29 | // 安装自己的公式插件
30 | FormulaScript.installPlugin(CustomPlugin.class);
31 | }
32 |
33 | @Before
34 | public void setup() {
35 | formula = new Formula();
36 | }
37 |
38 | @Test
39 | public void echo() {
40 | Object result = formula.run("ECHO(大侠王波波)");
41 | LOG.info("result: {}", result);
42 | }
43 |
44 | @Test
45 | public void uuid() {
46 | Object result = formula.run("UUID()");
47 | LOG.info("result: {}", result);
48 | }
49 |
50 | @Test
51 | public void lower() {
52 | Object result = formula.run("LOWER(ABC)");
53 | Assert.assertEquals("abc", result);
54 | }
55 |
56 | @Test
57 | public void variable() {
58 | Binding binding = new Binding();
59 | binding.setVariable("xxx", "ABC");
60 | Object result = formula.run("LOWER(xxx)", binding);
61 | Assert.assertEquals("abc", result);
62 | }
63 |
64 | @Test
65 | public void runJava() {
66 | Binding binding = new Binding();
67 | binding.setVariable("value", new Date());
68 | Object result = formula.runJava("import java.text.SimpleDateFormat;\n" +
69 | "SimpleDateFormat format = new SimpleDateFormat(\"yyyy年MM月dd日\");\n" +
70 | // 这里通过上下文传入value为当前时间,代码块中可直接使用
71 | // 最后一行的结果做为返回值
72 | "format.format(value);", binding);
73 | LOG.info("result: {}", result);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/script/VariableFormulaScript.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.script;
2 |
3 | import groovy.lang.Binding;
4 | import groovy.lang.MissingPropertyException;
5 | import tk.fishfish.formula.DagFormulaEngine;
6 | import tk.fishfish.formula.dag.FormulaTask;
7 | import tk.fishfish.formula.exception.FormulaException;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * 绑定变量公式
13 | *
14 | * @author 奔波儿灞
15 | * @since 1.0
16 | */
17 | public class VariableFormulaScript extends FormulaScript {
18 |
19 | private static final String VARIABLE_START = "#{";
20 | private static final String VARIABLE_END = "}";
21 |
22 | @Override
23 | public Object invokeMethod(String name, Object args) {
24 | // 解析变量
25 | parseVariables((Object[]) args);
26 | // 方法调用
27 | return super.invokeMethod(name, args);
28 | }
29 |
30 | private void parseVariables(Object[] args) {
31 | for (int i = 0; i < args.length; i++) {
32 | Object arg = args[i];
33 | if (arg instanceof String) {
34 | String argStr = (String) arg;
35 | if (argStr.startsWith(VARIABLE_START) && argStr.endsWith(VARIABLE_END)) {
36 | String variable = argStr.substring(VARIABLE_START.length(), argStr.length() - 1);
37 |
38 | // 对于明细引用明细,修正为当前行
39 | variable = parseVariable(variable, getBinding());
40 |
41 | // 从绑定上下文中取值
42 | Object value = null;
43 | try {
44 | value = getBinding().getProperty(variable);
45 | } catch (MissingPropertyException e) {
46 | // ignore
47 | }
48 | args[i] = value;
49 | }
50 | }
51 | }
52 | }
53 |
54 | @SuppressWarnings("unchecked")
55 | private String parseVariable(String variable, Binding binding) {
56 | try {
57 | int index = (int) binding.getVariable(DagFormulaEngine.BINDING_CURRENT_INDEX);
58 | if (index > -1) {
59 | List tasks = (List) binding.getVariable(DagFormulaEngine.BINDING_TASKS);
60 | FormulaTask task = tasks.stream()
61 | .filter(e -> e.getName().equals(variable))
62 | .findFirst()
63 | .orElseThrow(() -> new FormulaException("公式引擎错误,找不到_tasks系统变量"));
64 | if (task.getIndex() > -1) {
65 | return variable + "[" + index + "]";
66 | }
67 | }
68 | return variable;
69 | } catch (MissingPropertyException e) {
70 | return variable;
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/dag/FormulaTask.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.dag;
2 |
3 | import groovy.lang.Binding;
4 | import groovy.lang.MissingPropertyException;
5 | import tk.fishfish.formula.Formula;
6 | import tk.fishfish.formula.exception.DagException;
7 | import tk.fishfish.formula.util.StringUtils;
8 |
9 | import java.util.Objects;
10 |
11 | /**
12 | * 公式任务
13 | *
14 | * @author 奔波儿灞
15 | * @since 1.0
16 | */
17 | public class FormulaTask implements Runnable {
18 |
19 | private static final int MAX_SIZE = 100;
20 |
21 | private final Formula formula;
22 |
23 | private final Binding binding;
24 |
25 | private final FormulaData formulaData;
26 |
27 | public FormulaTask(Formula formula, Binding binding, FormulaData formulaData) {
28 | this.formula = formula;
29 | this.binding = binding;
30 | this.formulaData = formulaData;
31 | }
32 |
33 | @Override
34 | public void run() {
35 | Object result = formulaData.getValue();
36 | // 初始值先放入上下文,防止自引用问题
37 | if (result != null) {
38 | // 明细表索引
39 | if (formulaData.getIndex() > -1) {
40 | binding.setVariable(formulaData.getName() + "[" + formulaData.getIndex() + "]", result);
41 | } else {
42 | binding.setVariable(formulaData.getName(), result);
43 | }
44 | }
45 | if (StringUtils.isNotEmpty(formulaData.getFormula())) {
46 | result = formula.run(formulaData.getFormula(), binding);
47 | }
48 | // 计算出的结果放入上下文
49 | if (formulaData.getIndex() > -1) {
50 | Object[] values;
51 | try {
52 | values = (Object[]) binding.getVariable(formulaData.getName());
53 | } catch (MissingPropertyException e) {
54 | values = new Object[MAX_SIZE];
55 | binding.setVariable(formulaData.getName(), values);
56 | }
57 | if (formulaData.getIndex() >= values.length) {
58 | throw new DagException("dag array max size: " + values.length);
59 | }
60 | values[formulaData.getIndex()] = result;
61 | // 明细表索引
62 | binding.setVariable(formulaData.getName() + "[" + formulaData.getIndex() + "]", result);
63 | } else {
64 | binding.setVariable(formulaData.getName(), result);
65 | }
66 | // 设置结果
67 | formulaData.setValue(result);
68 | }
69 |
70 | public String getName() {
71 | return formulaData.getName();
72 | }
73 |
74 | public int getIndex() {
75 | return formulaData.getIndex();
76 | }
77 |
78 | public String getFormula() {
79 | return formulaData.getFormula();
80 | }
81 |
82 | @Override
83 | public boolean equals(Object o) {
84 | if (this == o) {
85 | return true;
86 | }
87 | if (o == null || getClass() != o.getClass()) {
88 | return false;
89 | }
90 | FormulaTask that = (FormulaTask) o;
91 | return formulaData.equals(that.formulaData);
92 | }
93 |
94 | @Override
95 | public int hashCode() {
96 | return Objects.hash(formulaData);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/script/FormulaScript.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula.script;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import tk.fishfish.formula.annotation.FormulaMapping;
6 | import tk.fishfish.formula.exception.FormulaException;
7 | import tk.fishfish.formula.exception.RegistryFormulaException;
8 | import tk.fishfish.formula.plugin.Plugin;
9 | import tk.fishfish.formula.reflect.Invocation;
10 |
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.util.Arrays;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 | import java.util.ServiceLoader;
16 |
17 | /**
18 | * 公式脚本
19 | *
20 | * @author 奔波儿灞
21 | * @since 1.0
22 | */
23 | public class FormulaScript extends BaseScript {
24 |
25 | private static final Logger LOG = LoggerFactory.getLogger(FormulaScript.class);
26 |
27 | private static final Map PLUGIN_METHOD_MAPPING = new HashMap<>();
28 |
29 | static {
30 | loadSpiMappings();
31 | }
32 |
33 | @Override
34 | public Object invokeMethod(String name, Object args) {
35 | Invocation invocation = PLUGIN_METHOD_MAPPING.get(name);
36 | if (invocation == null) {
37 | throw new FormulaException("can not found method for formula: " + name);
38 | }
39 | try {
40 | return invocation.invoke((Object[]) args);
41 | } catch (InvocationTargetException | IllegalAccessException e) {
42 | throw new FormulaException("invoke method failed", e);
43 | }
44 | }
45 |
46 | public static void installPlugin(Class extends Plugin> pluginClazz) {
47 | final Plugin instance;
48 | try {
49 | instance = pluginClazz.newInstance();
50 | } catch (InstantiationException | IllegalAccessException e) {
51 | throw new FormulaException("plugin can not instantiation", e);
52 | }
53 | installPlugin(instance);
54 | }
55 |
56 | public static void installPlugin(Plugin plugin) {
57 | Class extends Plugin> pluginClazz = plugin.getClass();
58 | LOG.info("install formula plugin: {}", pluginClazz.getName());
59 | Arrays.stream(pluginClazz.getMethods())
60 | .filter(method -> method.isAnnotationPresent(FormulaMapping.class))
61 | .forEach(method -> {
62 | FormulaMapping mapping = method.getDeclaredAnnotation(FormulaMapping.class);
63 | String alias = mapping.value();
64 | if (PLUGIN_METHOD_MAPPING.get(alias) != null) {
65 | throw new RegistryFormulaException("formula name [" + alias + "] already exist.");
66 | }
67 | PLUGIN_METHOD_MAPPING.put(alias, new Invocation(method, plugin));
68 | LOG.debug("mapping {} -> {}.{}", alias, pluginClazz.getName(), method.getName());
69 | });
70 | }
71 |
72 | private static void loadSpiMappings() {
73 | LOG.info("loading service provider interface formula.");
74 | ServiceLoader loader = ServiceLoader.load(Plugin.class);
75 | loader.iterator().forEachRemaining(plugin -> installPlugin(plugin.getClass()));
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/tk/fishfish/formula/Formula.java:
--------------------------------------------------------------------------------
1 | package tk.fishfish.formula;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import groovy.lang.Binding;
6 | import groovy.lang.GroovyClassLoader;
7 | import groovy.lang.Script;
8 | import org.codehaus.groovy.control.CompilerConfiguration;
9 | import org.codehaus.groovy.runtime.EncodingGroovyMethods;
10 | import org.codehaus.groovy.runtime.InvokerHelper;
11 | import tk.fishfish.formula.exception.FormulaException;
12 | import tk.fishfish.formula.script.VariableFormulaScript;
13 |
14 | import java.util.concurrent.TimeUnit;
15 |
16 | /**
17 | * 公式
18 | *
19 | * @author 奔波儿灞
20 | * @since 1.0
21 | */
22 | public final class Formula {
23 |
24 | private final CompilerConfiguration cfg;
25 | private final Cache> scriptCache;
26 |
27 | public Formula() {
28 | cfg = new CompilerConfiguration();
29 | cfg.setScriptBaseClass(VariableFormulaScript.class.getName());
30 | scriptCache = CacheBuilder.newBuilder()
31 | .maximumSize(1024)
32 | .expireAfterWrite(5, TimeUnit.MINUTES)
33 | .build();
34 | }
35 |
36 | /**
37 | * 运行脚本
38 | *
39 | * @param scriptText 公式脚本
40 | * @return 结果
41 | */
42 | public Object run(String scriptText) {
43 | return run(scriptText, new Binding());
44 | }
45 |
46 | /**
47 | * 运行脚本
48 | *
49 | * @param scriptText 公式脚本
50 | * @param binding 绑定上下文
51 | * @return 结果
52 | */
53 | @SuppressWarnings("unchecked")
54 | public Object run(String scriptText, Binding binding) {
55 | Class