first
This commit is contained in:
152
docs/custom-item-en-source.adoc
Normal file
152
docs/custom-item-en-source.adoc
Normal file
@@ -0,0 +1,152 @@
|
||||
:toc:
|
||||
|
||||
= Custom Syntax Elements
|
||||
|
||||
This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements.
|
||||
|
||||
== Overview
|
||||
|
||||
* Custom functions
|
||||
** Implement the CustomFunction interface
|
||||
** Use Java functional interfaces
|
||||
** Register via annotation scanning
|
||||
** Add via QLExpress script
|
||||
** Implement QLFunctionalVarargs
|
||||
* Custom operators
|
||||
** Implement CustomBinaryOperator
|
||||
** Replace built-in operators
|
||||
** Use Java functional interfaces
|
||||
** Add aliases
|
||||
** Implement QLFunctionalVarargs
|
||||
* Extension functions
|
||||
** Extend ExtensionFunction
|
||||
** Implement QLFunctionalVarargs
|
||||
* Aliases for operators and functions
|
||||
|
||||
== Custom Functions
|
||||
|
||||
=== Implement the CustomFunction interface
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction]
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional]
|
||||
----
|
||||
|
||||
=== Register via annotations
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject]
|
||||
----
|
||||
|
||||
=== Add via QLExpress script
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
== Custom Operators
|
||||
|
||||
=== Implement CustomBinaryOperator and set precedence
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence]
|
||||
----
|
||||
|
||||
=== Replace built-in operators
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator]
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs]
|
||||
----
|
||||
|
||||
== Extension Functions
|
||||
|
||||
=== Extend ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction]
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs]
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs: One object defines three kinds of operations
|
||||
|
||||
The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne]
|
||||
----
|
||||
|
||||
=== Interface definition
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[]
|
||||
----
|
||||
|
||||
== Aliases for operators and functions (keyword aliases also supported)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias]
|
||||
----
|
||||
|
||||
== Notes and recommendations
|
||||
|
||||
- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver.
|
||||
- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules.
|
||||
- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists.
|
||||
|
||||
334
docs/custom-item-en.adoc
Normal file
334
docs/custom-item-en.adoc
Normal file
@@ -0,0 +1,334 @@
|
||||
:toc:
|
||||
|
||||
= Custom Syntax Elements
|
||||
|
||||
This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements.
|
||||
|
||||
== Overview
|
||||
|
||||
* Custom functions
|
||||
** Implement the CustomFunction interface
|
||||
** Use Java functional interfaces
|
||||
** Register via annotation scanning
|
||||
** Add via QLExpress script
|
||||
** Implement QLFunctionalVarargs
|
||||
* Custom operators
|
||||
** Implement CustomBinaryOperator
|
||||
** Replace built-in operators
|
||||
** Use Java functional interfaces
|
||||
** Add aliases
|
||||
** Implement QLFunctionalVarargs
|
||||
* Extension functions
|
||||
** Extend ExtensionFunction
|
||||
** Implement QLFunctionalVarargs
|
||||
* Aliases for operators and functions
|
||||
|
||||
== Custom Functions
|
||||
|
||||
=== Implement the CustomFunction interface
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.test.function;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.Parameters;
|
||||
import com.alibaba.qlexpress4.runtime.QContext;
|
||||
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
|
||||
|
||||
public class HelloFunction implements CustomFunction {
|
||||
@Override
|
||||
public Object call(QContext qContext, Parameters parameters)
|
||||
throws Throwable {
|
||||
String tenant = (String)qContext.attachment().get("tenant");
|
||||
return "hello," + tenant;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addFunction("hello", new HelloFunction());
|
||||
String resultJack = (String)express4Runner.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
// Additional information(tenant for example) can be brought into the custom function from outside via attachments
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
|
||||
assertEquals("hello,jack", resultJack);
|
||||
String resultLucy =
|
||||
(String)express4Runner
|
||||
.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
|
||||
.getResult();
|
||||
assertEquals("hello,lucy", resultLucy);
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// Function<T,R>
|
||||
runner.addFunction("inc", (Function<Integer, Integer>)x -> x + 1);
|
||||
// Predicate<T>
|
||||
runner.addFunction("isPos", (Predicate<Integer>)x -> x > 0);
|
||||
// Runnable
|
||||
runner.addFunction("notify", () -> {
|
||||
});
|
||||
// Consumer<T>
|
||||
runner.addFunction("print", (Consumer<Object>)System.out::println);
|
||||
|
||||
Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(2, r1);
|
||||
assertEquals(true, r2);
|
||||
----
|
||||
|
||||
=== Register via annotations
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class MyFunctionUtil {
|
||||
@QLFunction({"myAdd", "iAdd"})
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@QLFunction("arr3")
|
||||
public static int[] array3(int a, int b, int c) {
|
||||
return new int[] {a, b, c};
|
||||
}
|
||||
|
||||
@QLFunction("concat")
|
||||
public String concat(String a, String b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
|
||||
assertEquals(4, addResult.getSucc().size());
|
||||
Object result =
|
||||
express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(14, result);
|
||||
express4Runner.addStaticFunction(MyFunctionUtil.class);
|
||||
Object result1 =
|
||||
express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(10, result1);
|
||||
|
||||
Object result2 =
|
||||
express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("aanull", result2);
|
||||
----
|
||||
|
||||
=== Add via QLExpress script
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class JoinFunction implements QLFunctionalVarargs {
|
||||
@Override
|
||||
public Object call(Object... params) {
|
||||
return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
== Custom Operators
|
||||
|
||||
=== Implement CustomBinaryOperator and set precedence
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD);
|
||||
Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
// precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16"
|
||||
assertEquals("16", r);
|
||||
----
|
||||
|
||||
=== Replace built-in operators
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
boolean ok = runner.replaceDefaultOperator("+",
|
||||
(left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString()));
|
||||
assertTrue(ok);
|
||||
Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(3.5d, r);
|
||||
----
|
||||
|
||||
=== Use Java functional interfaces
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
|
||||
Object resultOperator =
|
||||
express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultOperator);
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("join", params -> params[0] + "," + params[1]);
|
||||
Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2", r);
|
||||
----
|
||||
|
||||
== Extension Functions
|
||||
|
||||
=== Extend ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
ExtensionFunction helloFunction = new ExtensionFunction() {
|
||||
@Override
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object obj, Object[] args) {
|
||||
String originStr = (String)obj;
|
||||
return "Hello," + originStr;
|
||||
}
|
||||
};
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addExtendFunction(helloFunction);
|
||||
Object result =
|
||||
express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("Hello,jack", result);
|
||||
----
|
||||
|
||||
=== Implement QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// simpler way to define extension function
|
||||
express4Runner.addExtendFunction("add",
|
||||
Number.class,
|
||||
params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue());
|
||||
QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
|
||||
assertEquals(3, resultAdd.getResult());
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs: One object defines three kinds of operations
|
||||
|
||||
The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
QLFunctionalVarargs allInOne = params -> {
|
||||
// sum numbers no matter how many args
|
||||
double sum = 0d;
|
||||
for (Object p : params) {
|
||||
if (p instanceof Number) {
|
||||
sum += ((Number)p).doubleValue();
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// as function
|
||||
runner.addVarArgsFunction("sumAll", allInOne);
|
||||
// as operator
|
||||
runner.addOperator("+&", allInOne);
|
||||
// as extension function: first arg is the receiver, followed by call arguments
|
||||
runner.addExtendFunction("plusAll", Number.class, allInOne);
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(6d, rf);
|
||||
assertEquals(5d, ro);
|
||||
assertEquals(6d, re);
|
||||
----
|
||||
|
||||
=== Interface definition
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
----
|
||||
|
||||
== Aliases for operators and functions (keyword aliases also supported)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// add custom function zero
|
||||
express4Runner.addFunction("zero", (String ignore) -> 0);
|
||||
|
||||
// keyword alias
|
||||
assertTrue(express4Runner.addAlias("如果", "if"));
|
||||
assertTrue(express4Runner.addAlias("则", "then"));
|
||||
assertTrue(express4Runner.addAlias("否则", "else"));
|
||||
assertTrue(express4Runner.addAlias("返回", "return"));
|
||||
// operator alias
|
||||
assertTrue(express4Runner.addAlias("大于", ">"));
|
||||
// function alias
|
||||
assertTrue(express4Runner.addAlias("零", "zero"));
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("语文", 90);
|
||||
context.put("数学", 90);
|
||||
context.put("英语", 90);
|
||||
|
||||
Object result = express4Runner
|
||||
.execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS)
|
||||
.getResult();
|
||||
assertEquals(0, result);
|
||||
----
|
||||
|
||||
== Notes and recommendations
|
||||
|
||||
- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver.
|
||||
- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules.
|
||||
- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists.
|
||||
152
docs/custom-item-source.adoc
Normal file
152
docs/custom-item-source.adoc
Normal file
@@ -0,0 +1,152 @@
|
||||
:toc:
|
||||
|
||||
= 自定义语法元素
|
||||
|
||||
本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。
|
||||
|
||||
== 总览
|
||||
|
||||
* 自定义函数
|
||||
** 实现CustomFunction接口
|
||||
** 使用 Java 函数式接口
|
||||
** 通过注解扫描注册
|
||||
** 通过QLExpress脚本添加
|
||||
** 实现QLFunctionalVarargs
|
||||
* 自定义操作符
|
||||
** 实现CustomBinaryOperator
|
||||
** 替换内置操作符
|
||||
** 使用 Java 函数式接口
|
||||
** 添加别名
|
||||
** 实现QLFunctionalVarargs
|
||||
* 扩展函数
|
||||
** 继承ExtensionFunction
|
||||
** 实现QLFunctionalVarargs
|
||||
* 操作符与函数别名
|
||||
|
||||
== 自定义函数
|
||||
|
||||
=== 实现CustomFunction接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction]
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional]
|
||||
----
|
||||
|
||||
=== 注解方式注册
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject]
|
||||
----
|
||||
|
||||
=== 通过QLExpress脚本添加
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs]
|
||||
----
|
||||
|
||||
== 自定义操作符
|
||||
|
||||
=== 实现CustomBinaryOperator并设置优先级
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence]
|
||||
----
|
||||
|
||||
=== 替换内置操作符
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator]
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs]
|
||||
----
|
||||
|
||||
== 扩展函数
|
||||
|
||||
=== 继承ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction]
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs]
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs:一个对象同时定义三类操作
|
||||
|
||||
同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne]
|
||||
----
|
||||
|
||||
=== 接口定义
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[]
|
||||
----
|
||||
|
||||
== 操作符与函数别名(亦支持关键字别名)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias]
|
||||
----
|
||||
|
||||
== 说明与建议
|
||||
|
||||
- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。
|
||||
- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。
|
||||
- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。
|
||||
|
||||
334
docs/custom-item.adoc
Normal file
334
docs/custom-item.adoc
Normal file
@@ -0,0 +1,334 @@
|
||||
:toc:
|
||||
|
||||
= 自定义语法元素
|
||||
|
||||
本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。
|
||||
|
||||
== 总览
|
||||
|
||||
* 自定义函数
|
||||
** 实现CustomFunction接口
|
||||
** 使用 Java 函数式接口
|
||||
** 通过注解扫描注册
|
||||
** 通过QLExpress脚本添加
|
||||
** 实现QLFunctionalVarargs
|
||||
* 自定义操作符
|
||||
** 实现CustomBinaryOperator
|
||||
** 替换内置操作符
|
||||
** 使用 Java 函数式接口
|
||||
** 添加别名
|
||||
** 实现QLFunctionalVarargs
|
||||
* 扩展函数
|
||||
** 继承ExtensionFunction
|
||||
** 实现QLFunctionalVarargs
|
||||
* 操作符与函数别名
|
||||
|
||||
== 自定义函数
|
||||
|
||||
=== 实现CustomFunction接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.test.function;
|
||||
|
||||
import com.alibaba.qlexpress4.runtime.Parameters;
|
||||
import com.alibaba.qlexpress4.runtime.QContext;
|
||||
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
|
||||
|
||||
public class HelloFunction implements CustomFunction {
|
||||
@Override
|
||||
public Object call(QContext qContext, Parameters parameters)
|
||||
throws Throwable {
|
||||
String tenant = (String)qContext.attachment().get("tenant");
|
||||
return "hello," + tenant;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addFunction("hello", new HelloFunction());
|
||||
String resultJack = (String)express4Runner.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
// Additional information(tenant for example) can be brought into the custom function from outside via attachments
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
|
||||
assertEquals("hello,jack", resultJack);
|
||||
String resultLucy =
|
||||
(String)express4Runner
|
||||
.execute("hello()",
|
||||
Collections.emptyMap(),
|
||||
QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
|
||||
.getResult();
|
||||
assertEquals("hello,lucy", resultLucy);
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// Function<T,R>
|
||||
runner.addFunction("inc", (Function<Integer, Integer>)x -> x + 1);
|
||||
// Predicate<T>
|
||||
runner.addFunction("isPos", (Predicate<Integer>)x -> x > 0);
|
||||
// Runnable
|
||||
runner.addFunction("notify", () -> {
|
||||
});
|
||||
// Consumer<T>
|
||||
runner.addFunction("print", (Consumer<Object>)System.out::println);
|
||||
|
||||
Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(2, r1);
|
||||
assertEquals(true, r2);
|
||||
----
|
||||
|
||||
=== 注解方式注册
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class MyFunctionUtil {
|
||||
@QLFunction({"myAdd", "iAdd"})
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@QLFunction("arr3")
|
||||
public static int[] array3(int a, int b, int c) {
|
||||
return new int[] {a, b, c};
|
||||
}
|
||||
|
||||
@QLFunction("concat")
|
||||
public String concat(String a, String b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
|
||||
assertEquals(4, addResult.getSucc().size());
|
||||
Object result =
|
||||
express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(14, result);
|
||||
express4Runner.addStaticFunction(MyFunctionUtil.class);
|
||||
Object result1 =
|
||||
express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(10, result1);
|
||||
|
||||
Object result2 =
|
||||
express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("aanull", result2);
|
||||
----
|
||||
|
||||
=== 通过QLExpress脚本添加
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public static class JoinFunction implements QLFunctionalVarargs {
|
||||
@Override
|
||||
public Object call(Object... params) {
|
||||
return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addVarArgsFunction("join", new JoinFunction());
|
||||
Object resultFunction =
|
||||
express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultFunction);
|
||||
----
|
||||
|
||||
== 自定义操作符
|
||||
|
||||
=== 实现CustomBinaryOperator并设置优先级
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD);
|
||||
Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
// precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16"
|
||||
assertEquals("16", r);
|
||||
----
|
||||
|
||||
=== 替换内置操作符
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
boolean ok = runner.replaceDefaultOperator("+",
|
||||
(left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString()));
|
||||
assertTrue(ok);
|
||||
Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(3.5d, r);
|
||||
----
|
||||
|
||||
=== 使用 Java 函数式接口
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
|
||||
Object resultOperator =
|
||||
express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2,3", resultOperator);
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
runner.addOperator("join", params -> params[0] + "," + params[1]);
|
||||
Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1,2", r);
|
||||
----
|
||||
|
||||
== 扩展函数
|
||||
|
||||
=== 继承ExtensionFunction
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
ExtensionFunction helloFunction = new ExtensionFunction() {
|
||||
@Override
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object obj, Object[] args) {
|
||||
String originStr = (String)obj;
|
||||
return "Hello," + originStr;
|
||||
}
|
||||
};
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
express4Runner.addExtendFunction(helloFunction);
|
||||
Object result =
|
||||
express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("Hello,jack", result);
|
||||
----
|
||||
|
||||
=== 实现QLFunctionalVarargs
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// simpler way to define extension function
|
||||
express4Runner.addExtendFunction("add",
|
||||
Number.class,
|
||||
params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue());
|
||||
QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
|
||||
assertEquals(3, resultAdd.getResult());
|
||||
----
|
||||
|
||||
== QLFunctionalVarargs:一个对象同时定义三类操作
|
||||
|
||||
同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
QLFunctionalVarargs allInOne = params -> {
|
||||
// sum numbers no matter how many args
|
||||
double sum = 0d;
|
||||
for (Object p : params) {
|
||||
if (p instanceof Number) {
|
||||
sum += ((Number)p).doubleValue();
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// as function
|
||||
runner.addVarArgsFunction("sumAll", allInOne);
|
||||
// as operator
|
||||
runner.addOperator("+&", allInOne);
|
||||
// as extension function: first arg is the receiver, followed by call arguments
|
||||
runner.addExtendFunction("plusAll", Number.class, allInOne);
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(6d, rf);
|
||||
assertEquals(5d, ro);
|
||||
assertEquals(6d, re);
|
||||
----
|
||||
|
||||
=== 接口定义
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
----
|
||||
|
||||
== 操作符与函数别名(亦支持关键字别名)
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
// add custom function zero
|
||||
express4Runner.addFunction("zero", (String ignore) -> 0);
|
||||
|
||||
// keyword alias
|
||||
assertTrue(express4Runner.addAlias("如果", "if"));
|
||||
assertTrue(express4Runner.addAlias("则", "then"));
|
||||
assertTrue(express4Runner.addAlias("否则", "else"));
|
||||
assertTrue(express4Runner.addAlias("返回", "return"));
|
||||
// operator alias
|
||||
assertTrue(express4Runner.addAlias("大于", ">"));
|
||||
// function alias
|
||||
assertTrue(express4Runner.addAlias("零", "zero"));
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("语文", 90);
|
||||
context.put("数学", 90);
|
||||
context.put("英语", 90);
|
||||
|
||||
Object result = express4Runner
|
||||
.execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS)
|
||||
.getResult();
|
||||
assertEquals(0, result);
|
||||
----
|
||||
|
||||
== 说明与建议
|
||||
|
||||
- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。
|
||||
- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。
|
||||
- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。
|
||||
50
docs/execute-en-source.adoc
Normal file
50
docs/execute-en-source.adoc
Normal file
@@ -0,0 +1,50 @@
|
||||
:toc:
|
||||
|
||||
= Expression Execution
|
||||
|
||||
This section describes the different ways to execute expressions in QLExpress4.
|
||||
|
||||
== Use Map as the context
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values.
|
||||
- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl]
|
||||
----
|
||||
|
||||
== Use object fields as the context
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters).
|
||||
- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields.
|
||||
|
||||
// Note: This references the test code for execution with an object context.
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject]
|
||||
----
|
||||
|
||||
== Use a set of aliased objects
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored.
|
||||
- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias]
|
||||
----
|
||||
|
||||
== Use a custom `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method.
|
||||
- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar]
|
||||
----
|
||||
95
docs/execute-en.adoc
Normal file
95
docs/execute-en.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
:toc:
|
||||
|
||||
= Expression Execution
|
||||
|
||||
This section describes the different ways to execute expressions in QLExpress4.
|
||||
|
||||
== Use Map as the context
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values.
|
||||
- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("a", 1);
|
||||
context.put("b", 2);
|
||||
context.put("c", 3);
|
||||
Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(7, result);
|
||||
----
|
||||
|
||||
== Use object fields as the context
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters).
|
||||
- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields.
|
||||
|
||||
// Note: This references the test code for execution with an object context.
|
||||
[source,java,indent=0]
|
||||
----
|
||||
MyObj myObj = new MyObj();
|
||||
myObj.a = 1;
|
||||
myObj.b = "test";
|
||||
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1test", result);
|
||||
----
|
||||
|
||||
== Use a set of aliased objects
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored.
|
||||
- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Order order = new Order();
|
||||
order.setOrderNum("OR123455");
|
||||
order.setAmount(100);
|
||||
|
||||
User user = new User();
|
||||
user.setName("jack");
|
||||
user.setVip(true);
|
||||
|
||||
// Calculate the Final Order Amount
|
||||
Express4Runner express4Runner =
|
||||
new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build());
|
||||
Number result = (Number)express4Runner
|
||||
.executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user)
|
||||
.getResult();
|
||||
assertEquals(80, result.intValue());
|
||||
----
|
||||
|
||||
== Use a custom `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method.
|
||||
- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
|
||||
Map<String, Object> staticContext = new HashMap<>();
|
||||
staticContext.put("语文", 88);
|
||||
staticContext.put("数学", 99);
|
||||
staticContext.put("英语", 95);
|
||||
|
||||
QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS;
|
||||
DynamicVariableContext dynamicContext =
|
||||
new DynamicVariableContext(express4Runner, staticContext, defaultOptions);
|
||||
dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0");
|
||||
dynamicContext.put("是否优秀", "平均成绩>90");
|
||||
|
||||
// dynamic var
|
||||
assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult());
|
||||
assertEquals(94,
|
||||
((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
// static var
|
||||
assertEquals(187,
|
||||
((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
----
|
||||
50
docs/execute-source.adoc
Normal file
50
docs/execute-source.adoc
Normal file
@@ -0,0 +1,50 @@
|
||||
:toc:
|
||||
|
||||
= 表达式执行
|
||||
|
||||
本节说明在 QLExpress4 中执行表达式的多种方式。
|
||||
|
||||
== 使用 Map 作为上下文
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。
|
||||
- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl]
|
||||
----
|
||||
|
||||
== 使用对象字段作为上下文
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。
|
||||
- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。
|
||||
|
||||
// 说明:此处引用对象上下文执行的测试代码。
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject]
|
||||
----
|
||||
|
||||
== 使用带别名的对象集合
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。
|
||||
- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias]
|
||||
----
|
||||
|
||||
== 使用自定义 `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。
|
||||
- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar]
|
||||
----
|
||||
95
docs/execute.adoc
Normal file
95
docs/execute.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
:toc:
|
||||
|
||||
= 表达式执行
|
||||
|
||||
本节说明在 QLExpress4 中执行表达式的多种方式。
|
||||
|
||||
== 使用 Map 作为上下文
|
||||
|
||||
- `execute(String, Map<String,Object>, QLOptions)`
|
||||
- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。
|
||||
- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("a", 1);
|
||||
context.put("b", 2);
|
||||
context.put("c", 3);
|
||||
Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals(7, result);
|
||||
----
|
||||
|
||||
== 使用对象字段作为上下文
|
||||
|
||||
- `execute(String, Object, QLOptions)`
|
||||
- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。
|
||||
- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。
|
||||
|
||||
// 说明:此处引用对象上下文执行的测试代码。
|
||||
[source,java,indent=0]
|
||||
----
|
||||
MyObj myObj = new MyObj();
|
||||
myObj.a = 1;
|
||||
myObj.b = "test";
|
||||
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult();
|
||||
assertEquals("1test", result);
|
||||
----
|
||||
|
||||
== 使用带别名的对象集合
|
||||
|
||||
- `executeWithAliasObjects(String, QLOptions, Object...)`
|
||||
- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。
|
||||
- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Order order = new Order();
|
||||
order.setOrderNum("OR123455");
|
||||
order.setAmount(100);
|
||||
|
||||
User user = new User();
|
||||
user.setName("jack");
|
||||
user.setVip(true);
|
||||
|
||||
// Calculate the Final Order Amount
|
||||
Express4Runner express4Runner =
|
||||
new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build());
|
||||
Number result = (Number)express4Runner
|
||||
.executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user)
|
||||
.getResult();
|
||||
assertEquals(80, result.intValue());
|
||||
----
|
||||
|
||||
== 使用自定义 `ExpressContext`
|
||||
|
||||
- `execute(String, ExpressContext, QLOptions)`
|
||||
- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。
|
||||
- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
|
||||
|
||||
Map<String, Object> staticContext = new HashMap<>();
|
||||
staticContext.put("语文", 88);
|
||||
staticContext.put("数学", 99);
|
||||
staticContext.put("英语", 95);
|
||||
|
||||
QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS;
|
||||
DynamicVariableContext dynamicContext =
|
||||
new DynamicVariableContext(express4Runner, staticContext, defaultOptions);
|
||||
dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0");
|
||||
dynamicContext.put("是否优秀", "平均成绩>90");
|
||||
|
||||
// dynamic var
|
||||
assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult());
|
||||
assertEquals(94,
|
||||
((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
// static var
|
||||
assertEquals(187,
|
||||
((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue());
|
||||
----
|
||||
Reference in New Issue
Block a user