diff --git a/.gitignore b/.gitignore index f8bca96..5323e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ a_little_config_pro.txt dev_plan.txt + + diff --git a/LICENSE b/LICENSE index 37fb606..ff3e6fc 100644 --- a/LICENSE +++ b/LICENSE @@ -192,5 +192,3 @@ third-party archives. - - diff --git a/README.md b/README.md index 1e76765..6759bc5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 227 KB com.jfinal enjoy - 4.2 + 4.3 ``` diff --git a/pom.xml b/pom.xml index 1f77c01..bdda980 100644 --- a/pom.xml +++ b/pom.xml @@ -128,3 +128,5 @@ + + diff --git a/src/main/java/com/jfinal/proxy/ProxyClass.java b/src/main/java/com/jfinal/proxy/ProxyClass.java new file mode 100644 index 0000000..9de162a --- /dev/null +++ b/src/main/java/com/jfinal/proxy/ProxyClass.java @@ -0,0 +1,100 @@ +/** + * 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.proxy; + +import java.util.Map; + +/** + * ProxyClass + */ +public class ProxyClass { + + // 被代理的目标 + private Class target; + + /** + * 以下是代理类信息 + */ + private String pkg; // 包名 + private String name; // 类名 + private String sourceCode; // 源代码 + private Map byteCode; // 字节码 + private Class clazz; // 字节码被 loadClass 后的 Class + // private List proxyMethodList = new ArrayList<>(); + + public ProxyClass(Class target) { + this.target = target; + this.pkg = target.getPackage().getName(); + this.name = target.getSimpleName() + "$$EnhancerByJFinal"; + } + + /** + * 是否需要代理 + */ +// public boolean needProxy() { +// return proxyMethodList.size() > 0; +// } + + public Class getTarget() { + return target; + } + + public String getPkg() { + return pkg; + } + + public String getName() { + return name; + } + + public String getSourceCode() { + return sourceCode; + } + + public void setSourceCode(String sourceCode) { + this.sourceCode = sourceCode; + } + + public Map getByteCode() { + return byteCode; + } + + public void setByteCode(Map byteCode) { + this.byteCode = byteCode; + } + + public Class getClazz() { + return clazz; + } + + public void setClazz(Class clazz) { + this.clazz = clazz; + } + +// public void addProxyMethod(ProxyMethod proxyMethod) { +// proxyMethodList.add(proxyMethod); +// } + +// public List getProxyMethodList() { +// return proxyMethodList; +// } +} + + + + + diff --git a/src/main/java/com/jfinal/proxy/ProxyClassLoader.java b/src/main/java/com/jfinal/proxy/ProxyClassLoader.java new file mode 100644 index 0000000..cccfb87 --- /dev/null +++ b/src/main/java/com/jfinal/proxy/ProxyClassLoader.java @@ -0,0 +1,70 @@ +/** + * 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.proxy; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ProxyClassLoader + */ +public class ProxyClassLoader extends ClassLoader { + + protected Map byteCodeMap = new ConcurrentHashMap<>(); + + static { + registerAsParallelCapable(); + } + + public ProxyClassLoader() { + super(getParentClassLoader()); + } + + protected static ClassLoader getParentClassLoader() { + ClassLoader ret = Thread.currentThread().getContextClassLoader(); + return ret != null ? ret : ProxyClassLoader.class.getClassLoader(); + } + + public Class loadProxyClass(ProxyClass proxyClass) { + for (Entry e : proxyClass.getByteCode().entrySet()) { + byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); + } + + try { + return loadClass(proxyClass.getPkg() + "." + proxyClass.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] bytes = byteCodeMap.get(name); + if (bytes != null) { + Class ret = defineClass(name, bytes, 0, bytes.length); + byteCodeMap.remove(name); + return ret; + } + + return super.findClass(name); + } +} + + + + diff --git a/src/main/java/com/jfinal/proxy/ProxyCompiler.java b/src/main/java/com/jfinal/proxy/ProxyCompiler.java new file mode 100644 index 0000000..6ac6c01 --- /dev/null +++ b/src/main/java/com/jfinal/proxy/ProxyCompiler.java @@ -0,0 +1,238 @@ +/** + * 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.proxy; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import javax.tools.DiagnosticCollector; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +// import com.jfinal.log.Log; + +/** + * ProxyCompiler + * + * https://www.programcreek.com/java-api-examples/?api=javax.tools.JavaCompiler + */ +public class ProxyCompiler { + + // protected static final Log log = Log.getLog(ProxyCompiler.class); + + // protected List options = Arrays.asList("-target", "1.8" /*, "-parameters"*/); + protected volatile List options = null; + + protected List getOptions() { + if (options != null) { + return options; + } + + synchronized (this) { + if (options != null) { + return options; + } + + List ret = new ArrayList<>(); + ret.add("-target"); + ret.add("1.8"); + + String cp = getClassPath(); + if (cp != null && cp.trim().length() != 0) { + ret.add("-classpath"); + ret.add(cp); + } + + options = ret; + return options; + } + } + + /** + * 兼容 tomcat 丢失 class path,否则无法编译 + */ + protected String getClassPath() { + URLClassLoader classLoader = getURLClassLoader(); + if (classLoader == null) { + return null; + } + + int index = 0; + boolean isWindows = isWindows(); + StringBuilder ret = new StringBuilder(); + for (URL url : classLoader.getURLs()) { + if (index++ > 0) { + ret.append(File.pathSeparator); + } + + String path = url.getFile(); + + // 如果是 windows 系统,去除前缀字符 '/' + if (isWindows && path.startsWith("/")) { + path = path.substring(1); + } + + // 去除后缀字符 '/' + if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) { + path = path.substring(0, path.length() - 1); + } + + ret.append(path); + } + + return ret.toString(); + } + + protected boolean isWindows() { + String osName = System.getProperty("os.name", "unknown"); + return osName.toLowerCase().indexOf("windows") != -1; + } + + protected URLClassLoader getURLClassLoader() { + ClassLoader ret = Thread.currentThread().getContextClassLoader(); + if (ret == null) { + ret = ProxyCompiler.class.getClassLoader(); + } + return (ret instanceof URLClassLoader) ? (URLClassLoader)ret : null; + } + + 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"); + } + + DiagnosticCollector collector = new DiagnosticCollector<>(); + try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) { + + MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode()); + Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call(); + outputCompileError(result, collector); + + Map ret = new HashMap<>(); + for (Entry e : javaFileManager.fileObjects.entrySet()) { + ret.put(e.getKey(), e.getValue().getByteCode()); + } + + proxyClass.setByteCode(ret); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected void outputCompileError(Boolean result, DiagnosticCollector collector) { + if (! result) { + // collector.getDiagnostics().forEach(item -> log.error(item.toString())); + collector.getDiagnostics().forEach(item -> System.out.println(item.toString())); + } + } + + public ProxyCompiler setCompileOptions(List options) { + Objects.requireNonNull(options, "options can not be null"); + this.options = options; + return this; + } + + public ProxyCompiler addCompileOption(String option) { + Objects.requireNonNull(option, "option can not be null"); + options.add(option); + return this; + } + + public static class MyJavaFileObject extends SimpleJavaFileObject { + + private String source; + private ByteArrayOutputStream outPutStream; + + public MyJavaFileObject(String name, String source) { + super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE); + this.source = source; + } + + public MyJavaFileObject(String name, Kind kind) { + super(URI.create("String:///" + name + kind.extension), kind); + source = null; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + if (source == null) { + throw new IllegalStateException("source field can not be null"); + } + return source; + } + + @Override + public OutputStream openOutputStream() throws IOException { + outPutStream = new ByteArrayOutputStream(); + return outPutStream; + } + + public byte[] getByteCode() { + return outPutStream.toByteArray(); + } + } + + public static class MyJavaFileManager extends ForwardingJavaFileManager { + + public Map fileObjects = new HashMap<>(); + + public MyJavaFileManager(JavaFileManager fileManager) { + super(fileManager); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind); + fileObjects.put(qualifiedClassName, javaFileObject); + return javaFileObject; + } + + // 是否在编译时依赖另一个类的情况下用到本方法 ? + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { + JavaFileObject javaFileObject = fileObjects.get(className); + if (javaFileObject == null) { + javaFileObject = super.getJavaFileForInput(location, className, kind); + } + return javaFileObject; + } + } +} + + + + + + + + diff --git a/src/main/java/com/jfinal/template/Engine.java b/src/main/java/com/jfinal/template/Engine.java index e137755..96fdd17 100644 --- a/src/main/java/com/jfinal/template/Engine.java +++ b/src/main/java/com/jfinal/template/Engine.java @@ -279,6 +279,11 @@ public class Engine { return this; } + public Engine removeSharedObject(String name) { + config.removeSharedObject(name); + return this; + } + /** * Set output directive factory */ @@ -521,10 +526,10 @@ public class Engine { * * 系统当前默认 FieldGetter 实现类及其位置如下: * GetterMethodFieldGetter ---> 调用 getter 方法取值 + * RealFieldGetter ---> 直接获取 public 型的 object.field 值 * ModelFieldGetter ---> 调用 Model.get(String) 方法取值 * RecordFieldGetter ---> 调用 Record.get(String) 方法取值 - * MapFieldGetter ---> 调用 Map.get(String) 方法取值 - * RealFieldGetter ---> 直接获取 public 型的 object.field 值 + * MapFieldGetter ---> 调用 Map.get(String) 方法取值 * ArrayLengthGetter ---> 获取数组长度 * * 根据以上次序,如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter @@ -551,8 +556,19 @@ public class Engine { FieldKit.removeFieldGetter(fieldGetterClass); } - public static void setToFastFieldKeyBuilder() { - FieldKeyBuilder.setToFastFieldKeyBuilder(); + public static void setFastFieldKeyBuilder(boolean enable) { + FieldKeyBuilder.setFastFieldKeyBuilder(enable); + } + + /** + * 设置极速模式 + * + * 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用, + * 性能提升 12.9% + */ + public static void setFastMode(boolean fastMode) { + FieldKit.setFastMode(fastMode); + FieldKeyBuilder.setFastFieldKeyBuilder(fastMode); } } diff --git a/src/main/java/com/jfinal/template/EngineConfig.java b/src/main/java/com/jfinal/template/EngineConfig.java index f5e096a..214f5f3 100644 --- a/src/main/java/com/jfinal/template/EngineConfig.java +++ b/src/main/java/com/jfinal/template/EngineConfig.java @@ -209,10 +209,16 @@ public class EngineConfig { sharedObjectMap.put(name, object); } - Map getSharedObjectMap() { + public Map getSharedObjectMap() { return sharedObjectMap; } + public synchronized void removeSharedObject(String name) { + if (sharedObjectMap != null) { + sharedObjectMap.remove(name); + } + } + /** * Set output directive factory */ diff --git a/src/main/java/com/jfinal/template/Template.java b/src/main/java/com/jfinal/template/Template.java index c76d95d..7d01b96 100644 --- a/src/main/java/com/jfinal/template/Template.java +++ b/src/main/java/com/jfinal/template/Template.java @@ -108,7 +108,7 @@ public class Template { public StringBuilder renderToStringBuilder(Map data) { FastStringWriter fsw = new FastStringWriter(); render(data, fsw); - return fsw.getBuffer(); + return fsw.toStringBuilder(); } /** diff --git a/src/main/java/com/jfinal/template/expr/ast/FastFieldGetter.java b/src/main/java/com/jfinal/template/expr/ast/FastFieldGetter.java new file mode 100644 index 0000000..997827b --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/FastFieldGetter.java @@ -0,0 +1,181 @@ +package com.jfinal.template.expr.ast; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import com.jfinal.kit.StrKit; +import com.jfinal.proxy.ProxyClassLoader; + +/** + * 使用 jfinal proxy 机制消除 java.lang.reflect.Method.invoke(...) + * 提升性能,并且同时支持动态类型的 field 表达式取值 + */ +public class FastFieldGetter extends FieldGetter { + + protected static ProxyGenerator generator = new ProxyGenerator(); + protected static ProxyCompiler compiler = new ProxyCompiler(); + protected static ProxyClassLoader classLoader = new ProxyClassLoader(); + protected static Map, Proxy> cache = new ConcurrentHashMap<>(512, 0.25F); + + protected static boolean outputCompileError = false; + + protected Proxy proxy; + protected java.lang.reflect.Method getterMethod; + + public FastFieldGetter(Proxy proxy, java.lang.reflect.Method getterMethod) { + this.proxy = proxy; + this.getterMethod = getterMethod; + } + + /** + * 仅用于配置 Engine.addFieldGetter(0, new FastFieldGetter()); + */ + public FastFieldGetter() { + this(null, null); + } + + 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.getParameterCount() == 0) { + + Proxy proxy = cache.get(targetClass); + if (proxy == null) { + synchronized (targetClass) { + proxy = cache.get(targetClass); + if (proxy == null) { + try { + proxy = createProxy(targetClass, fieldName); + } catch (Throwable e) { + return null; + } + cache.putIfAbsent(targetClass, proxy); + } + } + } + + return new FastFieldGetter(proxy, method); + } + } + + return null; + } + + public Object get(Object target, String fieldName) throws Exception { + // return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY); + return proxy.getValue(target, fieldName); + } + + protected Proxy createProxy(Class targetClass, String fieldName) { + ProxyClass proxyClass = new ProxyClass(targetClass); + String sourceCode = generator.generate(proxyClass); + // System.out.println(sourceCode); + + proxyClass.setSourceCode(sourceCode); + compiler.compile(proxyClass); + Class retClass = classLoader.loadProxyClass(proxyClass); + proxyClass.setClazz(retClass); + try { + return (Proxy)retClass.newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public String toString() { + return getterMethod.toString(); + } + + // --------- + + /** + * 代理接口 + */ + public static interface Proxy { + public Object getValue(Object target, String fieldName); + } + + // --------- + + /** + * 代理类 + */ + static class ProxyClass extends com.jfinal.proxy.ProxyClass { + + private String name; // 类名 + + public ProxyClass(Class target) { + super(target); + + name = target.getSimpleName() + "$$EnhancerByJFinal_FieldGetter"; + } + + public String getName() { + return name; + } + } + + // --------- + + /** + * 代理生成器 + */ + static class ProxyGenerator { + + String generate(ProxyClass proxyClass) { + StringBuilder ret = new StringBuilder(1024); + + Class targetClass = proxyClass.getTarget(); + String className = proxyClass.getName(); + + ret.append("package ").append(proxyClass.getPkg()).append(";\n\n"); + ret.append("import com.jfinal.template.expr.ast.FastFieldGetter.Proxy;\n\n"); + ret.append("public class ").append(className).append(" implements Proxy {\n\n"); + ret.append("\tpublic Object getValue(Object target, String fieldName) {\n"); + ret.append("\t\tint hash = fieldName.hashCode();\n"); + ret.append("\t\tswitch (hash) {\n"); + + java.lang.reflect.Method[] methodArray = targetClass.getMethods(); + for (java.lang.reflect.Method method : methodArray) { + String mn = method.getName(); + if (method.getParameterCount() == 0 && mn.startsWith("get") && (!mn.equals("getClass"))) { + String fieldName = StrKit.firstCharToLowerCase(mn.substring(3)); + ret.append("\t\tcase ").append(fieldName.hashCode()).append(" :\n"); + ret.append("\t\t\treturn ((").append(targetClass.getName()).append(")target).").append(mn).append("();\n"); + } + } + + ret.append("\t\tdefault :\n"); + ret.append("\t\t\tthrow new RuntimeException(\"Can not access the field \\\"\" + target.getClass().getName() + \".\" + fieldName + \"\\\"\");\n"); + + ret.append("\t\t}\n"); + ret.append("\t}\n"); + ret.append("}\n"); + + return ret.toString(); + } + } + + // --------- + + public static void setOutputCompileError(boolean outputCompileError) { + FastFieldGetter.outputCompileError = outputCompileError; + } + + /** + * 代理编译器 + */ + static class ProxyCompiler extends com.jfinal.proxy.ProxyCompiler { + @Override + protected void outputCompileError(Boolean result, javax.tools.DiagnosticCollector collector) { + if (outputCompileError) { + super.outputCompileError(result, collector); + } + } + } +} + 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 dc3d806..69cd851 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldGetters.java @@ -68,7 +68,7 @@ public class FieldGetters { 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 (method.getName().equals(getterName) && method.getParameterCount() == 0) { // if (MethodKit.isForbiddenMethod(getterName)) { // throw new RuntimeException("Forbidden method: " + getterName); // } @@ -115,7 +115,7 @@ public class FieldGetters { 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) { + if (method.getName().equals(isMethodName) && method.getParameterCount() == 0) { Class returnType = method.getReturnType(); if (returnType == Boolean.class || returnType == boolean.class) { return new IsMethodFieldGetter(method); diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java b/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java index 6231139..bfc924b 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java @@ -33,10 +33,14 @@ public abstract class FieldKeyBuilder { } /** - * 设置为官方提供的 FastFieldKeyBuilder 实现,性能更高 + * 开启 FastFieldKeyBuilder,性能更高 */ - public static void setToFastFieldKeyBuilder() { - instance = new FastFieldKeyBuilder(); + public static void setFastFieldKeyBuilder(boolean enable) { + if (enable) { + instance = new FastFieldKeyBuilder(); + } else { + instance = new StrictFieldKeyBuilder(); + } } /** 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 fab3fc7..8ce0c43 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java @@ -140,6 +140,36 @@ public class FieldKit { public static void clearCache() { fieldGetterCache.clear(); } + + /** + * 设置极速模式 + * + * 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用, + * 性能提升 12.9% + */ + public static synchronized void setFastMode(boolean fastMode) { + if (fastMode) { + if ( !contains(FastFieldGetter.class) ) { + addFieldGetterToFirst(new FastFieldGetter()); + } + } else { + if (contains(FastFieldGetter.class)) { + removeFieldGetter(FastFieldGetter.class); + } + } + } + + /** + * 判断是否包含某个 FieldGetter + */ + public static boolean contains(Class fieldGetterClass) { + for (FieldGetter fg : getters) { + if (fg.getClass() == fieldGetterClass) { + return true; + } + } + return false; + } } diff --git a/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java b/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java index 1940d98..92ffb64 100644 --- a/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java @@ -16,9 +16,12 @@ package com.jfinal.template.ext.directive; +import java.io.IOException; import com.jfinal.template.Directive; import com.jfinal.template.Env; +import com.jfinal.template.TemplateException; import com.jfinal.template.io.Writer; +import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; /** @@ -29,45 +32,67 @@ import com.jfinal.template.stat.Scope; public class EscapeDirective extends Directive { public void exec(Env env, Scope scope, Writer writer) { - Object value = exprList.eval(scope); - if (value != null) { - write(writer, escape(value.toString())); + try { + Object value = exprList.eval(scope); + + if (value instanceof String) { + escape((String)value, writer); + } else if (value instanceof Number) { + Class c = value.getClass(); + if (c == Integer.class) { + writer.write((Integer)value); + } else if (c == Long.class) { + writer.write((Long)value); + } else if (c == Double.class) { + writer.write((Double)value); + } else if (c == Float.class) { + writer.write((Float)value); + } else if (c == Short.class) { + writer.write((Short)value); + } else { + writer.write(value.toString()); + } + } else if (value != null) { + escape(value.toString(), writer); + } + } catch(TemplateException | ParseException e) { + throw e; + } catch(Exception e) { + throw new TemplateException(e.getMessage(), location, e); } } - // TODO 挪到 StrKit 中 - private String escape(String str) { - if (str == null || str.length() == 0) { - return str; + private void escape(String str, Writer w) throws IOException { + int len = str.length(); + if (len == 0) { + return ; } - int len = str.length(); - StringBuilder ret = new StringBuilder(len * 2); for (int i = 0; i < len; i++) { char cur = str.charAt(i); switch (cur) { case '<': - ret.append("<"); + w.write("<"); break; case '>': - ret.append(">"); + w.write(">"); break; case '"': - ret.append("""); + w.write("""); break; case '\'': - // ret.append("'"); // IE 不支持 ' 考虑 ' - ret.append("'"); + // w.write("'"); // IE 不支持 ' 考虑 ' + w.write("'"); break; case '&': - ret.append("&"); + w.write("&"); break; default: - ret.append(cur); + w.write(str, i, 1); break; } } - - return ret.toString(); } } + + diff --git a/src/main/java/com/jfinal/template/io/FastStringWriter.java b/src/main/java/com/jfinal/template/io/FastStringWriter.java index 8eb6bb7..85e47e5 100644 --- a/src/main/java/com/jfinal/template/io/FastStringWriter.java +++ b/src/main/java/com/jfinal/template/io/FastStringWriter.java @@ -16,102 +16,153 @@ package com.jfinal.template.io; +import java.io.IOException; import java.io.Writer; /** * FastStringWriter * *
- * 由 JDK 中 StringWriter 改造而来,在其基础之上做了如下改变:
- * 1:StringBuffer 属性改为 StringBuilder,避免了前者的 synchronized 操作
- * 2:添加了 MAX_SIZE 属性
- * 3:去掉了 close() 方法声明中的 throws IOException,并添加了代码,原先该方法中无任何代码
+ * 由 JDK 中 Writer 改造而来,在其基础之上做了如下改变:
+ * 1:添加 char[] value 直接保存 char 值
+ * 2:添加 int len 记录数据长度
+ * 3:去掉 synchronized 操作
+ * 4:添加 MAX_BUFFER_SIZE,限定 value 被重用的最大长度
+ * 5:去掉了 close() 方法声明中的 throws IOException,并添加缓存回收逻辑
  * 
*/ public class FastStringWriter extends Writer { - private StringBuilder buf; + private char[] value; + private int len; - public FastStringWriter() { - buf = new StringBuilder(); - } - - public FastStringWriter(int initialSize) { - if (initialSize < 0) { - throw new IllegalArgumentException("Negative buffer size"); - } - buf = new StringBuilder(initialSize); - } - - public void write(int c) { - buf.append((char) c); - } - - public void write(char cbuf[], int off, int len) { - if ((off < 0) || (off > cbuf.length) || (len < 0) || - ((off + len) > cbuf.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - buf.append(cbuf, off, len); - } - - public void write(String str) { - buf.append(str); - } - - public void write(String str, int off, int len) { - buf.append(str.substring(off, off + len)); - } - - public FastStringWriter append(CharSequence csq) { - if (csq == null) { - write("null"); - } else { - write(csq.toString()); - } - return this; - } - - public FastStringWriter append(CharSequence csq, int start, int end) { - CharSequence cs = (csq == null ? "null" : csq); - write(cs.subSequence(start, end).toString()); - return this; - } - - public FastStringWriter append(char c) { - write(c); - return this; - } - - public String toString() { - return buf.toString(); - } - - public StringBuilder getBuffer() { - return buf; - } - - public void flush() { - - } - - static int MAX_SIZE = 1024 * 64; - - /** - * 由 StringWriter.close() 改造而来,原先该方法中无任何代码 ,改造如下: - * 1:去掉 throws IOException - * 2:添加 buf 空间释放处理逻辑 - * 3:添加 buf.setLength(0),以便于配合 ThreadLocal 回收利用 - */ - public void close() { - if (buf.length() > MAX_SIZE) { - buf = new StringBuilder(MAX_SIZE / 2); // 释放空间占用过大的 buf - } else { - buf.setLength(0); + private static int MAX_BUFFER_SIZE = 1024 * 256; // 1024 * 64; + + public static void setMaxBufferSize(int maxBufferSize) { + int min = 256; + if (maxBufferSize < min) { + throw new IllegalArgumentException("maxBufferSize must more than " + min); } - } + MAX_BUFFER_SIZE = maxBufferSize; + } + + @Override + public void close() /* throws IOException */ { + len = 0; + + // 释放空间占用过大的缓存 + if (value.length > MAX_BUFFER_SIZE) { + value = new char[Math.max(256, MAX_BUFFER_SIZE / 2)]; + } + } + + public String toString() { + return new String(value, 0, len); + } + + public StringBuilder toStringBuilder() { + return new StringBuilder(len + 64).append(value, 0, len); + } + + public FastStringWriter(int capacity) { + value = new char[capacity]; + } + + public FastStringWriter() { + this(128); + } + + /** + * 扩容 + */ + protected void expandCapacity(int newLen) { + int newCapacity = Math.max(newLen, value.length * 2); + char[] newValue = new char[newCapacity]; + + // 复制 value 中的值到 newValue + if (len > 0) { + System.arraycopy(value, 0, newValue, 0, len); + } + value = newValue; + } + + @Override + public void write(char buffer[], int offset, int len) throws IOException { + int newLen = this.len + len; + if (newLen > value.length) { + expandCapacity(newLen); + } + + System.arraycopy(buffer, offset, value, this.len, len); + this.len = newLen; + } + + @Override + public void write(String str, int offset, int len) throws IOException { + int newLen = this.len + len; + if (newLen > value.length) { + expandCapacity(newLen); + } + + str.getChars(offset, (offset + len), value, this.len); + this.len = newLen; + } + + @Override + public void write(int c) throws IOException { + char[] buffer = {(char)c}; + write(buffer, 0, 1); + } + + @Override + public void write(char buffer[]) throws IOException { + write(buffer, 0, buffer.length); + } + + @Override + public void write(String str) throws IOException { + write(str, 0, str.length()); + } + + @Override + public Writer append(CharSequence csq) throws IOException { + if (csq instanceof String) { + String str = (String)csq; + write(str, 0, str.length()); + return this; + } + + if (csq == null) + write("null"); + else + write(csq.toString()); + return this; + } + + @Override + public Writer append(CharSequence csq, int start, int end) throws IOException { + if (csq instanceof String) { + String str = (String)csq; + write(str, start, (end - start)); + return this; + } + + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + @Override + public Writer append(char c) throws IOException { + char[] buffer = {c}; + write(buffer, 0, 1); + return this; + } + + @Override + public void flush() throws IOException { + + } } 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 dd2bfb9..b2c0e4d 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Output.java +++ b/src/main/java/com/jfinal/template/stat/ast/Output.java @@ -66,8 +66,6 @@ public class Output extends Stat { } else { writer.write(value.toString()); } - } else if (value instanceof Boolean) { - writer.write((Boolean)value); } else if (value != null) { writer.write(value.toString()); }