From 13f2d302c3449f1fc5b9167d7193aeff961df8bf Mon Sep 17 00:00:00 2001 From: James Date: Thu, 4 Oct 2018 21:38:21 +0800 Subject: [PATCH] jfinal enjoy 3.5 --- pom.xml | 34 +-- src/main/java/com/jfinal/template/Engine.java | 82 ++++-- .../com/jfinal/template/expr/ast/Field.java | 52 +--- .../jfinal/template/expr/ast/FieldGetter.java | 58 ++++ .../template/expr/ast/FieldGetters.java | 270 ++++++++++++++++++ .../template/expr/ast/FieldKeyBuilder.java | 128 +++++++++ .../jfinal/template/expr/ast/FieldKit.java | 126 ++++++-- .../com/jfinal/template/expr/ast/Method.java | 30 +- .../jfinal/template/expr/ast/MethodInfo.java | 5 +- .../template/expr/ast/MethodInfoExt.java | 3 +- .../template/expr/ast/MethodKeyBuilder.java | 13 +- .../jfinal/template/expr/ast/MethodKit.java | 20 +- .../template/expr/ast/SharedMethod.java | 29 +- .../template/expr/ast/SharedMethodKit.java | 7 +- .../template/expr/ast/StaticMethod.java | 32 ++- .../ext/directive/RenderDirective.java | 23 +- .../com/jfinal/template/io/WriterBuffer.java | 2 +- .../template/source/ClassPathSource.java | 2 +- .../jfinal/template/source/FileSource.java | 2 +- .../com/jfinal/template/source/ISource.java | 6 +- .../jfinal/template/source/StringSource.java | 14 +- .../java/com/jfinal/template/stat/Parser.java | 2 +- .../com/jfinal/template/stat/ast/Output.java | 2 + .../jfinal/template/stat/ast/SetGlobal.java | 2 +- 24 files changed, 763 insertions(+), 181 deletions(-) create mode 100644 src/main/java/com/jfinal/template/expr/ast/FieldGetter.java create mode 100644 src/main/java/com/jfinal/template/expr/ast/FieldGetters.java create mode 100644 src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java diff --git a/pom.xml b/pom.xml index 4774c6e..07d4164 100644 --- a/pom.xml +++ b/pom.xml @@ -73,8 +73,8 @@ maven-compiler-plugin 3.6.1 - 1.6 - 1.6 + 1.7 + 1.7 @@ -91,21 +91,21 @@ - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins diff --git a/src/main/java/com/jfinal/template/Engine.java b/src/main/java/com/jfinal/template/Engine.java index 96b8c3c..d908b35 100644 --- a/src/main/java/com/jfinal/template/Engine.java +++ b/src/main/java/com/jfinal/template/Engine.java @@ -22,6 +22,9 @@ import java.util.Map; import com.jfinal.kit.HashKit; import com.jfinal.kit.StrKit; import com.jfinal.kit.SyncWriteMap; +import com.jfinal.template.expr.ast.FieldGetter; +import com.jfinal.template.expr.ast.FieldKeyBuilder; +import com.jfinal.template.expr.ast.FieldKit; import com.jfinal.template.expr.ast.MethodKit; import com.jfinal.template.source.ClassPathSourceFactory; import com.jfinal.template.source.ISource; @@ -126,7 +129,7 @@ public class Engine { } /** - * Get template with file name + * Get template by file name */ public Template getTemplate(String fileName) { if (fileName.charAt(0) != '/') { @@ -172,9 +175,9 @@ public class Engine { /** * Get template by string content * - * 重要:StringSource 中的 key = HashKit.md5(content),也即 key - * 与 content 有紧密的对应关系,当 content 发生变化时 key 值也相应变化 - * 因此,原先 key 所对应的 Template 缓存对象已无法被获取,当 getTemplateByString(String) + * 重要:StringSource 中的 cacheKey = HashKit.md5(content),也即 cacheKey + * 与 content 有紧密的对应关系,当 content 发生变化时 cacheKey 值也相应变化 + * 因此,原先 cacheKey 所对应的 Template 缓存对象已无法被获取,当 getTemplateByString(String) * 的 String 参数的数量不确定时会引发内存泄漏 * * 当 getTemplateByString(String, boolean) 中的 String 参数的 @@ -188,37 +191,37 @@ public class Engine { return buildTemplateBySource(new StringSource(content, cache)); } - String key = HashKit.md5(content); - Template template = templateCache.get(key); + String cacheKey = HashKit.md5(content); + Template template = templateCache.get(cacheKey); if (template == null) { template = buildTemplateBySource(new StringSource(content, cache)); - templateCache.put(key, template); + templateCache.put(cacheKey, template); } else if (devMode) { if (template.isModified()) { template = buildTemplateBySource(new StringSource(content, cache)); - templateCache.put(key, template); + templateCache.put(cacheKey, template); } } return template; } /** - * Get template with implementation of ISource + * Get template by implementation of ISource */ public Template getTemplate(ISource source) { - String key = source.getKey(); - if (key == null) { // key 为 null 则不缓存,详见 ISource.getKey() 注释 + String cacheKey = source.getCacheKey(); + if (cacheKey == null) { // cacheKey 为 null 则不缓存,详见 ISource.getCacheKey() 注释 return buildTemplateBySource(source); } - Template template = templateCache.get(key); + Template template = templateCache.get(cacheKey); if (template == null) { template = buildTemplateBySource(source); - templateCache.put(key, template); + templateCache.put(cacheKey, template); } else if (devMode) { if (template.isModified()) { template = buildTemplateBySource(source); - templateCache.put(key, template); + templateCache.put(cacheKey, template); } } return template; @@ -236,7 +239,7 @@ public class Engine { } /** - * Add shared function with file + * Add shared function by file */ public Engine addSharedFunction(String fileName) { config.addSharedFunction(fileName); @@ -252,7 +255,7 @@ public class Engine { } /** - * Add shared function with files + * Add shared function by files */ public Engine addSharedFunction(String... fileNames) { config.addSharedFunction(fileNames); @@ -336,7 +339,7 @@ public class Engine { } /** - * Remove shared Method with method name + * Remove shared Method by method name */ public Engine removeSharedMethod(String methodName) { config.removeSharedMethod(methodName); @@ -360,10 +363,10 @@ public class Engine { } /** - * Remove template cache with template key + * Remove template cache by cache key */ - public void removeTemplateCache(String templateKey) { - templateCache.remove(templateKey); + public void removeTemplateCache(String cacheKey) { + templateCache.remove(cacheKey); } /** @@ -500,6 +503,45 @@ public class Engine { public static void removeExtensionMethod(Class targetClass, Class extensionClass) { MethodKit.removeExtensionMethod(targetClass, extensionClass); } + + /** + * 添加 FieldGetter 实现类到指定的位置 + * + * 系统当前默认 FieldGetter 实现类及其位置如下: + * GetterMethodFieldGetter ---> 调用 getter 方法取值 + * ModelFieldGetter ---> 调用 Model.get(String) 方法取值 + * RecordFieldGetter ---> 调用 Record.get(String) 方法取值 + * MapFieldGetter ---> 调用 Map.get(String) 方法取值 + * RealFieldGetter ---> 直接获取 public 型的 object.field 值 + * ArrayLengthGetter ---> 获取数组长度 + * + * 根据以上次序,如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter + * 之后的代码如下: + * Engine.addFieldGetter(1, new IsMethodFieldGetter()); + * + * 注:IsMethodFieldGetter 系统已经提供,只是默认没有启用。该实现类通过调用 + * target.isXxx() 方法获取 target.xxx 表达式的值,其中 xxx 字段必须是 + * Boolean/boolean 类型 + */ + public static void addFieldGetter(int index, FieldGetter fieldGetter) { + FieldKit.addFieldGetter(index, fieldGetter); + } + + public static void addFieldGetterToLast(FieldGetter fieldGetter) { + FieldKit.addFieldGetterToLast(fieldGetter); + } + + public static void addFieldGetterToFirst(FieldGetter fieldGetter) { + FieldKit.addFieldGetterToFirst(fieldGetter); + } + + public static void removeFieldGetter(Class fieldGetterClass) { + FieldKit.removeFieldGetter(fieldGetterClass); + } + + public static void setToFastFieldKeyBuilder() { + FieldKeyBuilder.setToFastFieldKeyBuilder(); + } } 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 09a15a1..929863b 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Field.java +++ b/src/main/java/com/jfinal/template/expr/ast/Field.java @@ -16,11 +16,8 @@ package com.jfinal.template.expr.ast; -import java.lang.reflect.Array; import com.jfinal.kit.HashKit; import com.jfinal.kit.StrKit; -// import com.jfinal.plugin.activerecord.Model; -// import com.jfinal.plugin.activerecord.Record; import com.jfinal.template.TemplateException; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; @@ -68,49 +65,26 @@ public class Field extends Expr { throw new TemplateException("Can not accessed by \"" + fieldName + "\" field from null target", location); } - Class targetClass = target.getClass(); - Long key = buildFieldKey(targetClass); - MethodInfo getter; try { - getter = MethodKit.getGetterMethod(key, targetClass, getterName); + Class targetClass = target.getClass(); + Object key = FieldKeyBuilder.instance.getFieldKey(targetClass, getterNameHash); + FieldGetter fieldGetter = FieldKit.getFieldGetter(key, targetClass, fieldName); + if (fieldGetter.notNull()) { + return fieldGetter.get(target, fieldName); + } + } catch (TemplateException e) { + throw e; + } catch (ParseException e) { + throw e; } catch (Exception e) { throw new TemplateException(e.getMessage(), location, e); } - try { - if (getter != null) { - return getter.invoke(target, ExprList.NULL_OBJECT_ARRAY); - } -// if (target instanceof Model) { -// return ((Model)target).get(fieldName); -// } -// if (target instanceof Record) { -// return ((Record)target).get(fieldName); -// } - if (target instanceof java.util.Map) { - return ((java.util.Map)target).get(fieldName); - } - // if (target instanceof com.jfinal.kit.Ret) { - // return ((com.jfinal.kit.Ret)target).get(fieldName); - // } - java.lang.reflect.Field field = FieldKit.getField(key, targetClass, fieldName); - if (field != null) { - return field.get(target); - } - - // 支持获取数组长度: array.length - if ("length".equals(fieldName) && target.getClass().isArray()) { - return Array.getLength(target); - } - } catch (Exception e) { - throw new TemplateException(e.getMessage(), location, e); - } if (scope.getCtrl().isNullSafe()) { return null; } - if (expr instanceof Id) { String id = ((Id)expr).getId(); throw new TemplateException("public field not found: \"" + id + "." + fieldName + "\" and public getter method not found: \"" + id + "." + getterName + "()\"", location); @@ -118,9 +92,9 @@ public class Field extends Expr { throw new TemplateException("public field not found: \"" + fieldName + "\" and public getter method not found: \"" + getterName + "()\"", location); } - private Long buildFieldKey(Class targetClass) { - return targetClass.getName().hashCode() ^ getterNameHash; - } + // private Long buildFieldKey(Class targetClass) { + // return targetClass.getName().hashCode() ^ getterNameHash; + // } } diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldGetter.java b/src/main/java/com/jfinal/template/expr/ast/FieldGetter.java new file mode 100644 index 0000000..5f2f474 --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/FieldGetter.java @@ -0,0 +1,58 @@ +/** + * 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; + +/** + * FieldGetter 用于支持 target.field 表达式的取值, + * 以及支持用户扩展自定义的 FieldGetter 实现方式 + */ +public abstract class FieldGetter { + + /** + * 接管 target.fieldName 表达式,如果可以接管则返回接管对象,否则返回 null + * @param targetClass target.fieldName 表达式中 target 的 Class 类型 + * @param fieldName target.fieldName 表达式中的 fieldName 部分 + * @return 如果可以接管 targetClass.fieldName 则返回接管对象,否则返回 null + */ + public abstract FieldGetter takeOver(Class targetClass, String fieldName); + + /** + * 获取 target.fieldName 表达式的值 + * @param target 目标对象 + * @param fieldName 字段名称 + * @return target.fieldName 表达式的值 + */ + public abstract Object get(Object target, String fieldName) throws Exception; + + /** + * 仅仅 NullFieldGetter 会覆盖此方法并返回 false + * + * 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断, + * 并且让 Map fieldGetterCache 的 value 值不必使用 Object 类型, + * 还消除了 Field.exec(...) 中的 null 值判断 + */ + public boolean notNull() { + return true; + } +} + + + + + + + diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java b/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java new file mode 100644 index 0000000..3114e6d --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java @@ -0,0 +1,270 @@ +/** + * 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; + +import java.lang.reflect.Array; +import com.jfinal.kit.StrKit; + +/** + * FieldGetters 封装官方默认 FieldGetter 实现 + */ +public class FieldGetters { + + /** + * NullFieldGetter + * + * 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断,并且让 Map fieldGetterCache + * 中的 value 不必使用 Object 类型。还消除了 Field.exec(...) 中的 null 值判断 + */ + public static class NullFieldGetter extends FieldGetter { + + public static final NullFieldGetter me = new NullFieldGetter(); + + public boolean notNull() { + return false; + } + + public FieldGetter takeOver(Class targetClass, String fieldName) { + throw new RuntimeException("The method takeOver(Class, String) of NullFieldGetter should not be invoked"); + } + + public Object get(Object target, String fieldName) throws Exception { + throw new RuntimeException("The method get(Object, String) of NullFieldGetter should not be invoked"); + } + } + + /** + * GetterMethodFieldGetter + * + * 使用 getter 方法获取 target.field 表达式的值 + */ + public static class GetterMethodFieldGetter extends FieldGetter { + + protected java.lang.reflect.Method getterMethod; + + public GetterMethodFieldGetter(java.lang.reflect.Method getterMethod) { + this.getterMethod = getterMethod; + } + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if (MethodKit.isForbiddenClass(targetClass)) { + throw new RuntimeException("Forbidden class: " + targetClass.getName()); + } + + String getterName = "get" + StrKit.firstCharToUpperCase(fieldName); + 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); + } + + return new GetterMethodFieldGetter(method); + } + } + + return null; + } + + public Object get(Object target, String fieldName) throws Exception { + return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY); + } + + public String toString() { + return getterMethod.toString(); + } + } + + /** + * IsMethodFieldGetter + * + * 使用 target.isXxx() 方法获取值,默认不启用该功能,用户可以通过如下方式启用: + * Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter()); + */ + public static class IsMethodFieldGetter extends FieldGetter { + + protected java.lang.reflect.Method isMethod; + + // 此构造方法仅为了方便在 Engine.addFieldGetter(...) 添加时不用为构造方法传参 + public IsMethodFieldGetter() { + } + + public IsMethodFieldGetter(java.lang.reflect.Method isMethod) { + this.isMethod = isMethod; + } + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if (MethodKit.isForbiddenClass(targetClass)) { + throw new RuntimeException("Forbidden class: " + targetClass.getName()); + } + + String isMethodName = "is" + StrKit.firstCharToUpperCase(fieldName); + java.lang.reflect.Method[] methodArray = targetClass.getMethods(); + for (java.lang.reflect.Method method : methodArray) { + if (method.getName().equals(isMethodName) && method.getParameterTypes().length == 0) { + Class returnType = method.getReturnType(); + if (returnType == Boolean.class || returnType == boolean.class) { + return new IsMethodFieldGetter(method); + } + } + } + + return null; + } + + public Object get(Object target, String fieldName) throws Exception { + return isMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY); + } + + public String toString() { + return isMethod.toString(); + } + } + + /** + * ModelFieldGetter + * + * 使用 Model.get(String) 获取值 + + public static class ModelFieldGetter extends FieldGetter { + + // 所有 Model 可以共享 ModelFieldGetter 获取属性 + static final ModelFieldGetter singleton = new ModelFieldGetter(); + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if (com.jfinal.plugin.activerecord.Model.class.isAssignableFrom(targetClass)) { + return singleton; + } else { + return null; + } + } + + public Object get(Object target, String fieldName) throws Exception { + return ((com.jfinal.plugin.activerecord.Model)target).get(fieldName); + } + } */ + + /** + * RecordFieldGetter + * + * 使用 Record.get(String) 获取值 + + public static class RecordFieldGetter extends FieldGetter { + + // 所有 Record 可以共享 RecordFieldGetter 获取属性 + static final RecordFieldGetter singleton = new RecordFieldGetter(); + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if (com.jfinal.plugin.activerecord.Record.class.isAssignableFrom(targetClass)) { + return singleton; + } else { + return null; + } + } + + public Object get(Object target, String fieldName) throws Exception { + return ((com.jfinal.plugin.activerecord.Record)target).get(fieldName); + } + } */ + + /** + * MapFieldGetter + * + * 使用 Map.get(Object) 获取值 + */ + public static class MapFieldGetter extends FieldGetter { + + // 所有 Map 可以共享 MapFieldGetter 获取属性 + static final MapFieldGetter singleton = new MapFieldGetter(); + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if (java.util.Map.class.isAssignableFrom(targetClass)) { + return singleton; + } else { + return null; + } + } + + public Object get(Object target, String fieldName) throws Exception { + return ((java.util.Map)target).get(fieldName); + } + } + + /** + * RealFieldGetter + * + * 使用 target.field 获取值 + */ + public static class RealFieldGetter extends FieldGetter { + + protected java.lang.reflect.Field field; + + public RealFieldGetter(java.lang.reflect.Field field) { + this.field = field; + } + + public FieldGetter takeOver(Class targetClass, String fieldName) { + java.lang.reflect.Field[] fieldArray = targetClass.getFields(); + for (java.lang.reflect.Field field : fieldArray) { + if (field.getName().equals(fieldName)) { + return new RealFieldGetter(field); + } + } + + return null; + } + + public Object get(Object target, String fieldName) throws Exception { + return field.get(target); + } + + public String toString() { + return field.toString(); + } + } + + /** + * ArrayLengthGetter + * + * 获取数组长度: array.length + */ + public static class ArrayLengthGetter extends FieldGetter { + + // 所有数组可以共享 ArrayLengthGetter 获取属性 + static final ArrayLengthGetter singleton = new ArrayLengthGetter(); + + public FieldGetter takeOver(Class targetClass, String fieldName) { + if ("length".equals(fieldName) && targetClass.isArray()) { + return singleton; + } else { + return null; + } + } + + public Object get(Object target, String fieldName) throws Exception { + return Array.getLength(target); + } + } +} + + + + + + + + diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java b/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java new file mode 100644 index 0000000..6231139 --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java @@ -0,0 +1,128 @@ +/** + * 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; + +/** + * FieldKeyBuilder + * + * 用于生成缓存 FieldGetter 的 key + */ +public abstract class FieldKeyBuilder { + + public abstract Object getFieldKey(Class targetClass, long fieldFnv1a64Hash); + + // 假定是超大规模项目,并且假定其 Map/Model/Record + field 组合数量超级庞大,默认使用 StrictFieldKeyBuilder + static FieldKeyBuilder instance = new StrictFieldKeyBuilder(); + + public static FieldKeyBuilder getInstance() { + return instance; + } + + /** + * 设置为官方提供的 FastFieldKeyBuilder 实现,性能更高 + */ + public static void setToFastFieldKeyBuilder() { + instance = new FastFieldKeyBuilder(); + } + + /** + * 设置为自定义 FieldKeyBuilder + */ + public static void setFieldKeyBuilder(FieldKeyBuilder fieldKeyBuilder) { + if (fieldKeyBuilder == null) { + throw new IllegalArgumentException("fieldKeyBuilder can not be null"); + } + instance = fieldKeyBuilder; + } + + // ------------------------------------------------------------------------ + + /** + * FastFieldKeyBuilder + */ + public static class FastFieldKeyBuilder extends FieldKeyBuilder { + public Object getFieldKey(Class targetClass, long fieldFnv1a64Hash) { + return targetClass.getName().hashCode() ^ fieldFnv1a64Hash; + } + } + + // ------------------------------------------------------------------------ + + /** + * StrictFieldKeyBuilder + */ + public static class StrictFieldKeyBuilder extends FieldKeyBuilder { + public Object getFieldKey(Class targetClass, long fieldFnv1a64Hash) { + return new FieldKey(targetClass.getName().hashCode(), fieldFnv1a64Hash); + } + } + + // ------------------------------------------------------------------------ + + /** + * FieldKey + * + * FieldKey 用于封装 targetClass、fieldName 这两部分的 hash 值, + * 确保不会出现 key 值碰撞 + * + * 这两部分 hash 值在不同 class 与 field 的组合下出现碰撞的 + * 概率完全可以忽略不计 + * + * 备忘: + * 可以考虑用 ThreadLocal 重用 FieldKey 对象,但要注意放入 Map fieldGetterCache + * 中的 FieldKey 对象需要 clone 出来,确保线程安全。由于 FieldKey 占用空间不大, + * 所以 ThreadLocal 方案大概率并无优势,从 ThreadLocal 中获取数据时,除了耗时也无法 + * 避免创建对象 + */ + public static class FieldKey { + + final long classHash; + final long fieldHash; + + public FieldKey(long classHash, long fieldHash) { + this.classHash = classHash; + this.fieldHash = fieldHash; + } + + public int hashCode() { + return (int)(classHash ^ fieldHash); + } + + /** + * FieldKey 的核心价值在于此 equals 方法通过比较两部分 hash 值,避免超大规模场景下可能的 key 值碰撞 + * + * 不必判断 if (fieldKey instanceof FieldKey),因为所有 key 类型必须要相同 + * 不必判断 if (this == fieldKey),因为每次用于取值的 FieldKey 都是新建的 + */ + public boolean equals(Object fieldKey) { + FieldKey fk = (FieldKey)fieldKey; + return classHash == fk.classHash && fieldHash == fk.fieldHash; + } + + public String toString() { + return "classHash = " + classHash + "\nfieldHash = " + fieldHash; + } + } +} + + + + + + + + diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java index f233f38..84f1558 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java @@ -16,39 +16,122 @@ package com.jfinal.template.expr.ast; -import java.lang.reflect.Field; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; import com.jfinal.kit.SyncWriteMap; +import com.jfinal.template.expr.ast.FieldGetters.*; /** * FieldKit */ public class FieldKit { - private static final HashMap fieldCache = new SyncWriteMap(512, 0.5F); + private static FieldGetter[] getters = init(); - public static Field getField(Long key, Class targetClass, String fieldName) { - Object field = fieldCache.get(key); - if (field == null) { - field = doGetField(targetClass, fieldName); - if (field != null) { - fieldCache.put(key, field); - } else { - // 对于不存在的 Field,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险 - fieldCache.put(key, Boolean.FALSE); - } - } - return field instanceof Field ? (Field)field : null; + private static final HashMap fieldGetterCache = new SyncWriteMap(1024, 0.25F); + + /** + * 初始化官方默认 FieldGetter + * + * 注意: + * 默认不启用 IsMethodFieldGetter,用户可以通过下面的代码启用: + * Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter()); + * + * 也可以通过直接调用 target.isXxx() 方法来达到与 target.xxx 表达式相同的目的 + */ + private static FieldGetter[] init() { + LinkedList ret = new LinkedList(); + + ret.addLast(new GetterMethodFieldGetter(null)); + // ret.addLast(new ModelFieldGetter()); + // ret.addLast(new RecordFieldGetter()); + ret.addLast(new MapFieldGetter()); + ret.addLast(new RealFieldGetter(null)); + ret.addLast(new ArrayLengthGetter()); + // ret.addLast(new IsMethodFieldGetter()); + + return ret.toArray(new FieldGetter[ret.size()]); } - private static Field doGetField(Class targetClass, String fieldName) { - Field[] fs = targetClass.getFields(); - for (Field f : fs) { - if (f.getName().equals(fieldName)) { - return f; + public static FieldGetter getFieldGetter(Object key, Class targetClass, String fieldName) { + FieldGetter fieldGetter = fieldGetterCache.get(key); + if (fieldGetter == null) { + fieldGetter = doGetFieldGetter(targetClass, fieldName); // 已确保不会返回 null + fieldGetterCache.putIfAbsent(key, fieldGetter); + } + return fieldGetter; + } + + private static FieldGetter doGetFieldGetter(Class targetClass, String fieldName) { + FieldGetter ret; + for (FieldGetter fieldGetter : getters) { + ret = fieldGetter.takeOver(targetClass, fieldName); + if (ret != null) { + return ret; } } - return null; + return NullFieldGetter.me; + } + + public static void addFieldGetter(int index, FieldGetter fieldGetter) { + addFieldGetter(fieldGetter, index, true); + } + + public static void addFieldGetterToLast(FieldGetter fieldGetter) { + addFieldGetter(fieldGetter, null, true); + } + + public static void addFieldGetterToFirst(FieldGetter fieldGetter) { + addFieldGetter(fieldGetter, null, false); + } + + // 当 Integer index 不为 null 时,boolean addLast 为无效参数 + private static synchronized void addFieldGetter(FieldGetter fieldGetter, Integer index, boolean addLast) { + checkParameter(fieldGetter); + + LinkedList ret = getCurrentFieldGetters(); + if (index != null) { + ret.add(index, fieldGetter); + } else { + if (addLast) { + ret.addLast(fieldGetter); + } else { + ret.addFirst(fieldGetter); + } + } + getters = ret.toArray(new FieldGetter[ret.size()]); + } + + private static LinkedList getCurrentFieldGetters() { + LinkedList ret = new LinkedList(); + for (FieldGetter fieldGetter : getters) { + ret.add(fieldGetter); + } + return ret; + } + + private static void checkParameter(FieldGetter fieldGetter) { + if (fieldGetter == null) { + throw new IllegalArgumentException("The parameter fieldGetter can not be null"); + } + for (FieldGetter fg : getters) { + if (fg.getClass() == fieldGetter.getClass()) { + throw new RuntimeException("FieldGetter already exists : " + fieldGetter.getClass().getName()); + } + } + } + + public static synchronized void removeFieldGetter(Class fieldGetterClass) { + LinkedList ret = getCurrentFieldGetters(); + + for (Iterator it = ret.iterator(); it.hasNext();) { + if (it.next().getClass() == fieldGetterClass) { + it.remove(); + } + } + + getters = ret.toArray(new FieldGetter[ret.size()]); } } @@ -56,3 +139,6 @@ public class FieldKit { + + + 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 4c68e37..1503d57 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Method.java +++ b/src/main/java/com/jfinal/template/expr/ast/Method.java @@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope; * 如果在未来通过结合 #dynamic(boolean) 指令来优化,需要在 Ctrl 中引入一个 * boolean dynamic = false 变量,而不能在 Env、Scope 引入该变量 * - * 还需要引入一个 NullMethodInfo 以及 isNull() 方法,此优化复杂度提高不少, + * 还需要引入一个 NullMethodInfo 以及 notNull() 方法,此优化复杂度提高不少, * 暂时不做此优化 */ public class Method extends Expr { @@ -76,28 +76,26 @@ public class Method extends Expr { } Object[] argValues = exprList.evalExprList(scope); - MethodInfo methodInfo; try { - methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues); - } catch (Exception e) { - throw new TemplateException(e.getMessage(), location, e); - } - if (methodInfo == null) { + + MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues); + if (methodInfo != null) { + return methodInfo.invoke(target, argValues); + } + if (scope.getCtrl().isNullSafe()) { return null; } throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location); - } - - try { - return methodInfo.invoke(target, argValues); + + } catch (TemplateException e) { + throw e; + } catch (ParseException e) { + throw e; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); - if (t != null) { - throw new TemplateException(t.getMessage(), location, t); - } else { - throw new TemplateException(e.getMessage(), location, e); - } + if (t == null) {t = e;} + throw new TemplateException(t.getMessage(), location, t); } catch (Exception e) { throw new TemplateException(e.getMessage(), location, e); } 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 1f0f346..224f50d 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java @@ -17,7 +17,6 @@ package com.jfinal.template.expr.ast; import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -41,7 +40,7 @@ public class MethodInfo { this.paraTypes = method.getParameterTypes(); } - public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public Object invoke(Object target, Object... args) throws ReflectiveOperationException { if (isVarArgs) { return invokeVarArgsMethod(target, args); } else { @@ -49,7 +48,7 @@ public class MethodInfo { } } - protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws ReflectiveOperationException { Object[] finalArgValues = new Object[paraTypes.length]; int fixedParaLength = paraTypes.length - 1; diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java b/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java index 2eae3cd..091378a 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java @@ -16,7 +16,6 @@ package com.jfinal.template.expr.ast; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** @@ -37,7 +36,7 @@ public class MethodInfoExt extends MethodInfo { // this.paraTypes = newParaTypes; } - public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public Object invoke(Object target, Object... args) throws ReflectiveOperationException { Object[] finalArgs = new Object[args.length + 1]; finalArgs[0] = target; diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java b/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java index 51ee170..71987ad 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java @@ -43,17 +43,20 @@ public abstract class MethodKeyBuilder { * 如果希望将 configEngine(Engine me) 中的 Engine 切换到 StrictMethodKeyBuilder, * 需要在 YourJFinalConfig extends JFinalConfig 中利用如下代码块才能生效: * static { - * MethodKeyBuilder.useStrictMethodKeyBuilder(); + * MethodKeyBuilder.setToStrictMethodKeyBuilder(); * } * - * 原因是在 com.jfinal.core.Config 中 new Engine() 时 useStrictMethodKeyBuilder() + * 原因是在 com.jfinal.core.Config 中 new Engine() 时 setToStrictMethodKeyBuilder() * 方法并未生效,所以 extension method 生成 method key 时仍然使用的是 FastMethodKeyBuilder * 以至于在运行时,使用 StrictMethodKeyBuilder 生成的 key 找不到 extension method + * + * 后续版本考虑在调用 setToStrictMethodKeyBuilder() 以后重新初始化一下 MethodKit 中的变量 + * 原先的 static 初始化方式重构出 synchronized void init() 方法来方便调用 * * */ - public static void useStrictMethodKeyBuilder() { - MethodKeyBuilder.instance = new StrictMethodKeyBuilder(); + public static void setToStrictMethodKeyBuilder() { + instance = new StrictMethodKeyBuilder(); } /** @@ -63,7 +66,7 @@ public abstract class MethodKeyBuilder { if (methodKeyBuilder == null) { throw new IllegalArgumentException("methodKeyBuilder can not be null"); } - MethodKeyBuilder.instance = methodKeyBuilder; + instance = methodKeyBuilder; } /** 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 5286cc0..2739e22 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 Map methodCache = new SyncWriteMap(2048, 0.25F); + private static final SyncWriteMap methodCache = new SyncWriteMap(2048, 0.25F); // 初始化在模板中调用 method 时所在的被禁止使用类 static { @@ -94,6 +94,10 @@ public class MethodKit { return forbiddenClasses.contains(clazz); } + public static void addForbiddenClass(Class clazz) { + forbiddenClasses.add(clazz); + } + public static boolean isForbiddenMethod(String methodName) { return forbiddenMethods.contains(methodName); } @@ -109,10 +113,10 @@ public class MethodKit { if (method == null) { method = doGetMethod(key, targetClass, methodName, argTypes); if (method != null) { - methodCache.put(key, method); + methodCache.putIfAbsent(key, method); } else { // 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险 - methodCache.put(key, Boolean.FALSE); + methodCache.putIfAbsent(key, Void.class); } } return method instanceof MethodInfo ? (MethodInfo)method : null; @@ -121,19 +125,19 @@ public class MethodKit { /** * 获取 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.put(key, getterMethod); + methodCache.putIfAbsent(key, getterMethod); } else { - methodCache.put(key, Boolean.FALSE); + methodCache.putIfAbsent(key, Void.class); } } return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null; - } + } */ static Class[] getArgTypes(Object[] argValues) { if (argValues == null || argValues.length == 0) { @@ -280,7 +284,7 @@ public class MethodKit { } MethodInfoExt mie = new MethodInfoExt(objectOfExtensionClass, key, extensionClass/* targetClass */, method); - methodCache.put(key, mie); + methodCache.putIfAbsent(key, mie); } } } 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 0bba784..fb3f434 100644 --- a/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java +++ b/src/main/java/com/jfinal/template/expr/ast/SharedMethod.java @@ -26,9 +26,14 @@ import com.jfinal.template.stat.Scope; * SharedMethod * * 用法: - * engine.addSharedMethod(object); - * engine.addSharedStaticMethod(Xxx.class); - * #(method(para)) + * engine.addSharedMethod(new StrKit()); + * engine.addSharedStaticMethod(MyKit.class); + * + * #if (notBlank(para)) + * .... + * #end + * + * 上面代码中的 notBlank 方法来自 StrKit */ public class SharedMethod extends Expr { @@ -48,14 +53,20 @@ public class SharedMethod extends Expr { public Object eval(Scope scope) { Object[] argValues = exprList.evalExprList(scope); - SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues); - // ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法 - if (sharedMethodInfo == null) { - throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location); - } try { - return sharedMethodInfo.invoke(argValues); + SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues); + if (sharedMethodInfo != null) { + return sharedMethodInfo.invoke(argValues); + } else { + // ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法 + throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location); + } + + } catch (TemplateException e) { + throw e; + } catch (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/SharedMethodKit.java b/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java index d853c76..1904589 100644 --- a/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java @@ -22,7 +22,6 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.HashMap; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import com.jfinal.kit.HashKit; @@ -54,9 +53,9 @@ public class SharedMethodKit { if (method == null) { method = doGetSharedMethodInfo(methodName, argTypes); if (method != null) { - methodCache.put(key, method); + methodCache.putIfAbsent(key, method); } - // shared method 不支持 null safe,不缓存: methodCache.put(key, Boolean.FALSE) + // shared method 不支持 null safe,不缓存: methodCache.putIfAbsent(key, Void.class) } return method; } @@ -174,7 +173,7 @@ public class SharedMethodKit { this.target = target; } - public Object invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public Object invoke(Object... args) throws ReflectiveOperationException { return super.invoke(target, args); } 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 2f0920d..6e950af 100644 --- a/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java +++ b/src/main/java/com/jfinal/template/expr/ast/StaticMethod.java @@ -57,23 +57,25 @@ public class StaticMethod extends Expr { public Object eval(Scope scope) { Object[] argValues = exprList.evalExprList(scope); - MethodInfo methodInfo; - try { - methodInfo = MethodKit.getMethod(clazz, methodName, argValues); - } catch (Exception e) { - throw new TemplateException(e.getMessage(), location, e); - } - - // StaticMethod 是固定的存在,不支持 null safe,null safe 只支持具有动态特征的用法 - if (methodInfo == null) { - throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location); - } - if (!methodInfo.isStatic()) { - throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location); - } try { - return methodInfo.invoke(null, argValues); + MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues); + + if (methodInfo != null) { + if (methodInfo.isStatic()) { + return methodInfo.invoke(null, argValues); + } else { + throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location); + } + } else { + // StaticMethod 是固定的存在,不支持 null safe,null safe 只支持具有动态特征的用法 + throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location); + } + + } catch (TemplateException e) { + throw e; + } catch (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/RenderDirective.java b/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java index 0f8bb75..a2bfb15 100644 --- a/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java @@ -120,7 +120,8 @@ public class RenderDirective extends Directive { } } - subStat.stat.exec(subStat.env, scope, writer); + subStat.exec(null, scope, writer); // subStat.stat.exec(subStat.env, scope, writer); + scope.getCtrl().setJumpNone(); } @@ -138,16 +139,21 @@ public class RenderDirective extends Directive { } } - private static class SubStat { - SubEnv env; - Stat stat; - ISource source; + public static class SubStat extends Stat { + public SubEnv env; + public Stat stat; + public ISource source; - SubStat(SubEnv env, Stat stat, ISource source) { + public SubStat(SubEnv env, Stat stat, ISource source) { this.env = env; this.stat = stat; this.source = source; } + + @Override + public void exec(Env env, Scope scope, Writer writer) { + stat.exec(this.env, scope, writer); + } } /** @@ -160,8 +166,8 @@ public class RenderDirective extends Directive { * * 注意: #render 子模板中定义的模板函数无法在父模板中调用 */ - private static class SubEnv extends Env { - Env parentEnv; + public static class SubEnv extends Env { + public Env parentEnv; public SubEnv(Env parentEnv) { super(parentEnv.getEngineConfig()); @@ -171,6 +177,7 @@ public class RenderDirective extends Directive { /** * 接管父类 getFunction(),先从子模板中找模板函数,找不到再去父模板中找 */ + @Override public Define getFunction(String functionName) { Define func = functionMap.get(functionName); return func != null ? func : parentEnv.getFunction(functionName); diff --git a/src/main/java/com/jfinal/template/io/WriterBuffer.java b/src/main/java/com/jfinal/template/io/WriterBuffer.java index 8bc4b2b..a0332a2 100644 --- a/src/main/java/com/jfinal/template/io/WriterBuffer.java +++ b/src/main/java/com/jfinal/template/io/WriterBuffer.java @@ -22,7 +22,7 @@ package com.jfinal.template.io; public class WriterBuffer { private static final int MIN_BUFFER_SIZE = 64; // 缓冲区最小 64 字节 - private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节 + private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节 private int bufferSize = 2048; // 缓冲区大小 diff --git a/src/main/java/com/jfinal/template/source/ClassPathSource.java b/src/main/java/com/jfinal/template/source/ClassPathSource.java index 87c11cd..4a5ed80 100644 --- a/src/main/java/com/jfinal/template/source/ClassPathSource.java +++ b/src/main/java/com/jfinal/template/source/ClassPathSource.java @@ -105,7 +105,7 @@ public class ClassPathSource implements ISource { return finalFileName; } - public String getKey() { + public String getCacheKey() { return fileName; } diff --git a/src/main/java/com/jfinal/template/source/FileSource.java b/src/main/java/com/jfinal/template/source/FileSource.java index 1c6037b..e4f1813 100644 --- a/src/main/java/com/jfinal/template/source/FileSource.java +++ b/src/main/java/com/jfinal/template/source/FileSource.java @@ -48,7 +48,7 @@ public class FileSource implements ISource { return lastModified != new File(finalFileName).lastModified(); } - public String getKey() { + public String getCacheKey() { return fileName; } diff --git a/src/main/java/com/jfinal/template/source/ISource.java b/src/main/java/com/jfinal/template/source/ISource.java index c453a2e..d2a90a3 100644 --- a/src/main/java/com/jfinal/template/source/ISource.java +++ b/src/main/java/com/jfinal/template/source/ISource.java @@ -27,12 +27,12 @@ public interface ISource { boolean isModified(); /** - * key used to cache, return null if do not cache the template + * cache key used to cache, return null if do not cache the template * * 注意:如果不希望缓存从该 ISource 解析出来的 Template 对象 - * 让 getKey() 返回 null 值即可 + * 让 getCacheKey() 返回 null 值即可 */ - String getKey(); + String getCacheKey(); /** * content of ISource diff --git a/src/main/java/com/jfinal/template/source/StringSource.java b/src/main/java/com/jfinal/template/source/StringSource.java index e8748a9..be0907a 100644 --- a/src/main/java/com/jfinal/template/source/StringSource.java +++ b/src/main/java/com/jfinal/template/source/StringSource.java @@ -25,7 +25,7 @@ import com.jfinal.template.EngineConfig; */ public class StringSource implements ISource { - private String key; + private String cacheKey; private StringBuilder content; /** @@ -38,7 +38,7 @@ public class StringSource implements ISource { throw new IllegalArgumentException("content can not be blank"); } this.content = new StringBuilder(content); - this.key = cache ? HashKit.md5(content) : null; // 不缓存只要将 key 值赋为 null 即可 + this.cacheKey = cache ? HashKit.md5(content) : null; // 不缓存只要将 cacheKey 值赋为 null 即可 } public StringSource(StringBuilder content, boolean cache) { @@ -46,15 +46,15 @@ public class StringSource implements ISource { throw new IllegalArgumentException("content can not be blank"); } this.content = content; - this.key = cache ? HashKit.md5(content.toString()) : null; // 不缓存只要将 key 值赋为 null 即可 + this.cacheKey = cache ? HashKit.md5(content.toString()) : null; // 不缓存只要将 cacheKey 值赋为 null 即可 } public boolean isModified() { return false; } - public String getKey() { - return key; + public String getCacheKey() { + return cacheKey; } public StringBuilder getContent() { @@ -67,8 +67,8 @@ public class StringSource implements ISource { public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Key : ").append(key).append("\n"); - sb.append("Content : ").append(content).append("\n"); + sb.append("cacheKey : ").append(cacheKey).append("\n"); + sb.append("content : ").append(content).append("\n"); return sb.toString(); } } diff --git a/src/main/java/com/jfinal/template/stat/Parser.java b/src/main/java/com/jfinal/template/stat/Parser.java index 5fe605d..788f170 100644 --- a/src/main/java/com/jfinal/template/stat/Parser.java +++ b/src/main/java/com/jfinal/template/stat/Parser.java @@ -90,7 +90,7 @@ public class Parser { tokenList.add(EOF); StatList statList = statList(); if (peek() != EOF) { - throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row)); + throw new ParseException("Syntax error: can not match \"#" + peek().value() + "\"", getLocation(peek().row)); } return statList; } diff --git a/src/main/java/com/jfinal/template/stat/ast/Output.java b/src/main/java/com/jfinal/template/stat/ast/Output.java index e42d27d..0c02081 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Output.java +++ b/src/main/java/com/jfinal/template/stat/ast/Output.java @@ -73,6 +73,8 @@ public class Output extends Stat { } } catch(TemplateException e) { throw e; + } catch(ParseException e) { + throw e; } catch(Exception e) { throw new TemplateException(e.getMessage(), location, e); } 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 f2d7257..fcc9e47 100644 --- a/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java +++ b/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java @@ -27,7 +27,7 @@ import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; /** - * SetLocal 设置全局变量,全局作用域是指本次请求的整个 template + * SetGlobal 设置全局变量,全局作用域是指本次请求的整个 template * * 适用于极少数的在内层作用域中希望直接操作顶层作用域的场景 */