diff --git a/.gitignore b/.gitignore index 0e2b059..f8bca96 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ a_little_config_pro.txt dev_plan.txt + diff --git a/LICENSE b/LICENSE index ff3e6fc..37fb606 100644 --- a/LICENSE +++ b/LICENSE @@ -192,3 +192,5 @@ third-party archives. + + diff --git a/README.md b/README.md index 7ac8883..e89b072 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ ### Enjoy -Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、set、define、include、render 六个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。 +Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、switch、set、define、include、render 七个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。 #### Enjoy 主要特点 -- 消灭传统模板引擎中大量繁杂概念,仅六个核心指令,学习成本极低 -- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用javacc、antlr +- 消灭传统模板引擎中大量繁杂概念,仅七个核心指令,学习成本极低 +- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr - 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能 - 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎 - 与 java 打通式设计,在模板中与 java 交互极为方便 diff --git a/pom.xml b/pom.xml index f46d4c3..3bc8925 100644 --- a/pom.xml +++ b/pom.xml @@ -73,8 +73,8 @@ maven-compiler-plugin 3.6.1 - 1.7 - 1.7 + 1.8 + 1.8 @@ -121,7 +121,7 @@ - true + false diff --git a/src/main/java/com/jfinal/kit/Kv.java b/src/main/java/com/jfinal/kit/Kv.java index b00a896..a0d3edb 100644 --- a/src/main/java/com/jfinal/kit/Kv.java +++ b/src/main/java/com/jfinal/kit/Kv.java @@ -113,6 +113,20 @@ public class Kv extends HashMap { return this; } + public Kv setIfNotBlank(Object key, String value) { + if (StrKit.notBlank(value)) { + set(key, value); + } + return this; + } + + public Kv setIfNotNull(Object key, Object value) { + if (value != null) { + set(key, value); + } + return this; + } + public Kv set(Map map) { super.putAll(map); return this; diff --git a/src/main/java/com/jfinal/kit/ReflectKit.java b/src/main/java/com/jfinal/kit/ReflectKit.java index 0f7347e..3d1fd2c 100644 --- a/src/main/java/com/jfinal/kit/ReflectKit.java +++ b/src/main/java/com/jfinal/kit/ReflectKit.java @@ -24,7 +24,7 @@ public class ReflectKit { public static Object newInstance(Class clazz) { try { return clazz.newInstance(); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/com/jfinal/template/Engine.java b/src/main/java/com/jfinal/template/Engine.java index d908b35..44cfb49 100644 --- a/src/main/java/com/jfinal/template/Engine.java +++ b/src/main/java/com/jfinal/template/Engine.java @@ -520,8 +520,8 @@ public class Engine { * Engine.addFieldGetter(1, new IsMethodFieldGetter()); * * 注:IsMethodFieldGetter 系统已经提供,只是默认没有启用。该实现类通过调用 - * target.isXxx() 方法获取 target.xxx 表达式的值,其中 xxx 字段必须是 - * Boolean/boolean 类型 + * target.isXxx() 方法获取 target.xxx 表达式的值,其中 isXxx() 返回值 + * 必须是 Boolean/boolean 类型才会被调用 */ public static void addFieldGetter(int index, FieldGetter fieldGetter) { FieldKit.addFieldGetter(index, fieldGetter); diff --git a/src/main/java/com/jfinal/template/EngineConfig.java b/src/main/java/com/jfinal/template/EngineConfig.java index 33fafe8..f5e096a 100644 --- a/src/main/java/com/jfinal/template/EngineConfig.java +++ b/src/main/java/com/jfinal/template/EngineConfig.java @@ -73,9 +73,9 @@ public class EngineConfig { addDirective("string", StringDirective.class); addDirective("random", RandomDirective.class); addDirective("number", NumberDirective.class); + addDirective("call", CallDirective.class); // Add official shared method of Template Engine - // addSharedMethod(new Json()); addSharedMethod(new SharedMethodLib()); } @@ -83,6 +83,7 @@ public class EngineConfig { * Add shared function with file */ public void addSharedFunction(String fileName) { + fileName = fileName.replace("\\", "/"); // FileSource fileSource = new FileSource(baseTemplatePath, fileName, encoding); ISource source = sourceFactory.getSource(baseTemplatePath, fileName, encoding); doAddSharedFunction(source, fileName); @@ -261,8 +262,9 @@ public class EngineConfig { throw new IllegalArgumentException("baseTemplatePath can not be blank"); } baseTemplatePath = baseTemplatePath.trim(); + baseTemplatePath = baseTemplatePath.replace("\\", "/"); if (baseTemplatePath.length() > 1) { - if (baseTemplatePath.endsWith("/") || baseTemplatePath.endsWith("\\")) { + if (baseTemplatePath.endsWith("/")) { baseTemplatePath = baseTemplatePath.substring(0, baseTemplatePath.length() - 1); } } diff --git a/src/main/java/com/jfinal/template/expr/ExprParser.java b/src/main/java/com/jfinal/template/expr/ExprParser.java index f724a49..626107e 100644 --- a/src/main/java/com/jfinal/template/expr/ExprParser.java +++ b/src/main/java/com/jfinal/template/expr/ExprParser.java @@ -21,28 +21,7 @@ import java.util.LinkedHashMap; import java.util.List; import com.jfinal.template.EngineConfig; import com.jfinal.template.expr.Sym; -import com.jfinal.template.expr.ast.Arith; -import com.jfinal.template.expr.ast.Array; -import com.jfinal.template.expr.ast.Assign; -import com.jfinal.template.expr.ast.Compare; -import com.jfinal.template.expr.ast.Const; -import com.jfinal.template.expr.ast.Expr; -import com.jfinal.template.expr.ast.ExprList; -import com.jfinal.template.expr.ast.Field; -import com.jfinal.template.expr.ast.ForCtrl; -import com.jfinal.template.expr.ast.Id; -import com.jfinal.template.expr.ast.IncDec; -import com.jfinal.template.expr.ast.Index; -import com.jfinal.template.expr.ast.Logic; -import com.jfinal.template.expr.ast.Map; -import com.jfinal.template.expr.ast.Method; -import com.jfinal.template.expr.ast.NullSafe; -import com.jfinal.template.expr.ast.RangeArray; -import com.jfinal.template.expr.ast.SharedMethod; -import com.jfinal.template.expr.ast.StaticField; -import com.jfinal.template.expr.ast.StaticMethod; -import com.jfinal.template.expr.ast.Ternary; -import com.jfinal.template.expr.ast.Unary; +import com.jfinal.template.expr.ast.*; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParaToken; import com.jfinal.template.stat.ParseException; diff --git a/src/main/java/com/jfinal/template/expr/ast/Field.java b/src/main/java/com/jfinal/template/expr/ast/Field.java index 929863b..6da9705 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Field.java +++ b/src/main/java/com/jfinal/template/expr/ast/Field.java @@ -73,9 +73,7 @@ public class Field extends Expr { if (fieldGetter.notNull()) { return fieldGetter.get(target, fieldName); } - } catch (TemplateException e) { - throw e; - } catch (ParseException e) { + } catch (TemplateException | ParseException e) { throw e; } catch (Exception e) { throw new TemplateException(e.getMessage(), location, e); diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java b/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java index 3114e6d..dc3d806 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java @@ -69,9 +69,9 @@ public class FieldGetters { java.lang.reflect.Method[] methodArray = targetClass.getMethods(); for (java.lang.reflect.Method method : methodArray) { if (method.getName().equals(getterName) && method.getParameterTypes().length == 0) { - if (MethodKit.isForbiddenMethod(getterName)) { - throw new RuntimeException("Forbidden method: " + getterName); - } + // if (MethodKit.isForbiddenMethod(getterName)) { + // throw new RuntimeException("Forbidden method: " + getterName); + // } return new GetterMethodFieldGetter(method); } diff --git a/src/main/java/com/jfinal/template/expr/ast/Method.java b/src/main/java/com/jfinal/template/expr/ast/Method.java index 1503d57..237e330 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Method.java +++ b/src/main/java/com/jfinal/template/expr/ast/Method.java @@ -88,9 +88,7 @@ public class Method extends Expr { } throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location); - } catch (TemplateException e) { - throw e; - } catch (ParseException e) { + } catch (TemplateException | ParseException e) { throw e; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); diff --git a/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java b/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java index fb3f434..0930824 100644 --- a/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java +++ b/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java @@ -63,9 +63,7 @@ public class SharedMethod extends Expr { throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location); } - } catch (TemplateException e) { - throw e; - } catch (ParseException e) { + } catch (TemplateException | ParseException e) { throw e; } catch (Exception e) { throw new TemplateException(e.getMessage(), location, e); diff --git a/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java b/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java index 6e950af..5b14bd7 100644 --- a/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java +++ b/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java @@ -72,9 +72,7 @@ public class StaticMethod extends Expr { throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location); } - } catch (TemplateException e) { - throw e; - } catch (ParseException e) { + } catch (TemplateException | ParseException e) { throw e; } catch (Exception e) { throw new TemplateException(e.getMessage(), location, e); diff --git a/src/main/java/com/jfinal/template/ext/directive/CallDirective.java b/src/main/java/com/jfinal/template/ext/directive/CallDirective.java new file mode 100644 index 0000000..561bc19 --- /dev/null +++ b/src/main/java/com/jfinal/template/ext/directive/CallDirective.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jfinal.template.ext.directive; + +import java.util.ArrayList; +import com.jfinal.template.Directive; +import com.jfinal.template.Env; +import com.jfinal.template.TemplateException; +import com.jfinal.template.expr.ast.Const; +import com.jfinal.template.expr.ast.Expr; +import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; +import com.jfinal.template.stat.ParseException; +import com.jfinal.template.stat.Scope; +import com.jfinal.template.stat.ast.Define; + +/** + * CallDirective 动态调用模板函数 + * + * 模板函数的名称与参数都可以动态指定,提升模板函数调用的灵活性 + * + * 例如: + * #call(funcName, p1, p2, ..., pn) + * 其中 funcName,为函数名,p1、p2、pn 为被调用函数所使用的参数 + * + * + * 如果希望模板函数不存在时忽略其调用,添加常量值 true 在第一个参数位置即可 + * 例如: + * #call(true, funcName, p1, p2, ..., pn) + * + * + * TODO 后续优化看一下 ast.Call.java + */ +public class CallDirective extends Directive { + + protected Expr funcNameExpr; + protected ExprList paraExpr; + + protected boolean nullSafe = false; // 是否支持函数名不存在时跳过 + + public void setExprList(ExprList exprList) { + int len = exprList.length(); + if (len == 0) { + throw new ParseException("模板函数名不能缺失", location); + } + + int index = 0; + Expr expr = exprList.getExpr(index); + if (expr instanceof Const && ((Const)expr).isBoolean()) { + if (len == 1) { + throw new ParseException("模板函数名不能缺失", location); + } + + nullSafe = ((Const)expr).getBoolean(); + index++; + } + + funcNameExpr = exprList.getExpr(index++); + + ArrayList list = new ArrayList(); + for (int i=index; i + * 示例: + * #switch (month) + * #case (1, 3, 5, 7, 8, 10, 12) + * #(month) 月有 31 天 + * #case (2) + * #(month) 月平年有28天,闰年有29天 + * #default + * 月份错误: #(month ?? "null") + * #end + * + * 如上代码所示,如果 #case 指令参数有多个值,那么可以用逗号分隔, + * 上述逗号表达式的值 1, 3, 5, 7, 8, 10, 12 之中只要有一个与 + * switch 指令参数 month 相等的话,该 case 分支就会被执行, + * 该特性从根本上消灭了 #break 指令的必要性 + * + * + * 除了常量值以外 #case 参数还可以是任意表达式 + * 例如: + * #case (a, b, c, x + y, obj.method(z)) + * + * 上述代码中 #case 参数中的所有表达式先会被求值,然后逐一与 #switch + * 参数进行对比,同样也是只要有一个对比相等,则该 case 分支就会被执行 + * + * + */ +public class Switch extends Stat implements CaseSetter { + + private Expr expr; + private Case nextCase; + private Default _default; + + public Switch(ExprList exprList, Location location) { + if (exprList.length() == 0) { + throw new ParseException("The parameter of #switch directive can not be blank", location); + } + this.expr = exprList.getActualExpr(); + } + + public void setNextCase(Case nextCase) { + this.nextCase = nextCase; + } + + public void setDefault(Default _default, Location location) { + if (this._default != null) { + throw new ParseException("The #default case of #switch is already defined", location); + } + this._default = _default; + } + + public void exec(Env env, Scope scope, Writer writer) { + Object switchValue = expr.eval(scope); + + if (nextCase != null && nextCase.execIfMatch(switchValue, env, scope, writer)) { + return ; + } + + if (_default != null) { + _default.exec(env, scope, writer); + } + } + + public boolean hasEnd() { + return true; + } +} + + + + + + +