add ExpressionUtil

This commit is contained in:
Looly 2020-11-10 14:36:45 +08:00
parent 7817599cb6
commit 09d00f9fdf
22 changed files with 532 additions and 3 deletions

View File

@ -13,6 +13,7 @@
* 【core 】 HexUtil增加format方法issue#I245NF@Gitee
* 【poi 】 ExcelWriter增加setCurrentRowToEnd方法issue#I24A2R@Gitee
* 【core 】 ExcelWriter增加setCurrentRowToEnd方法issue#I24A2R@Gitee
* 【extra 】 增加表达式引擎封装ExpressionUtilpr#1203@Github
### Bug修复
* 【core 】 修复DateUtil.current使用System.nanoTime的问题issue#1198@Github

View File

@ -277,5 +277,40 @@
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.1.4</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.4.10.Final</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.jfirer</groupId>
<artifactId>jfireEl</artifactId>
<version>1.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,20 @@
package cn.hutool.extra.expression;
import java.util.Map;
/**
* 表达式引擎API接口通过实现此接口完成表达式的解析和执行
*
* @author looll,independenter
* @since 5.5.0
*/
public interface ExpressionEngine {
/**
* 执行表达式
* @param expression 表达式
* @param context 表达式上下文用于存储表达式中所需的变量值等
* @return 执行结果
*/
Object eval(String expression, Map<String, Object> context);
}

View File

@ -0,0 +1,33 @@
package cn.hutool.extra.expression;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
/**
* 表达式语言异常
*
* @author Looly
*/
public class ExpressionException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ExpressionException(Throwable e) {
super(ExceptionUtil.getMessage(e), e);
}
public ExpressionException(String message) {
super(message);
}
public ExpressionException(String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public ExpressionException(String message, Throwable throwable) {
super(message, throwable);
}
public ExpressionException(Throwable throwable, String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}

View File

@ -0,0 +1,34 @@
package cn.hutool.extra.expression;
import cn.hutool.extra.expression.engine.ExpressionFactory;
import java.util.Map;
/**
* 表达式引擎工具类
*
* @author looly
* @since 5.5.0
*/
public class ExpressionUtil {
/**
* 获得全局单例的表达式引擎
*
* @return 全局单例的表达式引擎
*/
public static ExpressionEngine getEngine() {
return ExpressionFactory.get();
}
/**
* 执行表达式
*
* @param expression 表达式
* @param context 表达式上下文用于存储表达式中所需的变量值等
* @return 执行结果
*/
public static Object eval(String expression, Map<String, Object> context) {
return getEngine().eval(expression, context);
}
}

View File

@ -0,0 +1,54 @@
package cn.hutool.extra.expression.engine;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.ServiceLoaderUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import cn.hutool.extra.expression.ExpressionEngine;
import cn.hutool.extra.expression.ExpressionException;
/**
* 表达式语言引擎工厂类用于根据用户引入的表达式jar自动创建对应的引擎对象
*
* @since 5.5.0
* @author looly
*/
public class ExpressionFactory {
/**
* 获得单例的{@link ExpressionEngine}
*
* @return 单例的{@link ExpressionEngine}
*/
public static ExpressionEngine get(){
return Singleton.get(ExpressionEngine.class.getName(), ExpressionFactory::create);
}
/**
* 根据用户引入的表达式引擎jar自动创建对应的拼音引擎对象<br>
* 推荐创建的引擎单例使用此方法每次调用会返回新的引擎
*
* @return {@link ExpressionEngine}
*/
public static ExpressionEngine create() {
final ExpressionEngine engine = doCreate();
StaticLog.debug("Use [{}] Engine As Default.", StrUtil.removeSuffix(engine.getClass().getSimpleName(), "Engine"));
return engine;
}
/**
* 根据用户引入的拼音引擎jar自动创建对应的拼音引擎对象<br>
* 推荐创建的引擎单例使用此方法每次调用会返回新的引擎
*
* @return {@link ExpressionEngine}
*/
private static ExpressionEngine doCreate() {
final ExpressionEngine engine = ServiceLoaderUtil.loadFirstAvailable(ExpressionEngine.class);
if(null != engine){
return engine;
}
throw new ExpressionException("No expression jar found ! Please add one of it to your project !");
}
}

View File

@ -0,0 +1,40 @@
package cn.hutool.extra.expression.engine.aviator;
import cn.hutool.extra.expression.ExpressionEngine;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import java.util.Map;
/**
* Aviator引擎封装<br>
* https://github.com/killme2008/aviatorscript
*
* @author looly
* @since 5.5.0
*/
public class AviatorEngine implements ExpressionEngine {
private final AviatorEvaluatorInstance engine;
/**
* 构造
*/
public AviatorEngine() {
engine = AviatorEvaluator.getInstance();
}
@Override
public Object eval(String expression, Map<String, Object> context) {
return engine.execute(expression, context);
}
/**
* 获取{@link AviatorEvaluatorInstance}
*
* @return {@link AviatorEvaluatorInstance}
*/
public AviatorEvaluatorInstance getEngine() {
return this.engine;
}
}

View File

@ -0,0 +1,7 @@
/**
* Aviator引擎封装https://github.com/killme2008/aviatorscript
*
* @author looly
*
*/
package cn.hutool.extra.expression.engine.aviator;

View File

@ -0,0 +1,37 @@
package cn.hutool.extra.expression.engine.jexl;
import cn.hutool.extra.expression.ExpressionEngine;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.MapContext;
import java.util.Map;
/**
* Jexl3引擎封装<br>
* https://github.com/apache/commons-jexl
*
* @since 5.5.0
* @author looly
*/
public class JexlEngine implements ExpressionEngine {
private final org.apache.commons.jexl3.JexlEngine engine;
public JexlEngine(){
engine = (new JexlBuilder()).cache(512).strict(true).silent(false).create();
}
@Override
public Object eval(String expression, Map<String, Object> context) {
return engine.createExpression(expression).evaluate(new MapContext(context));
}
/**
* 获取{@link org.apache.commons.jexl3.JexlEngine}
*
* @return {@link org.apache.commons.jexl3.JexlEngine}
*/
public org.apache.commons.jexl3.JexlEngine getEngine() {
return this.engine;
}
}

View File

@ -0,0 +1,6 @@
/**
* Jexl3引擎封装,https://github.com/apache/commons-jexl
*
* @author looly
*/
package cn.hutool.extra.expression.engine.jexl;

View File

@ -0,0 +1,27 @@
package cn.hutool.extra.expression.engine.jfireel;
import cn.hutool.extra.expression.ExpressionEngine;
import com.jfirer.jfireel.expression.Expression;
import java.util.Map;
/**
* JfireEL引擎封装<br>
* https://gitee.com/eric_ds/jfireEL
*
* @since 5.5.0
* @author looly
*/
public class JfireELEngine implements ExpressionEngine {
/**
* 构造
*/
public JfireELEngine(){
}
@Override
public Object eval(String expression, Map<String, Object> context) {
return Expression.parse(expression).calculate(context);
}
}

View File

@ -0,0 +1,7 @@
/**
* JfireEL引擎封装<br>
* https://gitee.com/eric_ds/jfireEL
*
* @author looly
*/
package cn.hutool.extra.expression.engine.jfireel;

View File

@ -0,0 +1,27 @@
package cn.hutool.extra.expression.engine.mvel;
import cn.hutool.extra.expression.ExpressionEngine;
import org.mvel2.MVEL;
import java.util.Map;
/**
* MVEL (MVFLEX Expression Language)引擎封装<br>
* https://github.com/mvel/mvel
*
* @since 5.5.0
* @author looly
*/
public class MvelEngine implements ExpressionEngine {
/**
* 构造
*/
public MvelEngine(){
}
@Override
public Object eval(String expression, Map<String, Object> context) {
return MVEL.eval(expression, context);
}
}

View File

@ -0,0 +1,7 @@
/**
* MVEL (MVFLEX Expression Language)引擎封装<br>
* https://github.com/mvel/mvel
*
* @author looly
*/
package cn.hutool.extra.expression.engine.mvel;

View File

@ -0,0 +1,7 @@
/**
* 表达式语言引擎封装
*
* @author looly
*
*/
package cn.hutool.extra.expression.engine;

View File

@ -0,0 +1,35 @@
package cn.hutool.extra.expression.engine.spel;
import cn.hutool.extra.expression.ExpressionEngine;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Map;
/**
* Spring-Expression引擎封装<br>
* https://github.com/spring-projects/spring-framework/tree/master/spring-expression
*
* @since 5.5.0
* @author looly
*/
public class SpELEngine implements ExpressionEngine {
private final ExpressionParser parser;
/**
* 构造
*/
public SpELEngine(){
parser = new SpelExpressionParser();
}
@Override
public Object eval(String expression, Map<String, Object> context) {
final EvaluationContext evaluationContext = new StandardEvaluationContext();
context.forEach(evaluationContext::setVariable);
return parser.parseExpression(expression).getValue(evaluationContext);
}
}

View File

@ -0,0 +1,7 @@
/**
* Spring-Expression引擎封装<br>
* https://github.com/spring-projects/spring-framework/tree/master/spring-expression
*
* @author looly
*/
package cn.hutool.extra.expression.engine.spel;

View File

@ -0,0 +1,7 @@
/**
* 表达式语言引擎封装
*
* @author looly
*
*/
package cn.hutool.extra.expression;

View File

@ -3,10 +3,11 @@ package cn.hutool.extra.pinyin.engine;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.ServiceLoaderUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.pinyin.PinyinEngine;
import cn.hutool.extra.template.TemplateException;
import cn.hutool.log.StaticLog;
import cn.hutool.extra.pinyin.PinyinEngine;
import cn.hutool.extra.pinyin.PinyinException;
/**
* 简单拼音引擎工厂用于根据用户引入的拼音库jar自动创建对应的拼音引擎对象
*
@ -47,6 +48,6 @@ public class PinyinFactory {
return engine;
}
throw new TemplateException("No pinyin jar found ! Please add one of it to your project !");
throw new PinyinException("No pinyin jar found ! Please add one of it to your project !");
}
}

View File

@ -0,0 +1,5 @@
cn.hutool.extra.expression.engine.aviator.AviatorEngine
cn.hutool.extra.expression.engine.jexl.JexlEngine
cn.hutool.extra.expression.engine.mvel.MvelEngine
cn.hutool.extra.expression.engine.jfireel.JfireELEngine
cn.hutool.extra.expression.engine.spel.SpELEngine

View File

@ -0,0 +1,62 @@
package cn.hutool.extra.expression;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Dict;
import cn.hutool.extra.expression.engine.aviator.AviatorEngine;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.util.Date;
/**
* Aviator引擎单元测试来自https://github.com/looly/hutool/pull/1203
*/
public class AviatorTest {
@Test
public void simpleTest(){
Foo foo = new Foo(100, 3.14f, new Date());
ExpressionEngine engine = new AviatorEngine();
String exp =
"\"[foo i=\"+ foo.i + \", f=\" + foo.f + \", date.year=\" + (foo.date.year+1900) + \", date.month=\" + foo.date.month + \", bars[0].name=\" + #foo.bars[0].name + \"]\"";
String result = (String) engine.eval(exp, Dict.create().set("foo", foo));
Assert.assertEquals("[foo i=100, f=3.14, date.year=2020, date.month=10, bars[0].name=bar]", result);
// Assignment.
exp = "#foo.bars[0].name='hello aviator' ; #foo.bars[0].name";
result = (String) engine.eval(exp, Dict.create().set("foo", foo));
Assert.assertEquals("hello aviator", result);
Assert.assertEquals("hello aviator", foo.bars[0].getName());
exp = "foo.bars[0] = nil ; foo.bars[0]";
result = (String) engine.eval(exp, Dict.create().set("foo", foo));
Console.log("Execute expression: " + exp);
Assert.assertNull(result);
Assert.assertNull(foo.bars[0]);
}
@Data
public static class Bar {
public Bar() {
this.name = "bar";
}
private String name;
}
@Data
public static class Foo {
int i;
float f;
Date date;
Bar[] bars = new Bar[1];
public Foo(final int i, final float f, final Date date) {
super();
this.i = i;
this.f = f;
this.date = date;
this.bars[0] = new Bar();
}
}
}

View File

@ -0,0 +1,70 @@
package cn.hutool.extra.expression;
import cn.hutool.core.lang.Dict;
import cn.hutool.extra.expression.engine.jexl.JexlEngine;
import cn.hutool.extra.expression.engine.jfireel.JfireELEngine;
import cn.hutool.extra.expression.engine.mvel.MvelEngine;
import cn.hutool.extra.expression.engine.spel.SpELEngine;
import org.junit.Assert;
import org.junit.Test;
public class ExpressionUtilTest {
@Test
public void evalTest(){
final Dict dict = Dict.create()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = ExpressionUtil.eval("a-(b-c)", dict);
Assert.assertEquals(-143.8, (double)eval, 2);
}
@Test
public void jexlTest(){
ExpressionEngine engine = new JexlEngine();
final Dict dict = Dict.create()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = engine.eval("a-(b-c)", dict);
Assert.assertEquals(-143.8, (double)eval, 2);
}
@Test
public void mvelTest(){
ExpressionEngine engine = new MvelEngine();
final Dict dict = Dict.create()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = engine.eval("a-(b-c)", dict);
Assert.assertEquals(-143.8, (double)eval, 2);
}
@Test
public void jfireELTest(){
ExpressionEngine engine = new JfireELEngine();
final Dict dict = Dict.create()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = engine.eval("a-(b-c)", dict);
Assert.assertEquals(-143.8, (double)eval, 2);
}
@Test
public void spELTest(){
ExpressionEngine engine = new SpELEngine();
final Dict dict = Dict.create()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = engine.eval("#a-(#b-#c)", dict);
Assert.assertEquals(-143.8, (double)eval, 2);
}
}