浏览代码

add ExpressionUtil

Looly 5 年之前
父节点
当前提交
09d00f9fdf
共有 22 个文件被更改,包括 532 次插入3 次删除
  1. 1 0
      CHANGELOG.md
  2. 35 0
      hutool-extra/pom.xml
  3. 20 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionEngine.java
  4. 33 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionException.java
  5. 34 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionUtil.java
  6. 54 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java
  7. 40 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/aviator/AviatorEngine.java
  8. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/aviator/package-info.java
  9. 37 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jexl/JexlEngine.java
  10. 6 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jexl/package-info.java
  11. 27 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jfireel/JfireELEngine.java
  12. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jfireel/package-info.java
  13. 27 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/mvel/MvelEngine.java
  14. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/mvel/package-info.java
  15. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/package-info.java
  16. 35 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/spel/SpELEngine.java
  17. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/engine/spel/package-info.java
  18. 7 0
      hutool-extra/src/main/java/cn/hutool/extra/expression/package-info.java
  19. 4 3
      hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java
  20. 5 0
      hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine
  21. 62 0
      hutool-extra/src/test/java/cn/hutool/extra/expression/AviatorTest.java
  22. 70 0
      hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java

+ 1 - 0
CHANGELOG.md

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

+ 35 - 0
hutool-extra/pom.xml

@@ -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>

+ 20 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionEngine.java

@@ -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);
+}

+ 33 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionException.java

@@ -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);
+	}
+}

+ 34 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/ExpressionUtil.java

@@ -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);
+	}
+}

+ 54 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java

@@ -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 !");
+	}
+}

+ 40 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/aviator/AviatorEngine.java

@@ -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;
+	}
+}

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/aviator/package-info.java

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

+ 37 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jexl/JexlEngine.java

@@ -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;
+	}
+}

+ 6 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jexl/package-info.java

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

+ 27 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jfireel/JfireELEngine.java

@@ -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);
+	}
+}

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/jfireel/package-info.java

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

+ 27 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/mvel/MvelEngine.java

@@ -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);
+	}
+}

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/mvel/package-info.java

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

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/package-info.java

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

+ 35 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/spel/SpELEngine.java

@@ -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);
+	}
+}

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/engine/spel/package-info.java

@@ -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;

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/expression/package-info.java

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

+ 4 - 3
hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java

@@ -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 !");
 	}
 }

+ 5 - 0
hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine

@@ -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

+ 62 - 0
hutool-extra/src/test/java/cn/hutool/extra/expression/AviatorTest.java

@@ -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();
+		}
+	}
+}

+ 70 - 0
hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java

@@ -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);
+	}
+}