diff --git a/pom.xml b/pom.xml index c85ae0a..b2d1e9c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.jfinal enjoy - 4.7 + 4.8 jar diff --git a/src/main/java/com/jfinal/proxy/ProxyCompiler.java b/src/main/java/com/jfinal/proxy/ProxyCompiler.java index 6ac6c01..ddc8bc3 100644 --- a/src/main/java/com/jfinal/proxy/ProxyCompiler.java +++ b/src/main/java/com/jfinal/proxy/ProxyCompiler.java @@ -128,7 +128,8 @@ public class ProxyCompiler { public void compile(ProxyClass proxyClass) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { - throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH"); + throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" + + "Visit https://jfinal.com/doc/4-8 for details \n"); } DiagnosticCollector collector = new DiagnosticCollector<>(); 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 237e330..96d2f14 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Method.java +++ b/src/main/java/com/jfinal/template/expr/ast/Method.java @@ -79,7 +79,7 @@ public class Method extends Expr { try { MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues); - if (methodInfo != null) { + if (methodInfo.notNull()) { return methodInfo.invoke(target, argValues); } diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java b/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java index 224f50d..2c3ec70 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java @@ -93,6 +93,29 @@ public class MethodInfo { } return ret.append(")").toString(); } + + // --------- 以下代码仅用于支持 NullMethodInfo + + /** + * 仅供 NullMethodInfo 继承使用 + */ + protected MethodInfo() { + this.key = null; + this.clazz = null; + this.method = null; + this.isVarArgs = false; + this.paraTypes = null; + } + + /** + * 仅仅 NullMethodInfo 会覆盖此方法并返回 false + * + * 1:MethodKit.getMethod(...) 消除 instanceof 判断 + * 2:Method.exec(...) 消除 null 值判断 + */ + public boolean notNull() { + return true; + } } diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodKit.java b/src/main/java/com/jfinal/template/expr/ast/MethodKit.java index 872f264..cc4fb66 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodKit.java @@ -41,7 +41,7 @@ public class MethodKit { private static final Set forbiddenMethods = new HashSet(64); private static final Set> forbiddenClasses = new HashSet>(64); private static final Map, Class> primitiveMap = new HashMap, Class>(64); - private static final SyncWriteMap methodCache = new SyncWriteMap(2048, 0.25F); + private static final SyncWriteMap methodCache = new SyncWriteMap(2048, 0.25F); // 初始化在模板中调用 method 时所在的被禁止使用类 static { @@ -124,36 +124,17 @@ public class MethodKit { public static MethodInfo getMethod(Class targetClass, String methodName, Object[] argValues) { Class[] argTypes = getArgTypes(argValues); Long key = getMethodKey(targetClass, methodName, argTypes); - Object method = methodCache.get(key); + MethodInfo method = methodCache.get(key); if (method == null) { + // 已确保不会返回 null,对于不存在的 Method,只进行一次获取操作 + // 提升 null safe 表达式性能,未来需要考虑内存泄漏风险 method = doGetMethod(key, targetClass, methodName, argTypes); - if (method != null) { - methodCache.putIfAbsent(key, method); - } else { - // 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险 - methodCache.putIfAbsent(key, Void.class); - } + methodCache.putIfAbsent(key, method); } - return method instanceof MethodInfo ? (MethodInfo)method : null; + + return method; } - /** - * 获取 getter 方法 - * 使用与 Field 相同的 key,避免生成两次 key值 - * ---> jfinal 3.5 已将此功能转移至 FieldKit - public static MethodInfo getGetterMethod(Long key, Class targetClass, String methodName) { - Object getterMethod = methodCache.get(key); - if (getterMethod == null) { - getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES); - if (getterMethod != null) { - methodCache.putIfAbsent(key, getterMethod); - } else { - methodCache.putIfAbsent(key, Void.class); - } - } - return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null; - } */ - static Class[] getArgTypes(Object[] argValues) { if (argValues == null || argValues.length == 0) { return NULL_ARG_TYPES; @@ -169,7 +150,9 @@ public class MethodKit { if (forbiddenClasses.contains(targetClass)) { throw new RuntimeException("Forbidden class: " + targetClass.getName()); } + // 仅开启 forbiddenClasses 检测 + // Method、SharedMethod、StaticMethod 已用 MethodKit.isForbiddenMethod(...) 检测 // if (forbiddenMethods.contains(methodName)) { // throw new RuntimeException("Forbidden method: " + methodName); // } @@ -186,7 +169,8 @@ public class MethodKit { } } } - return null; + + return NullMethodInfo.me; } static boolean matchFixedArgTypes(Class[] paraTypes, Class[] argTypes) { diff --git a/src/main/java/com/jfinal/template/expr/ast/NullMethodInfo.java b/src/main/java/com/jfinal/template/expr/ast/NullMethodInfo.java new file mode 100644 index 0000000..87b334a --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/NullMethodInfo.java @@ -0,0 +1,37 @@ +/** + * 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.expr.ast; + +/** + * NullMethodInfo + * + * 1:MethodKit.getMethod(...) 消除 instanceof 判断 + * 2:Method.exec(...) 消除 null 值判断 + */ +public class NullMethodInfo extends MethodInfo { + + public static final NullMethodInfo me = new NullMethodInfo(); + + public boolean notNull() { + return false; + } + + public Object invoke(Object target, Object... args) throws ReflectiveOperationException { + throw new RuntimeException("The method invoke(Object, Object...) of NullMethodInfo should not be invoked"); + } +} + 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 5b14bd7..efb1dc5 100644 --- a/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java +++ b/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java @@ -50,6 +50,14 @@ public class StaticMethod extends Expr { } catch (Exception e) { throw new ParseException(e.getMessage(), location, e); } + + if (MethodKit.isForbiddenClass(this.clazz)) { + throw new ParseException("Forbidden class: " + this.clazz.getName(), location); + } + if (MethodKit.isForbiddenMethod(methodName)) { + throw new ParseException("Forbidden method: " + methodName, location); + } + this.methodName = methodName; this.exprList = exprList; this.location = location; @@ -61,7 +69,7 @@ public class StaticMethod extends Expr { try { MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues); - if (methodInfo != null) { + if (methodInfo.notNull()) { if (methodInfo.isStatic()) { return methodInfo.invoke(null, argValues); } else { diff --git a/src/main/java/com/jfinal/template/stat/Scope.java b/src/main/java/com/jfinal/template/stat/Scope.java index 9e36250..1080a5f 100644 --- a/src/main/java/com/jfinal/template/stat/Scope.java +++ b/src/main/java/com/jfinal/template/stat/Scope.java @@ -78,6 +78,7 @@ public class Scope { if (cur.data == null) { // 支持顶层 data 为 null 值 cur.data = new HashMap(); } + cur.data.put(key, value); return ; } @@ -159,6 +160,10 @@ public class Scope { public void setGlobal(Object key, Object value) { for (Scope cur=this; true; cur=cur.parent) { if (cur.parent == null) { + if (cur.data == null) { + cur.data = new HashMap(); + } + cur.data.put(key, value); return ; } @@ -172,7 +177,7 @@ public class Scope { public Object getGlobal(Object key) { for (Scope cur=this; true; cur=cur.parent) { if (cur.parent == null) { - return cur.data.get(key); + return cur.data != null ? cur.data.get(key) : null; } } } @@ -184,7 +189,10 @@ public class Scope { public void removeGlobal(Object key) { for (Scope cur=this; true; cur=cur.parent) { if (cur.parent == null) { - cur.data.remove(key); + if (cur.data != null) { + cur.data.remove(key); + } + return ; } } diff --git a/src/main/java/com/jfinal/template/stat/ast/Set.java b/src/main/java/com/jfinal/template/stat/ast/Set.java index 38472d7..84db541 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Set.java +++ b/src/main/java/com/jfinal/template/stat/ast/Set.java @@ -20,6 +20,7 @@ import com.jfinal.template.Env; import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.expr.ast.IncDec; import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; @@ -44,10 +45,11 @@ public class Set extends Stat { } for (Expr expr : exprList.getExprArray()) { - if ( !(expr instanceof Assign) ) { + if ( !(expr instanceof Assign || expr instanceof IncDec) ) { throw new ParseException("#set directive only supports assignment expressions", location); } } + this.expr = exprList.getActualExpr(); } diff --git a/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java b/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java index fcc9e47..1ba9bd6 100644 --- a/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java +++ b/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java @@ -20,6 +20,7 @@ import com.jfinal.template.Env; import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.expr.ast.IncDec; import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Location; @@ -41,10 +42,11 @@ public class SetGlobal extends Stat { } for (Expr expr : exprList.getExprArray()) { - if ( !(expr instanceof Assign) ) { + if ( !(expr instanceof Assign || expr instanceof IncDec) ) { throw new ParseException("#setGlobal directive only supports assignment expressions", location); } } + this.expr = exprList.getActualExpr(); } diff --git a/src/main/java/com/jfinal/template/stat/ast/SetLocal.java b/src/main/java/com/jfinal/template/stat/ast/SetLocal.java index 23804b2..a9b8b30 100644 --- a/src/main/java/com/jfinal/template/stat/ast/SetLocal.java +++ b/src/main/java/com/jfinal/template/stat/ast/SetLocal.java @@ -20,6 +20,7 @@ import com.jfinal.template.Env; import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.expr.ast.IncDec; import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Location; @@ -42,10 +43,11 @@ public class SetLocal extends Stat { } for (Expr expr : exprList.getExprArray()) { - if ( !(expr instanceof Assign) ) { + if ( !(expr instanceof Assign || expr instanceof IncDec) ) { throw new ParseException("#setLocal directive only supports assignment expressions", location); } } + this.expr = exprList.getActualExpr(); }