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