├── .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 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 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