From 61aa1d20826a0a1dcab4d5fbd8e56674c5eeb8c2 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 21 Nov 2017 22:43:34 +0800 Subject: [PATCH] enjoy 3.3 release ^_^ --- pom.xml | 4 +- src/main/java/com/jfinal/kit/ElKit.java | 6 +- src/main/java/com/jfinal/kit/HashKit.java | 12 + src/main/java/com/jfinal/template/Engine.java | 23 +- .../com/jfinal/template/EngineConfig.java | 47 +- src/main/java/com/jfinal/template/Env.java | 4 + .../java/com/jfinal/template/Template.java | 55 +- .../com/jfinal/template/expr/ExprParser.java | 18 +- .../com/jfinal/template/expr/ast/Compare.java | 10 + .../com/jfinal/template/expr/ast/Const.java | 8 +- .../jfinal/template/expr/ast/ExprList.java | 55 +- .../com/jfinal/template/expr/ast/Field.java | 11 +- .../jfinal/template/expr/ast/FieldKit.java | 16 +- .../com/jfinal/template/expr/ast/ForCtrl.java | 6 +- .../com/jfinal/template/expr/ast/Logic.java | 69 +- .../jfinal/template/expr/ast/MethodInfo.java | 6 +- .../template/expr/ast/MethodInfoExt.java | 2 +- .../template/expr/ast/MethodKeyBuilder.java | 132 ++ .../jfinal/template/expr/ast/MethodKit.java | 37 +- .../jfinal/template/expr/ast/NullExpr.java | 36 + .../jfinal/template/expr/ast/NullSafe.java | 10 +- .../template/expr/ast/SharedMethodKit.java | 41 +- .../template/ext/directive/DateDirective.java | 52 +- .../ext/directive/EscapeDirective.java | 2 +- .../template/ext/directive/NowDirective.java | 18 +- .../ext/directive/NumberDirective.java | 108 ++ .../ext/directive/RandomDirective.java | 2 +- .../ext/directive/RenderDirective.java | 9 +- .../ext/directive/StringDirective.java | 13 +- .../ext/sharedmethod/SharedMethodLib.java | 81 + .../template/ext/spring/JFinalView.java | 7 +- .../ext/spring/JFinalViewResolver.java | 63 +- .../com/jfinal/template/io/ByteWriter.java | 122 ++ .../com/jfinal/template/io/CharWriter.java | 117 ++ .../com/jfinal/template/io/DateFormats.java | 44 + .../java/com/jfinal/template/io/Encoder.java | 28 + .../jfinal/template/io/EncoderFactory.java | 44 + .../template/{ => io}/FastStringWriter.java | 27 +- .../jfinal/template/io/FloatingDecimal.java | 1306 +++++++++++++++++ .../jfinal/template/io/FloatingWriter.java | 67 + .../com/jfinal/template/io/IWritable.java | 38 + .../com/jfinal/template/io/IntegerWriter.java | 126 ++ .../com/jfinal/template/io/JdkEncoder.java | 65 + .../com/jfinal/template/io/LongWriter.java | 105 ++ .../com/jfinal/template/io/Utf8Encoder.java | 98 ++ .../java/com/jfinal/template/io/Writer.java | 68 + .../com/jfinal/template/io/WriterBuffer.java | 86 ++ .../java/com/jfinal/template/stat/Lexer.java | 28 +- .../java/com/jfinal/template/stat/Parser.java | 21 +- .../java/com/jfinal/template/stat/Scope.java | 27 +- .../com/jfinal/template/stat/ast/Break.java | 2 +- .../com/jfinal/template/stat/ast/Call.java | 2 +- .../jfinal/template/stat/ast/Continue.java | 2 +- .../com/jfinal/template/stat/ast/Define.java | 6 +- .../com/jfinal/template/stat/ast/Else.java | 6 +- .../com/jfinal/template/stat/ast/ElseIf.java | 13 +- .../com/jfinal/template/stat/ast/For.java | 10 +- .../jfinal/template/stat/ast/ForEntry.java | 2 +- .../template/stat/ast/ForIteratorStatus.java | 14 +- .../java/com/jfinal/template/stat/ast/If.java | 11 +- .../com/jfinal/template/stat/ast/Include.java | 4 +- .../jfinal/template/stat/ast/NullStat.java | 40 + .../com/jfinal/template/stat/ast/Output.java | 33 +- .../com/jfinal/template/stat/ast/Return.java | 2 +- .../com/jfinal/template/stat/ast/Set.java | 8 +- .../jfinal/template/stat/ast/SetGlobal.java | 8 +- .../jfinal/template/stat/ast/SetLocal.java | 8 +- .../com/jfinal/template/stat/ast/Stat.java | 15 +- .../jfinal/template/stat/ast/StatList.java | 30 +- .../com/jfinal/template/stat/ast/Text.java | 81 +- 70 files changed, 3378 insertions(+), 299 deletions(-) create mode 100644 src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java create mode 100644 src/main/java/com/jfinal/template/expr/ast/NullExpr.java create mode 100644 src/main/java/com/jfinal/template/ext/directive/NumberDirective.java create mode 100644 src/main/java/com/jfinal/template/ext/sharedmethod/SharedMethodLib.java create mode 100644 src/main/java/com/jfinal/template/io/ByteWriter.java create mode 100644 src/main/java/com/jfinal/template/io/CharWriter.java create mode 100644 src/main/java/com/jfinal/template/io/DateFormats.java create mode 100644 src/main/java/com/jfinal/template/io/Encoder.java create mode 100644 src/main/java/com/jfinal/template/io/EncoderFactory.java rename src/main/java/com/jfinal/template/{ => io}/FastStringWriter.java (70%) create mode 100644 src/main/java/com/jfinal/template/io/FloatingDecimal.java create mode 100644 src/main/java/com/jfinal/template/io/FloatingWriter.java create mode 100644 src/main/java/com/jfinal/template/io/IWritable.java create mode 100644 src/main/java/com/jfinal/template/io/IntegerWriter.java create mode 100644 src/main/java/com/jfinal/template/io/JdkEncoder.java create mode 100644 src/main/java/com/jfinal/template/io/LongWriter.java create mode 100644 src/main/java/com/jfinal/template/io/Utf8Encoder.java create mode 100644 src/main/java/com/jfinal/template/io/Writer.java create mode 100644 src/main/java/com/jfinal/template/io/WriterBuffer.java create mode 100644 src/main/java/com/jfinal/template/stat/ast/NullStat.java diff --git a/pom.xml b/pom.xml index db84048..3a9886d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ enjoy jar enjoy - 3.3-SNAPSHOT + 3.3 http://www.jfinal.com Enjoy is a simple, light, rapid, independent, extensible Java Template Engine. @@ -121,7 +121,7 @@ - false + true diff --git a/src/main/java/com/jfinal/kit/ElKit.java b/src/main/java/com/jfinal/kit/ElKit.java index 4a0fbfa..7e23ed0 100644 --- a/src/main/java/com/jfinal/kit/ElKit.java +++ b/src/main/java/com/jfinal/kit/ElKit.java @@ -16,12 +16,12 @@ package com.jfinal.kit; -import java.io.Writer; import java.util.Map; import com.jfinal.template.Directive; import com.jfinal.template.Engine; import com.jfinal.template.Env; import com.jfinal.template.Template; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** @@ -42,7 +42,7 @@ public class ElKit { private static final String RETURN_VALUE_KEY = "_RETURN_VALUE_"; static { - engine.addDirective("eval", new InnerEvalDirective()); + engine.addDirective("eval", InnerEvalDirective.class); } public Engine getEngine() { @@ -57,7 +57,7 @@ public class ElKit { public static T eval(String expr, Map data) { String stringTemplate = "#eval(" + expr + ")"; Template template = engine.getTemplateByString(stringTemplate); - template.render(data, null); + template.render(data, (java.io.Writer)null); return (T)data.get(RETURN_VALUE_KEY); } diff --git a/src/main/java/com/jfinal/kit/HashKit.java b/src/main/java/com/jfinal/kit/HashKit.java index 4dee65e..10a3b04 100644 --- a/src/main/java/com/jfinal/kit/HashKit.java +++ b/src/main/java/com/jfinal/kit/HashKit.java @@ -20,10 +20,22 @@ import java.security.MessageDigest; public class HashKit { + public static final long FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325L; + public static final long FNV_PRIME_64 = 0x100000001b3L; + private static final java.security.SecureRandom random = new java.security.SecureRandom(); private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); private static final char[] CHAR_ARRAY = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); + public static long fnv1a64(String key) { + long hash = FNV_OFFSET_BASIS_64; + for(int i=0, size=key.length(); i + * 示例: + * addDirective("now", NowDirective.class) + * */ - public Engine addDirective(String directiveName, Directive directive) { - config.addDirective(directiveName, directive); + public Engine addDirective(String directiveName, Class directiveClass) { + config.addDirective(directiveName, directiveClass); return this; } + /** + * 该方法已被 addDirective(String, Class) 所代替 + */ + @Deprecated + public Engine addDirective(String directiveName, Directive directive) { + return addDirective(directiveName, directive.getClass()); + } + /** * Remove directive */ @@ -445,6 +457,11 @@ public class Engine { return config.getEncoding(); } + public Engine setWriterBufferSize(int bufferSize) { + config.setWriterBufferSize(bufferSize); + return this; + } + /** * Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效, * 但如果在 devMode 之下并不希望对 addSharedFunction(...), @@ -466,7 +483,7 @@ public class Engine { } public static void removeExtensionMethod(Class targetClass, Object objectOfExtensionClass) { - MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);; + MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass); } public static void removeExtensionMethod(Class targetClass, Class extensionClass) { diff --git a/src/main/java/com/jfinal/template/EngineConfig.java b/src/main/java/com/jfinal/template/EngineConfig.java index dcb9a61..0979836 100644 --- a/src/main/java/com/jfinal/template/EngineConfig.java +++ b/src/main/java/com/jfinal/template/EngineConfig.java @@ -26,7 +26,9 @@ import com.jfinal.kit.StrKit; import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.SharedMethodKit; import com.jfinal.template.ext.directive.*; -// import com.jfinal.template.ext.sharedmethod.Json; +import com.jfinal.template.ext.sharedmethod.SharedMethodLib; +import com.jfinal.template.io.EncoderFactory; +import com.jfinal.template.io.WriterBuffer; import com.jfinal.template.source.FileSource; import com.jfinal.template.source.FileSourceFactory; import com.jfinal.template.source.ISource; @@ -36,7 +38,6 @@ import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Parser; import com.jfinal.template.stat.ast.Define; import com.jfinal.template.stat.ast.Output; -import com.jfinal.template.stat.ast.Stat; /** * EngineConfig @@ -45,6 +46,8 @@ public class EngineConfig { public static final String DEFAULT_ENCODING = "UTF-8"; + WriterBuffer writerBuffer = new WriterBuffer(); + private Map sharedFunctionMap = new HashMap(); private List sharedFunctionSourceList = new ArrayList(); // for devMode only @@ -52,7 +55,7 @@ public class EngineConfig { private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me; private ISourceFactory sourceFactory = new FileSourceFactory(); - private Map directiveMap = new HashMap(); + private Map> directiveMap = new HashMap>(); private SharedMethodKit sharedMethodKit = new SharedMethodKit(); private boolean devMode = false; @@ -63,14 +66,16 @@ public class EngineConfig { public EngineConfig() { // Add official directive of Template Engine - addDirective("render", new RenderDirective()); - addDirective("date", new DateDirective()); - addDirective("escape", new EscapeDirective()); - addDirective("string", new StringDirective()); - addDirective("random", new RandomDirective()); + addDirective("render", RenderDirective.class); + addDirective("date", DateDirective.class); + addDirective("escape", EscapeDirective.class); + addDirective("string", StringDirective.class); + addDirective("random", RandomDirective.class); + addDirective("number", NumberDirective.class); // Add official shared method of Template Engine // addSharedMethod(new Json()); + addSharedMethod(new SharedMethodLib()); } /** @@ -268,6 +273,17 @@ public class EngineConfig { throw new IllegalArgumentException("encoding can not be blank"); } this.encoding = encoding; + + writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding + } + + public void setEncoderFactory(EncoderFactory encoderFactory) { + writerBuffer.setEncoderFactory(encoderFactory); + writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding + } + + public void setWriterBufferSize(int bufferSize) { + writerBuffer.setBufferSize(bufferSize); } public String getEncoding() { @@ -289,20 +305,25 @@ public class EngineConfig { this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode; } - public synchronized void addDirective(String directiveName, Directive directive) { + @Deprecated + public void addDirective(String directiveName, Directive directive) { + addDirective(directiveName, directive.getClass()); + } + + public synchronized void addDirective(String directiveName, Class directiveClass) { if (StrKit.isBlank(directiveName)) { throw new IllegalArgumentException("directive name can not be blank"); } - if (directive == null) { - throw new IllegalArgumentException("directive can not be null"); + if (directiveClass == null) { + throw new IllegalArgumentException("directiveClass can not be null"); } if (directiveMap.containsKey(directiveName)) { throw new IllegalArgumentException("directive already exists : " + directiveName); } - directiveMap.put(directiveName, directive); + directiveMap.put(directiveName, directiveClass); } - public Stat getDirective(String directiveName) { + public Class getDirective(String directiveName) { return directiveMap.get(directiveName); } diff --git a/src/main/java/com/jfinal/template/Env.java b/src/main/java/com/jfinal/template/Env.java index a92c6eb..a298db5 100644 --- a/src/main/java/com/jfinal/template/Env.java +++ b/src/main/java/com/jfinal/template/Env.java @@ -48,6 +48,10 @@ public class Env { return engineConfig; } + public boolean isDevMode() { + return engineConfig.isDevMode(); + } + /** * Add template function */ diff --git a/src/main/java/com/jfinal/template/Template.java b/src/main/java/com/jfinal/template/Template.java index 9e1a9e1..80ca56a 100644 --- a/src/main/java/com/jfinal/template/Template.java +++ b/src/main/java/com/jfinal/template/Template.java @@ -16,8 +16,12 @@ package com.jfinal.template; +import java.io.OutputStream; import java.io.Writer; import java.util.Map; +import com.jfinal.template.io.ByteWriter; +import com.jfinal.template.io.CharWriter; +import com.jfinal.template.io.FastStringWriter; import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.ast.Stat; @@ -42,27 +46,57 @@ public class Template { this.ast = ast; } + /** + * 渲染到 OutputStream 中去 + */ + public void render(Map data, OutputStream outputStream) { + ByteWriter byteWriter = env.engineConfig.writerBuffer.getByteWriter(outputStream); + try { + ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), byteWriter); + } finally { + byteWriter.close(); + } + } + + /** + * 支持无 data 参数,渲染到 OutputStream 中去
+ * 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景 + */ + public void render(OutputStream outputStream) { + render(null, outputStream); + } + /** * 渲染到 Writer 中去 */ public void render(Map data, Writer writer) { - ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), writer); + CharWriter charWriter = env.engineConfig.writerBuffer.getCharWriter(writer); + try { + ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), charWriter); + } finally { + charWriter.close(); + } } /** * 支持无 data 参数,渲染到 Writer 中去
- * 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
- * 此外,其它所有 render 方法也支持传入 null 值 data 参数 + * 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景 */ public void render(Writer writer) { - ast.exec(env, new Scope(null, env.engineConfig.sharedObjectMap), writer); + render(null, writer); } /** - * 渲染到 FastStringWriter 中去 + * 渲染到 String 中去 */ - public void render(Map data, FastStringWriter fastStringWriter) { - ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), fastStringWriter); + public String renderToString(Map data) { + FastStringWriter fsw = env.engineConfig.writerBuffer.getFastStringWriter(); + try { + render(data, fsw); + return fsw.toString(); + } finally { + fsw.close(); + } } /** @@ -74,13 +108,6 @@ public class Template { return fsw.getBuffer(); } - /** - * 渲染到 String 中去 - */ - public String renderToString(Map data) { - return renderToStringBuilder(data).toString(); - } - public boolean isModified() { return env.isSourceListModified(); } diff --git a/src/main/java/com/jfinal/template/expr/ExprParser.java b/src/main/java/com/jfinal/template/expr/ExprParser.java index dd1922d..08f0165 100644 --- a/src/main/java/com/jfinal/template/expr/ExprParser.java +++ b/src/main/java/com/jfinal/template/expr/ExprParser.java @@ -100,6 +100,8 @@ public class ExprParser { public ForCtrl parseForCtrl() { Expr forCtrl = parse(false); + + // 可能返回 ExprList.NULL_EXPR_LIST,必须做判断 if (forCtrl instanceof ForCtrl) { return (ForCtrl)forCtrl; } else { @@ -124,7 +126,7 @@ public class ExprParser { /** * exprList : expr (',' expr)* */ - Expr exprList() { + ExprList exprList() { List exprList = new ArrayList(); while (true) { Expr stat = expr(); @@ -346,7 +348,7 @@ public class ExprParser { return new StaticMethod(clazz, memberName, location); } - ExprList exprList = (ExprList)exprList(); + ExprList exprList = exprList(); match(Sym.RPAREN); return new StaticMethod(clazz, memberName, exprList, location); } @@ -383,7 +385,7 @@ public class ExprParser { return indexMethodField(sharedMethod); } - ExprList exprList = (ExprList)exprList(); + ExprList exprList = exprList(); SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location); match(Sym.RPAREN); return indexMethodField(sharedMethod); @@ -433,7 +435,7 @@ public class ExprParser { } // expr '.' ID '(' exprList ')' - ExprList exprList = (ExprList)exprList(); + ExprList exprList = exprList(); match(Sym.RPAREN); expr = new Method(expr, tok.value(), exprList, location); } @@ -498,7 +500,7 @@ public class ExprParser { move(); return new Array(ExprList.NULL_EXPR_ARRAY, location); } - ExprList exprList = (ExprList)exprList(); + ExprList exprList = exprList(); if (exprList.length() == 1 && peek().sym == Sym.RANGE) { move(); Expr end = expr(); @@ -560,13 +562,13 @@ public class ExprParser { /** * forControl : ID : expr | exprList? ';' expr? ';' exprList? */ - Expr forCtrl() { - ExprList exprList = (ExprList)exprList(); + ForCtrl forCtrl() { + ExprList exprList = exprList(); if (peek().sym == Sym.SEMICOLON) { move(); Expr cond = expr(); match(Sym.SEMICOLON); - Expr update = exprList(); + ExprList update = exprList(); return new ForCtrl(exprList, cond, update, location); } diff --git a/src/main/java/com/jfinal/template/expr/ast/Compare.java b/src/main/java/com/jfinal/template/expr/ast/Compare.java index 7fbf9bb..3e091e9 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Compare.java +++ b/src/main/java/com/jfinal/template/expr/ast/Compare.java @@ -94,9 +94,11 @@ public class Compare extends Expr { case Arith.FLOAT: // 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级 // return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue()); + return l.floatValue() == r.floatValue(); case Arith.DOUBLE: // 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级 // return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue()); + return l.doubleValue() == r.doubleValue(); case Arith.BIGDECIMAL: BigDecimal[] bd = toBigDecimals(l, r); return (bd[0]).compareTo(bd[1]) == 0; @@ -120,8 +122,10 @@ public class Compare extends Expr { return l.longValue() > r.longValue(); case Arith.FLOAT: // return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue()); + return l.floatValue() > r.floatValue(); case Arith.DOUBLE: // return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue()); + return l.doubleValue() > r.doubleValue(); case Arith.BIGDECIMAL: BigDecimal[] bd = toBigDecimals(l, r); return (bd[0]).compareTo(bd[1]) > 0; @@ -150,8 +154,10 @@ public class Compare extends Expr { return l.longValue() >= r.longValue(); case Arith.FLOAT: // return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue()); + return l.floatValue() >= r.floatValue(); case Arith.DOUBLE: // return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue()); + return l.doubleValue() >= r.doubleValue(); case Arith.BIGDECIMAL: BigDecimal[] bd = toBigDecimals(l, r); return (bd[0]).compareTo(bd[1]) >= 0; @@ -180,8 +186,10 @@ public class Compare extends Expr { return l.longValue() < r.longValue(); case Arith.FLOAT: // return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue()); + return l.floatValue() < r.floatValue(); case Arith.DOUBLE: // return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue()); + return l.doubleValue() < r.doubleValue(); case Arith.BIGDECIMAL: BigDecimal[] bd = toBigDecimals(l, r); return (bd[0]).compareTo(bd[1]) < 0; @@ -210,8 +218,10 @@ public class Compare extends Expr { return l.longValue() <= r.longValue(); case Arith.FLOAT: // return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue()); + return l.floatValue() <= r.floatValue(); case Arith.DOUBLE: // return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue()); + return l.doubleValue() <= r.doubleValue(); case Arith.BIGDECIMAL: BigDecimal[] bd = toBigDecimals(l, r); return (bd[0]).compareTo(bd[1]) <= 0; diff --git a/src/main/java/com/jfinal/template/expr/ast/Const.java b/src/main/java/com/jfinal/template/expr/ast/Const.java index bd54ed6..31bacdf 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Const.java +++ b/src/main/java/com/jfinal/template/expr/ast/Const.java @@ -72,10 +72,6 @@ public class Const extends Expr { return value; } - public String toString() { - return value.toString(); - } - public boolean isStr() { return type == Sym.STR; } @@ -139,6 +135,10 @@ public class Const extends Expr { public Double getDouble() { return (Double)value; } + + public String toString() { + return value != null ? value.toString() : "null"; + } } diff --git a/src/main/java/com/jfinal/template/expr/ast/ExprList.java b/src/main/java/com/jfinal/template/expr/ast/ExprList.java index dc1ba13..8decdcf 100644 --- a/src/main/java/com/jfinal/template/expr/ast/ExprList.java +++ b/src/main/java/com/jfinal/template/expr/ast/ExprList.java @@ -16,6 +16,7 @@ package com.jfinal.template.expr.ast; +import java.util.ArrayList; import java.util.List; import com.jfinal.template.TemplateException; import com.jfinal.template.stat.Scope; @@ -25,24 +26,40 @@ import com.jfinal.template.stat.Scope; */ public class ExprList extends Expr { + public static final Expr NULL_EXPR = NullExpr.me; public static final Expr[] NULL_EXPR_ARRAY = new Expr[0]; + public static final ExprList NULL_EXPR_LIST = new ExprList(new ArrayList(0)); + public static final Object[] NULL_OBJECT_ARRAY = new Object[0]; - public static final ExprList NULL_EXPR_LIST = new ExprList(); private Expr[] exprArray; - private ExprList() { - this.exprArray = NULL_EXPR_ARRAY; - } - public ExprList(List exprList) { - if (exprList != null && exprList.size() > 0) { + if (exprList.size() > 0) { exprArray = exprList.toArray(new Expr[exprList.size()]); } else { exprArray = NULL_EXPR_ARRAY; } } + /** + * 持有 ExprList 的指令可以通过此方法提升 AST 执行性能 + * 1:当 exprArray.length == 1 时返回 exprArray[0] + * 2:当 exprArray.length == 0 时返回 NullExpr + * 3:其它情况返回 ExprList 自身 + * + * 意义在于,当满足前面两个条件时,避免掉了 ExprList.eval(...) 方法中的判断与循环 + */ + public Expr getActualExpr() { + if (exprArray.length == 1) { + return exprArray[0]; + } else if (exprArray.length == 0) { + return NULL_EXPR; + } else { + return this; + } + } + public Expr[] getExprArray() { return exprArray; } @@ -54,6 +71,14 @@ public class ExprList extends Expr { return exprArray[index]; } + public Expr getFirstExpr() { + return exprArray.length > 0 ? exprArray[0] : null; + } + + public Expr getLastExpr() { + return exprArray.length > 0 ? exprArray[exprArray.length - 1] : null; + } + public int length() { return exprArray.length; } @@ -62,11 +87,20 @@ public class ExprList extends Expr { * 对所有表达式求值,只返回最后一个表达式的值 */ public Object eval(Scope scope) { - Object ret = null; - for (Expr expr : exprArray) { - ret = expr.eval(scope); + // 优化:绝大多数情况下 length 等于 1 + if (exprArray.length == 1) { + return exprArray[0].eval(scope); } - return ret; + + if (exprArray.length == 0) { + return null; + } + + int end = exprArray.length - 1; + for (int i=0; i targetClass = target.getClass(); - String key = FieldKit.getFieldKey(targetClass, getterName); + Long key = buildFieldKey(targetClass); + MethodInfo getter; try { getter = MethodKit.getGetterMethod(key, targetClass, getterName); @@ -112,6 +117,10 @@ public class Field extends Expr { } throw new TemplateException("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location); } + + private Long buildFieldKey(Class targetClass) { + return targetClass.getName().hashCode() ^ getterNameHash; + } } 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 2c3c0fa..4b4e651 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java @@ -17,21 +17,21 @@ package com.jfinal.template.expr.ast; import java.lang.reflect.Field; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashMap; /** * FieldKit */ public class FieldKit { - private static final ConcurrentHashMap fieldCache = new ConcurrentHashMap(); + private static final HashMap fieldCache = new HashMap(); - public static Field getField(String key, Class targetClass, String fieldName) { + 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.putIfAbsent(key, field); + fieldCache.put(key, field); } else { // 对于不存在的 Field,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险 fieldCache.put(key, Boolean.FALSE); @@ -49,14 +49,6 @@ public class FieldKit { } return null; } - - /** - * 获取 Field 用于缓存的 key - */ - public static String getFieldKey(Class targetClass, String getterName) { - return new StringBuilder(64).append(targetClass.getName()) - .append('.').append(getterName).toString(); - } } diff --git a/src/main/java/com/jfinal/template/expr/ast/ForCtrl.java b/src/main/java/com/jfinal/template/expr/ast/ForCtrl.java index fd695d9..95aa6bd 100644 --- a/src/main/java/com/jfinal/template/expr/ast/ForCtrl.java +++ b/src/main/java/com/jfinal/template/expr/ast/ForCtrl.java @@ -58,10 +58,10 @@ public class ForCtrl extends Expr { /** * exprList? ';' expr? ';' exprList? */ - public ForCtrl(Expr init, Expr cond, Expr update, Location location) { - this.init = init; + public ForCtrl(ExprList init, Expr cond, ExprList update, Location location) { + this.init = init.getActualExpr(); this.cond = cond; - this.update = update; + this.update = update.getActualExpr(); this.id = null; this.expr = null; this.location = location; diff --git a/src/main/java/com/jfinal/template/expr/ast/Logic.java b/src/main/java/com/jfinal/template/expr/ast/Logic.java index dcbabb3..4406267 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Logic.java +++ b/src/main/java/com/jfinal/template/expr/ast/Logic.java @@ -37,6 +37,16 @@ public class Logic extends Expr { private Expr left; // ! 运算没有 left 参数 private Expr right; + // 默认为新工作模式 + private static boolean newWorkMode = true; + /** + * 设置为旧工作模式,为了兼容 jfinal 3.3 之前的版本 + */ + @Deprecated + public static void setToOldWorkMode() { + newWorkMode = false; + } + /** * 构造 || && 结点 */ @@ -95,44 +105,55 @@ public class Logic extends Expr { * 规则: * 1:null 返回 false * 2:boolean 类型,原值返回 - * 3:Map、Connection(List被包括在内) 返回 size() > 0 - * 4:数组,返回 length > 0 - * 5:String、StringBuilder、StringBuffer 等继承自 CharSequence 类的对象,返回 length > 0 - * 6:Number 类型,返回 value != 0 - * 7:Iterator 返回 hasNext() 值 - * 8:其它返回 true + * 3:String、StringBuilder 等一切继承自 CharSequence 类的对象,返回 length > 0 + * 4:其它返回 true + * + * 通过 Logic.setToOldWorkMode() 设置,可支持老版本中的以下四个规则: + * 1:Number 类型,返回 value != 0 + * 2:Map、Collection(List被包括在内) 返回 size() > 0 + * 3:数组,返回 length > 0 + * 4:Iterator 返回 hasNext() 值 */ public static boolean isTrue(Object v) { if (v == null) { return false; } + if (v instanceof Boolean) { return (Boolean)v; } - if (v instanceof Collection) { - return ((Collection)v).size() > 0; - } - if (v instanceof Map) { - return ((Map)v).size() > 0; - } - if (v.getClass().isArray()) { - return Array.getLength(v) > 0; - } if (v instanceof CharSequence) { return ((CharSequence)v).length() > 0; } - if (v instanceof Number) { - if (v instanceof Double) { - return ((Number)v).doubleValue() != 0; + + // 如果不是新工作模式,则对下面类型进行判断 + if ( !newWorkMode ) { + if (v instanceof Number) { + if (v instanceof Double) { + return ((Number)v).doubleValue() != 0; + } + if (v instanceof Float) { + return ((Number)v).floatValue() != 0; + } + return ((Number)v).intValue() != 0; } - if (v instanceof Float) { - return ((Number)v).floatValue() != 0; + + // 下面四种类型的判断已提供了 shared method 扩展,用法如下: + // #if(notEmpty(object)) 以及 #if(isEmpty(object)) + if (v instanceof Collection) { + return ((Collection)v).size() > 0; + } + if (v instanceof Map) { + return ((Map)v).size() > 0; + } + if (v.getClass().isArray()) { + return Array.getLength(v) > 0; + } + if (v instanceof Iterator) { + return ((Iterator)v).hasNext(); } - return ((Number)v).intValue() != 0; - } - if (v instanceof Iterator) { - return ((Iterator)v).hasNext(); } + return true; } 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 6964e17..c81fc03 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodInfo.java @@ -26,14 +26,14 @@ import java.lang.reflect.Modifier; */ public class MethodInfo { - protected final String key; + protected final Long key; protected final Class clazz; protected final Method method; protected final boolean isVarArgs; protected final Class[] paraTypes; - public MethodInfo(String key, Class clazz, Method method) { + public MethodInfo(Long key, Class clazz, Method method) { this.key = key; this.clazz = clazz; this.method = method; @@ -64,7 +64,7 @@ public class MethodInfo { return method.invoke(target, finalArgValues); } - public String getKey() { + public Long getKey() { return key; } 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 b6dfcca..559d2fa 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java @@ -26,7 +26,7 @@ public class MethodInfoExt extends MethodInfo { protected Object objectOfExtensionClass; - public MethodInfoExt(Object objectOfExtensionClass, String key, Class clazz, Method method) { + public MethodInfoExt(Object objectOfExtensionClass, Long key, Class clazz, Method method) { super(key, clazz, method); this.objectOfExtensionClass = objectOfExtensionClass; diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java b/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java new file mode 100644 index 0000000..7ce8395 --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2011-2017, 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 com.jfinal.kit.HashKit; + +/** + * MethodKeyBuilder + */ +public abstract class MethodKeyBuilder { + + /** + * 生成指定 class、指定方法名、指定方法形参类型的 key 值,用于缓存 + */ + public abstract Long getMethodKey(Class targetClass, String methodName, Class[] argTypes); + + // 默认使用 FastMethodKeyBuilder + static MethodKeyBuilder instance = new FastMethodKeyBuilder(); + + public static MethodKeyBuilder getInstance() { + return instance; + } + + /** + * 切换到 StrictMethodKeyBuilder + * + *
+	 * 特别注意:
+	 *   如果希望将 configEngine(Engine me) 中的 Engine 切换到 StrictMethodKeyBuilder,
+	 *   需要在 YourJFinalConfig extends JFinalConfig 中利用如下代码块才能生效:
+	 * 	  static {
+	 * 			MethodKeyBuilder.useStrictMethodKeyBuilder();
+	 *    }
+	 * 
+	 *   原因是在 com.jfinal.core.Config 中 new Engine() 时 useStrictMethodKeyBuilder()
+	 *   方法并未生效,所以 extension method 生成 method key 时仍然使用的是  FastMethodKeyBuilder
+	 *   以至于在运行时,使用 StrictMethodKeyBuilder 生成的 key 找不到 extension method
+	 * 
+	 * 
+ */ + public static void useStrictMethodKeyBuilder() { + MethodKeyBuilder.instance = new StrictMethodKeyBuilder(); + } + + /** + * 切换到用户自定义 MethodKeyBuilder + */ + public static void setMethodKeyBuilder(MethodKeyBuilder methodKeyBuilder) { + if (methodKeyBuilder == null) { + throw new IllegalArgumentException("methodKeyBuilder can not be null"); + } + MethodKeyBuilder.instance = methodKeyBuilder; + } + + /** + * FastMethodKeyBuilder + * + * targetClass、methodName、argTypes 的 hash 直接使用 String.hashCode() + * String.hashCode() 会被缓存,性能更好 + */ + public static class FastMethodKeyBuilder extends MethodKeyBuilder { + public Long getMethodKey(Class targetClass, String methodName, Class[] argTypes) { + long hash = HashKit.FNV_OFFSET_BASIS_64; + hash ^= targetClass.getName().hashCode(); + hash *= HashKit.FNV_PRIME_64; + + hash ^= methodName.hashCode(); + hash *= HashKit.FNV_PRIME_64; + + if (argTypes != null) { + for (int i=0; i type = argTypes[i]; + if (type != null) { + hash ^= type.getName().hashCode(); + hash *= HashKit.FNV_PRIME_64; + } + } + } + return hash; + } + } + + /** + * StrictMethodKeyBuilder + * + * targetClass、methodName、argTypes 三部分全部使用 fnv1a64 算法计算 hash + */ + public static class StrictMethodKeyBuilder extends MethodKeyBuilder { + public Long getMethodKey(Class targetClass, String methodName, Class[] argTypes) { + long hash = HashKit.FNV_OFFSET_BASIS_64; + + hash = fnv1a64(hash, targetClass.getName()); + hash = fnv1a64(hash, methodName); + if (argTypes != null) { + for (int i=0; i type = argTypes[i]; + if (type != null) { + hash = fnv1a64(hash, type.getName()); + } + } + } + + return hash; + } + + private long fnv1a64(long offsetBasis, String key) { + long hash = offsetBasis; + for(int i=0, size=key.length(); i forbiddenMethods = new HashSet(); private static final Set> forbiddenClasses = new HashSet>(); private static final Map, Class> primitiveMap = new HashMap, Class>(); - private static final ConcurrentHashMap methodCache = new ConcurrentHashMap(); + private static final HashMap methodCache = new HashMap(); // 初始化在模板中调用 method 时所在的被禁止使用类 static { @@ -105,12 +103,12 @@ public class MethodKit { public static MethodInfo getMethod(Class targetClass, String methodName, Object[] argValues) { Class[] argTypes = getArgTypes(argValues); - String key = getMethodKey(targetClass, methodName, argTypes); + Long key = getMethodKey(targetClass, methodName, argTypes); Object method = methodCache.get(key); if (method == null) { method = doGetMethod(key, targetClass, methodName, argTypes); if (method != null) { - methodCache.putIfAbsent(key, method); + methodCache.put(key, method); } else { // 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险 methodCache.put(key, Boolean.FALSE); @@ -123,12 +121,12 @@ public class MethodKit { * 获取 getter 方法 * 使用与 Field 相同的 key,避免生成两次 key值 */ - public static MethodInfo getGetterMethod(String key, Class targetClass, String methodName) { + 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); + methodCache.put(key, getterMethod); } else { methodCache.put(key, Boolean.FALSE); } @@ -147,7 +145,7 @@ public class MethodKit { return argTypes; } - private static MethodInfo doGetMethod(String key, Class targetClass, String methodName, Class[] argTypes) { + private static MethodInfo doGetMethod(Long key, Class targetClass, String methodName, Class[] argTypes) { if (forbiddenClasses.contains(targetClass)) { throw new RuntimeException("Forbidden class: " + targetClass.getName()); } @@ -229,23 +227,8 @@ public class MethodKit { /** * 获取方法用于缓存的 key */ - private static String getMethodKey(Class targetClass, String methodName, Class[] argTypes) { - StringBuilder key = new StringBuilder(96); - key.append(targetClass.getName()); - key.append('.').append(methodName); - if (argTypes != null && argTypes.length > 0) { - createArgTypesDigest(argTypes, key); - } - return key.toString(); - } - - static void createArgTypesDigest(Class[] argTypes, StringBuilder key) { - StringBuilder argTypesDigest = new StringBuilder(64); - for (int i=0; i type = argTypes[i]; - argTypesDigest.append(type != null ? type.getName() : "null"); - } - key.append(HashKit.md5(argTypesDigest.toString())); + private static Long getMethodKey(Class targetClass, String methodName, Class[] argTypes) { + return MethodKeyBuilder.instance.getMethodKey(targetClass, methodName, argTypes); } // 以下代码实现 extension method 功能 -------------------- @@ -290,7 +273,7 @@ public class MethodKit { throw new RuntimeException("Extension method \"" + methodName + "\" is already exists in class \"" + targetClass.getName() + "\""); } } catch (NoSuchMethodException e) { // Method 找不到才能添加该扩展方法 - String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes)); + Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes)); if (methodCache.containsKey(key)) { throw new RuntimeException(buildMethodSignatureForException("The extension method is already exists: " + extensionClass.getName() + ".", methodName, targetParaTypes)); } @@ -319,7 +302,7 @@ public class MethodKit { Class[] targetParaTypes = new Class[extensionMethodParaTypes.length - 1]; System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length); - String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes)); + Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes)); methodCache.remove(key); } } diff --git a/src/main/java/com/jfinal/template/expr/ast/NullExpr.java b/src/main/java/com/jfinal/template/expr/ast/NullExpr.java new file mode 100644 index 0000000..e8361b1 --- /dev/null +++ b/src/main/java/com/jfinal/template/expr/ast/NullExpr.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2011-2017, 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 com.jfinal.template.stat.Scope; + +/** + * NullExpr + */ +public class NullExpr extends Expr { + + public static final NullExpr me = new NullExpr(); + + private NullExpr() {} + + public Object eval(Scope scope) { + return null; + } +} + + + diff --git a/src/main/java/com/jfinal/template/expr/ast/NullSafe.java b/src/main/java/com/jfinal/template/expr/ast/NullSafe.java index 4c9d05c..81d38bd 100644 --- a/src/main/java/com/jfinal/template/expr/ast/NullSafe.java +++ b/src/main/java/com/jfinal/template/expr/ast/NullSafe.java @@ -49,15 +49,18 @@ public class NullSafe extends Expr { Ctrl ctrl = scope.getCtrl(); boolean oldNullSafeValue = ctrl.isNullSafe(); - Object ret; try { ctrl.setNullSafe(true); - ret = left.eval(scope); + Object ret = left.eval(scope); + if (ret != null) { + return ret; + } } finally { ctrl.setNullSafe(oldNullSafeValue); } - return ret == null && right != null ? right.eval(scope) : ret; + // right 表达式处于 null safe 区域之外 + return right != null ? right.eval(scope) : null; } } @@ -66,3 +69,4 @@ public class NullSafe extends Expr { + 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 c2abb39..b77316c 100644 --- a/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java @@ -21,10 +21,11 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashMap; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import com.jfinal.kit.HashKit; import com.jfinal.kit.ReflectKit; /** @@ -32,27 +33,27 @@ import com.jfinal.kit.ReflectKit; */ public class SharedMethodKit { - private static final Set excludedMethodKey = new HashSet(); + private static final Set excludedMethodKey = new HashSet(); static { Method[] methods = Object.class.getMethods(); for (Method method : methods) { - String key = getSharedMethodKey(method.getName(), method.getParameterTypes()); + Long key = getSharedMethodKey(method.getName(), method.getParameterTypes()); excludedMethodKey.add(key); } } private final List sharedMethodList = new ArrayList(); - private final ConcurrentHashMap methodCache = new ConcurrentHashMap(); + private final HashMap methodCache = new HashMap(); public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) { Class[] argTypes = MethodKit.getArgTypes(argValues); - String key = getSharedMethodKey(methodName, argTypes); + Long key = getSharedMethodKey(methodName, argTypes); SharedMethodInfo method = methodCache.get(key); if (method == null) { method = doGetSharedMethodInfo(methodName, argTypes); if (method != null) { - methodCache.putIfAbsent(key, method); + methodCache.put(key, method); } // shared method 不支持 null safe,不缓存: methodCache.put(key, Boolean.FALSE) } @@ -110,7 +111,7 @@ public class SharedMethodKit { SharedMethodInfo current = it.next(); String methodName = method.getName(); if (current.getName().equals(methodName)) { - String key = getSharedMethodKey(methodName, method.getParameterTypes()); + Long key = getSharedMethodKey(methodName, method.getParameterTypes()); if (current.getKey().equals(key)) { it.remove(); } @@ -125,7 +126,7 @@ public class SharedMethodKit { Method[] methods = sharedClass.getMethods(); for (Method method : methods) { - String key = getSharedMethodKey(method.getName(), method.getParameterTypes()); + Long key = getSharedMethodKey(method.getName(), method.getParameterTypes()); if (excludedMethodKey.contains(key)) { continue ; } @@ -144,19 +145,27 @@ public class SharedMethodKit { } } - private static String getSharedMethodKey(String methodName, Class[] argTypes) { - StringBuilder key = new StringBuilder(64); - key.append(methodName); - if (argTypes != null && argTypes.length > 0) { - MethodKit.createArgTypesDigest(argTypes, key); + private static Long getSharedMethodKey(String methodName, Class[] argTypes) { + long hash = HashKit.FNV_OFFSET_BASIS_64; + hash ^= methodName.hashCode(); + hash *= HashKit.FNV_PRIME_64; + + if (argTypes != null) { + for (int i=0; i type = argTypes[i]; + if (type != null) { + hash ^= type.getName().hashCode(); + hash *= HashKit.FNV_PRIME_64; + } + } } - return key.toString(); - } + return hash; + } static class SharedMethodInfo extends MethodInfo { final Object target; - private SharedMethodInfo(String key, Class clazz, Method method, Object target) { + private SharedMethodInfo(Long key, Class clazz, Method method, Object target) { super(key, clazz, method); this.target = target; } diff --git a/src/main/java/com/jfinal/template/ext/directive/DateDirective.java b/src/main/java/com/jfinal/template/ext/directive/DateDirective.java index 51ba3c6..1cfc512 100644 --- a/src/main/java/com/jfinal/template/ext/directive/DateDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/DateDirective.java @@ -16,24 +16,28 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; -import java.text.SimpleDateFormat; +import java.io.IOException; +import java.util.Date; import com.jfinal.template.Directive; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; /** - * 不带参时,按默认 pattern 输出当前日期 + * #date 日期格式化输出指令 * - * #date() 指令支持无参时获取当前指令,第一个参数 string 当成是 pattern + * 三种用法: + * 1:#date(createAt) 用默认 datePattern 配置,输出 createAt 变量中的日期值 + * 2:#date(createAt, "yyyy-MM-dd HH:mm:ss") 用第二个参数指定的 datePattern,输出 createAt 变量中的日期值 + * 3:#date() 用默认 datePattern 配置,输出 “当前” 日期值 * - * 日期输出指令,第一个参数是被输出的 java.util.Date 对象或其子类对象 - * 无第二个参数时按默认 patter 输出,第二个参数为 expr 表达式,表示 pattern - * 第二个为 date 时,表示当第一个为 null 时的默认值 + * 注意: + * 1:#date 指令中的参数可以是变量,例如:#date(d, p) 中的 d 与 p 可以全都是变量 + * 2:默认 datePattern 可通过 Engine.setDatePattern(...) 进行配置 */ public class DateDirective extends Directive { @@ -51,34 +55,32 @@ public class DateDirective extends Directive { this.valueExpr = null; this.datePatternExpr = null; } else if (paraNum == 1) { - this.valueExpr = exprList.getExprArray()[0]; + this.valueExpr = exprList.getExpr(0); this.datePatternExpr = null; } else if (paraNum == 2) { - this.valueExpr = exprList.getExprArray()[0]; - this.datePatternExpr = exprList.getExprArray()[1]; + this.valueExpr = exprList.getExpr(0); + this.datePatternExpr = exprList.getExpr(1); } } public void exec(Env env, Scope scope, Writer writer) { - if (paraNum == 0) { - outputToday(env, writer); - } else if (paraNum == 1) { + if (paraNum == 1) { outputWithoutDatePattern(env, scope, writer); } else if (paraNum == 2) { outputWithDatePattern(env, scope, writer); + } else { + outputToday(env, writer); } } private void outputToday(Env env, Writer writer) { - Object value = format(new java.util.Date(), env.getEngineConfig().getDatePattern()); - write(writer, value.toString()); + write(writer, new Date(), env.getEngineConfig().getDatePattern()); } private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) { Object value = valueExpr.eval(scope); if (value != null) { - value = format(value, env.getEngineConfig().getDatePattern()); - write(writer, value.toString()); + write(writer, (Date)value, env.getEngineConfig().getDatePattern()); } } @@ -88,18 +90,18 @@ public class DateDirective extends Directive { return ; } - Object dp = this.datePatternExpr.eval(scope); - if ( !(dp instanceof String) ) { - throw new TemplateException("The sencond parameter dataPattern of #date directive must be String", location); + Object datePattern = this.datePatternExpr.eval(scope); + if ( !(datePattern instanceof String) ) { + throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location); } - value = format(value, (String)dp); - write(writer, value.toString()); + + write(writer, (Date)value, (String)datePattern); } - private String format(Object value, String datePattern) { + private void write(Writer writer, Date date, String datePattern) { try { - return new SimpleDateFormat(datePattern).format(value); - } catch (Exception e) { + writer.write(date, datePattern); + } catch (IOException e) { throw new TemplateException(e.getMessage(), location, e); } } 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 38e17e0..7f20aba 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,9 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; import com.jfinal.template.Directive; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** diff --git a/src/main/java/com/jfinal/template/ext/directive/NowDirective.java b/src/main/java/com/jfinal/template/ext/directive/NowDirective.java index 7c4bddb..f3e6474 100644 --- a/src/main/java/com/jfinal/template/ext/directive/NowDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/NowDirective.java @@ -16,12 +16,13 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; -import java.text.SimpleDateFormat; +import java.io.IOException; +import java.util.Date; import com.jfinal.template.Directive; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; @@ -39,22 +40,21 @@ public class NowDirective extends Directive { } public void exec(Env env, Scope scope, Writer writer) { - String dataPattern; + String datePattern; if (exprList.length() == 0) { - dataPattern = env.getEngineConfig().getDatePattern(); + datePattern = env.getEngineConfig().getDatePattern(); } else { Object dp = exprList.eval(scope); if (dp instanceof String) { - dataPattern = (String)dp; + datePattern = (String)dp; } else { - throw new TemplateException("The parameter of #new directive must be String", location); + throw new TemplateException("The parameter of #now directive must be String", location); } } try { - String value = new SimpleDateFormat(dataPattern).format(new java.util.Date()); - write(writer, value); - } catch (Exception e) { + writer.write(new Date(), datePattern); + } catch (IOException e) { throw new TemplateException(e.getMessage(), location, e); } } diff --git a/src/main/java/com/jfinal/template/ext/directive/NumberDirective.java b/src/main/java/com/jfinal/template/ext/directive/NumberDirective.java new file mode 100644 index 0000000..a670603 --- /dev/null +++ b/src/main/java/com/jfinal/template/ext/directive/NumberDirective.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jfinal.template.ext.directive; + +import java.text.DecimalFormat; +import com.jfinal.template.Directive; +import com.jfinal.template.Env; +import com.jfinal.template.TemplateException; +import com.jfinal.template.expr.ast.Expr; +import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; +import com.jfinal.template.stat.ParseException; +import com.jfinal.template.stat.Scope; + +/** + * #number 数字格式化输出指令 + * + * 两种用法: + * 1:#number(n) 用默认 pattern 输出变量中的值 + * 2:#number(n, "#.##") 用第二个参数指定的 pattern 输出变量中的值 + * + * 注意: + * 1:pattern 的使用与 java.text.DecimalFormat 的完全一样 + * 在拿不定主意的时候可以在搜索引擎中搜索关键字:DecimalFormat + * 2:#number 指令中的参数可以是变量,例如:#number(n, p) 中的 n 与 p 可以全都是变量 + * + *
+ * 示例:
+ * #number(3.1415926, "#.##")
+ * #number(0.9518, "#.##%")
+ * #number(300000, "光速为每秒 ,### 公里。")
+ * 
+ * #set(n = 1.234)
+ * #set(p = "#.##")
+ * #number(n, p)
+ * 
+ */ +public class NumberDirective extends Directive { + + private Expr valueExpr; + private Expr patternExpr; + private int paraNum; + + public void setExprList(ExprList exprList) { + this.paraNum = exprList.length(); + if (paraNum == 0) { + throw new ParseException("The parameter of #number directive can not be blank", location); + } + if (paraNum > 2) { + throw new ParseException("Wrong number parameter of #number directive, two parameters allowed at most", location); + } + + if (paraNum == 1) { + this.valueExpr = exprList.getExpr(0); + this.patternExpr = null; + } else if (paraNum == 2) { + this.valueExpr = exprList.getExpr(0); + this.patternExpr = exprList.getExpr(1); + } + } + + public void exec(Env env, Scope scope, Writer writer) { + Object value = valueExpr.eval(scope); + if (value == null) { + return ; + } + + if (paraNum == 1) { + outputWithoutPattern(writer, value); + } else if (paraNum == 2) { + outputWithPattern(scope, writer, value); + } + } + + private void outputWithoutPattern(Writer writer, Object value) { + String ret = new DecimalFormat().format(value); + write(writer, ret); + } + + private void outputWithPattern(Scope scope, Writer writer, Object value) { + Object pattern = patternExpr.eval(scope); + if ( !(pattern instanceof String) ) { + throw new TemplateException("The sencond parameter pattern of #number directive must be String", location); + } + + String ret = new DecimalFormat((String)pattern).format(value); + write(writer, ret); + } +} + + + + + diff --git a/src/main/java/com/jfinal/template/ext/directive/RandomDirective.java b/src/main/java/com/jfinal/template/ext/directive/RandomDirective.java index c57f548..35fe43a 100644 --- a/src/main/java/com/jfinal/template/ext/directive/RandomDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/RandomDirective.java @@ -16,9 +16,9 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; import com.jfinal.template.Directive; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** 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 58543fc..64fbf7f 100644 --- a/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java @@ -16,7 +16,6 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; import java.util.HashMap; import java.util.Map; import com.jfinal.template.Directive; @@ -25,6 +24,7 @@ import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.source.ISource; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.ParseException; @@ -33,6 +33,7 @@ import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.ast.Define; import com.jfinal.template.stat.ast.Include; import com.jfinal.template.stat.ast.Stat; +import com.jfinal.template.stat.ast.StatList; /** * #render 指令用于动态渲染子模板,作为 include 指令的补充 @@ -111,7 +112,7 @@ public class RenderDirective extends Directive { if (statInfo == null) { statInfo = parseStatInfo(env, subFileName); statInfoCache.put(subFileName, statInfo); - } else if (env.getEngineConfig().isDevMode()) { + } else if (env.isDevMode()) { // statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载 if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) { statInfo = parseStatInfo(env, subFileName); @@ -130,8 +131,8 @@ public class RenderDirective extends Directive { try { EnvSub envSub = new EnvSub(env); - Stat stat = new Parser(envSub, fileSource.getContent(), subFileName).parse(); - return new StatInfo(envSub, stat, fileSource); + StatList statList = new Parser(envSub, fileSource.getContent(), subFileName).parse(); + return new StatInfo(envSub, statList.getActualStat(), fileSource); } catch (Exception e) { throw new ParseException(e.getMessage(), location, e); } diff --git a/src/main/java/com/jfinal/template/ext/directive/StringDirective.java b/src/main/java/com/jfinal/template/ext/directive/StringDirective.java index 72dbee2..34e6192 100644 --- a/src/main/java/com/jfinal/template/ext/directive/StringDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/StringDirective.java @@ -16,10 +16,11 @@ package com.jfinal.template.ext.directive; -import java.io.Writer; import com.jfinal.template.Directive; import com.jfinal.template.Env; -import com.jfinal.template.FastStringWriter; +import com.jfinal.template.io.CharWriter; +import com.jfinal.template.io.FastStringWriter; +import com.jfinal.template.io.Writer; import com.jfinal.template.expr.ast.Const; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; @@ -68,8 +69,14 @@ public class StringDirective extends Directive { } public void exec(Env env, Scope scope, Writer writer) { + CharWriter charWriter = new CharWriter(64); FastStringWriter fsw = new FastStringWriter(); - stat.exec(env, scope, fsw); + charWriter.init(fsw); + try { + stat.exec(env, scope, charWriter); + } finally { + charWriter.close(); + } if (this.isLocalAssignment) { scope.setLocal(name, fsw.toString()); diff --git a/src/main/java/com/jfinal/template/ext/sharedmethod/SharedMethodLib.java b/src/main/java/com/jfinal/template/ext/sharedmethod/SharedMethodLib.java new file mode 100644 index 0000000..63bcb7a --- /dev/null +++ b/src/main/java/com/jfinal/template/ext/sharedmethod/SharedMethodLib.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jfinal.template.ext.sharedmethod; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * SharedMethodLib 共享方法库,逐步添加一些最常用的共享方法 + * + *
+ * 3.3 版本之前的 Logic.isTrue(Object) 方法不再对 Collection、 + * Map、数组、Iterator、Iterable 进行为空的判断,这部分逻辑已转移至 + * SharedMethodLib.isEmpty(Object) + */ +public class SharedMethodLib { + + /** + * 判断 Collection、Map、数组、Iterator、Iterable 类型对象中的元素个数是否为 0 + * 规则: + * 1:null 返回 true + * 2:List、Set 等一切继承自 Collection 的,返回 isEmpty() + * 3:Map 返回 isEmpty() + * 4:数组返回 length == 0 + * 5:Iterator 返回 ! hasNext() + * 6:Iterable 返回 ! iterator().hasNext() + * + * 注意:原先 Logic.isTrue(Object) 中对集合与数组类型为空的判断转移到此方法中 + */ + public Boolean isEmpty(Object v) { + if (v == null) { + return true; + } + + if (v instanceof Collection) { + return ((Collection)v).isEmpty(); + } + if (v instanceof Map) { + return ((Map)v).isEmpty(); + } + + if (v.getClass().isArray()) { + return Array.getLength(v) == 0; + } + + if (v instanceof Iterator) { + return ! ((Iterator)v).hasNext(); + } + if (v instanceof Iterable) { + return ! ((Iterable)v).iterator().hasNext(); + } + + throw new IllegalArgumentException("isEmpty(...) 方法只能接受 Collection、Map、数组、Iterator、Iterable 类型参数"); + } + + public Boolean notEmpty(Object v) { + return !isEmpty(v); + } +} + + + + + + diff --git a/src/main/java/com/jfinal/template/ext/spring/JFinalView.java b/src/main/java/com/jfinal/template/ext/spring/JFinalView.java index ae218b2..ec50add 100644 --- a/src/main/java/com/jfinal/template/ext/spring/JFinalView.java +++ b/src/main/java/com/jfinal/template/ext/spring/JFinalView.java @@ -16,7 +16,7 @@ package com.jfinal.template.ext.spring; -import java.io.Writer; +import java.io.OutputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -56,9 +56,8 @@ public class JFinalView extends AbstractTemplateView { } } - Writer writer = response.getWriter(); - JFinalViewResolver.engine.getTemplate(getUrl()).render(model, writer); - writer.flush(); + OutputStream os = response.getOutputStream(); + JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os); } } diff --git a/src/main/java/com/jfinal/template/ext/spring/JFinalViewResolver.java b/src/main/java/com/jfinal/template/ext/spring/JFinalViewResolver.java index 00a99ad..96c391c 100644 --- a/src/main/java/com/jfinal/template/ext/spring/JFinalViewResolver.java +++ b/src/main/java/com/jfinal/template/ext/spring/JFinalViewResolver.java @@ -18,7 +18,9 @@ package com.jfinal.template.ext.spring; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.servlet.ServletContext; +import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.AbstractTemplateViewResolver; import com.jfinal.kit.StrKit; import com.jfinal.template.Directive; @@ -54,6 +56,15 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver { static boolean sessionInView = false; static boolean createSession = true; + private static JFinalViewResolver me = null; + + /** + * me 会保存在第一次被创建对象 + */ + public static JFinalViewResolver me() { + return me; + } + public Engine getEngine() { return engine; } @@ -83,6 +94,24 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver { } } + /** + * 通过 List 配置多个 shared function file + *
+	 * 配置示例:
+	 * 	
+	 *     	
+	 *     		_layout.html
+	 *     		_paginate.html
+	 *     	
+	 * 	
+	 * 
+ */ + public void setSharedFunctionList(List sharedFunctionList) { + if (sharedFunctionList != null) { + JFinalViewResolver.sharedFunctionFiles.addAll(sharedFunctionList); + } + } + /** * 添加 shared function 文件,可调用多次添加多个文件 */ @@ -94,8 +123,16 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver { /** * 添加自定义指令 */ + public void addDirective(String directiveName, Class directiveClass) { + engine.addDirective(directiveName, directiveClass); + } + + /** + * 添加自定义指令,已被 addDirective(String, Class) 方法取代 + */ + @Deprecated public void addDirective(String directiveName, Directive directive) { - engine.addDirective(directiveName, directive); + addDirective(directiveName, directive.getClass()); } /** @@ -196,6 +233,12 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver { // --------------------------------------------------------------- public JFinalViewResolver() { + synchronized(JFinalViewResolver.class) { + if (me == null) { + me = this; + } + } + setViewClass(requiredViewClass()); setOrder(0); setContentType("text/html;charset=UTF-8"); @@ -208,12 +251,30 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver { return JFinalView.class; } + /** + * 支持 jfinal enjoy、jsp、freemarker、velocity 四类模板共存于一个项目中 + * + * 注意:这里采用识别 ".jsp"、".ftl"、".vm" 模板后缀名的方式来实现功能 + * 所以 jfinal enjoy 模板不要采用上述三种后缀名,否则功能将失效 + * 还要注意与 jsp、freemarker、velocity 以外类型模板共存使用时 + * 需要改造该方法 + */ + protected View loadView(String viewName, Locale locale) throws Exception { + String suffix = getSuffix(); + if (".jsp".equals(suffix) || ".ftl".equals(suffix) || ".vm".equals(suffix)) { + return null; + } else { + return super.loadView(viewName, locale); + } + } + /** * spring 回调,利用 ServletContext 做必要的初始化工作 */ @Override protected void initServletContext(ServletContext servletContext) { super.initServletContext(servletContext); + super.setExposeRequestAttributes(true); initBaseTemplatePath(servletContext); initSharedFunction(); diff --git a/src/main/java/com/jfinal/template/io/ByteWriter.java b/src/main/java/com/jfinal/template/io/ByteWriter.java new file mode 100644 index 0000000..4b2b249 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/ByteWriter.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * ByteWriter + */ +public class ByteWriter extends Writer { + + OutputStream out; + Encoder encoder; + + char[] chars; + byte[] bytes; + + public ByteWriter(Encoder encoder, int bufferSize) { + this.encoder = encoder; + this.chars = new char[bufferSize]; + this.bytes = new byte[bufferSize * ((int)encoder.maxBytesPerChar())]; + } + + public ByteWriter init(OutputStream outputStream) { + this.out = outputStream; + return this; + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() { + try { + if (out != null) { + out.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + out = null; + } + } + + public void write(String str, int offset, int len) throws IOException { + while (len > chars.length) { + write(str, offset, chars.length); + offset += chars.length; + len -= chars.length; + } + + str.getChars(offset, offset + len, chars, 0); + int byteLen = encoder.encode(chars, 0, len, bytes); + out.write(bytes, 0, byteLen); + } + + public void write(String str) throws IOException { + write(str, 0, str.length()); + } + + public void write(StringBuilder stringBuilder, int offset, int len) throws IOException { + while (len > chars.length) { + write(stringBuilder, offset, chars.length); + offset += chars.length; + len -= chars.length; + } + + stringBuilder.getChars(offset, offset + len, chars, 0); + int byteLen = encoder.encode(chars, 0, len, bytes); + out.write(bytes, 0, byteLen); + } + + public void write(StringBuilder stringBuilder) throws IOException { + write(stringBuilder, 0, stringBuilder.length()); + } + + public void write(IWritable writable) throws IOException { + byte[] data = writable.getBytes(); + out.write(data, 0, data.length); + } + + public void write(int intValue) throws IOException { + IntegerWriter.write(this, intValue); + } + + public void write(long longValue) throws IOException { + LongWriter.write(this, longValue); + } + + public void write(double doubleValue) throws IOException { + FloatingWriter.write(this, doubleValue); + } + + public void write(float floatValue) throws IOException { + FloatingWriter.write(this, floatValue); + } + + private static final byte[] TRUE_BYTES = "true".getBytes(); + private static final byte[] FALSE_BYTES = "false".getBytes(); + + public void write(boolean booleanValue) throws IOException { + out.write(booleanValue ? TRUE_BYTES : FALSE_BYTES); + } +} + + + diff --git a/src/main/java/com/jfinal/template/io/CharWriter.java b/src/main/java/com/jfinal/template/io/CharWriter.java new file mode 100644 index 0000000..e0a73c0 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/CharWriter.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.io.IOException; + +/** + * CharWriter + */ +public class CharWriter extends Writer { + + java.io.Writer out; + char[] chars; + + public CharWriter(int bufferSize) { + this.chars = new char[bufferSize]; + } + + public CharWriter init(java.io.Writer writer) { + this.out = writer; + return this; + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() { + try { + if (out != null) { + out.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + out = null; + } + } + + public void write(String str, int offset, int len) throws IOException { + while (len > chars.length) { + write(str, offset, chars.length); + offset += chars.length; + len -= chars.length; + } + + str.getChars(offset, offset + len, chars, 0); + out.write(chars, 0, len); + } + + public void write(String str) throws IOException { + write(str, 0, str.length()); + } + + public void write(StringBuilder stringBuilder, int offset, int len) throws IOException { + while (len > chars.length) { + write(stringBuilder, offset, chars.length); + offset += chars.length; + len -= chars.length; + } + + stringBuilder.getChars(offset, offset + len, chars, 0); + out.write(chars, 0, len); + } + + public void write(StringBuilder stringBuilder) throws IOException { + write(stringBuilder, 0, stringBuilder.length()); + } + + public void write(IWritable writable) throws IOException { + char[] data = writable.getChars(); + out.write(data, 0, data.length); + } + + public void write(int intValue) throws IOException { + IntegerWriter.write(this, intValue); + } + + public void write(long longValue) throws IOException { + LongWriter.write(this, longValue); + } + + public void write(double doubleValue) throws IOException { + FloatingWriter.write(this, doubleValue); + } + + public void write(float floatValue) throws IOException { + FloatingWriter.write(this, floatValue); + } + + private static final char[] TRUE_CHARS = "true".toCharArray(); + private static final char[] FALSE_CHARS = "false".toCharArray(); + + public void write(boolean booleanValue) throws IOException { + out.write(booleanValue ? TRUE_CHARS : FALSE_CHARS); + } +} + + + + + + diff --git a/src/main/java/com/jfinal/template/io/DateFormats.java b/src/main/java/com/jfinal/template/io/DateFormats.java new file mode 100644 index 0000000..4fdc4a6 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/DateFormats.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; + +/** + * DateFormats + */ +public class DateFormats { + + private Map map = new HashMap(); + + public SimpleDateFormat getDateFormat(String datePattern) { + SimpleDateFormat ret = map.get(datePattern); + if (ret == null) { + ret = new SimpleDateFormat(datePattern); + map.put(datePattern, ret); + } + return ret; + } +} + + + + + + diff --git a/src/main/java/com/jfinal/template/io/Encoder.java b/src/main/java/com/jfinal/template/io/Encoder.java new file mode 100644 index 0000000..2cf23f2 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/Encoder.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +/** + * Encoder + */ +public abstract class Encoder { + + public abstract float maxBytesPerChar(); + + public abstract int encode(char[] chars, int offset, int len, byte[] bytes); +} + diff --git a/src/main/java/com/jfinal/template/io/EncoderFactory.java b/src/main/java/com/jfinal/template/io/EncoderFactory.java new file mode 100644 index 0000000..2150546 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/EncoderFactory.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.nio.charset.Charset; +import com.jfinal.template.EngineConfig; + +/** + * EncoderFactory + */ +public class EncoderFactory { + + protected Charset charset = Charset.forName(EngineConfig.DEFAULT_ENCODING); + + void setEncoding(String encoding) { + charset = Charset.forName(encoding); + } + + public Encoder getEncoder() { + if (Charset.forName("UTF-8").equals(charset)) { + return Utf8Encoder.me; + } else { + return new JdkEncoder(charset); + } + } +} + + + + diff --git a/src/main/java/com/jfinal/template/FastStringWriter.java b/src/main/java/com/jfinal/template/io/FastStringWriter.java similarity index 70% rename from src/main/java/com/jfinal/template/FastStringWriter.java rename to src/main/java/com/jfinal/template/io/FastStringWriter.java index b835b89..4dc783f 100644 --- a/src/main/java/com/jfinal/template/FastStringWriter.java +++ b/src/main/java/com/jfinal/template/io/FastStringWriter.java @@ -14,16 +14,19 @@ * limitations under the License. */ -package com.jfinal.template; +package com.jfinal.template.io; -import java.io.IOException; import java.io.Writer; /** * FastStringWriter * - * 由 JDK 中 StringWriter 改造而成,将 StringBuffer 属性替换为 - * StringBuilder,避免 StringBuffer 的 synchronized 操作 + *
+ * 由 JDK 中 StringWriter 改造而来,在其基础之上做了如下改变:
+ * 1:StringBuffer 属性改为 StringBuilder,避免了前者的 synchronized 操作
+ * 2:添加了 MAX_SIZE 属性
+ * 3:去掉了 close() 方法声明中的 throws IOException,并添加了代码,原先该方法中无任何代码
+ * 
*/ public class FastStringWriter extends Writer { @@ -94,8 +97,20 @@ public class FastStringWriter extends Writer { } - public void close() throws IOException { - + static int MAX_SIZE = 1024 * 128; + + /** + * 由 StringWriter.close() 改造而来,原先该方法中无任何代码 ,改造如下: + * 1:去掉 throws IOException + * 2:添加 buf 空间释放处理逻辑 + * 3:添加 buf.setLength(0),以便于配合 ThreadLocal 回收利用 + */ + public void close() { + if (buf.length() > MAX_SIZE) { + buf = new StringBuilder(); // 释放空间占用过大的 buf + } else { + buf.setLength(0); + } } } diff --git a/src/main/java/com/jfinal/template/io/FloatingDecimal.java b/src/main/java/com/jfinal/template/io/FloatingDecimal.java new file mode 100644 index 0000000..bc17ea5 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/FloatingDecimal.java @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + */ + +package com.jfinal.template.io; + +public class FloatingDecimal{ + boolean isExceptional; + boolean isNegative; + int decExponent; + char digits[]; + int nDigits; + int bigIntExp; + int bigIntNBits; + boolean mustSetRoundDir = false; + boolean fromHex = false; + int roundDir = 0; // set by doubleValue + + /* + * Constants of the implementation + * Most are IEEE-754 related. + * (There are more really boring constants at the end.) + */ + static final long signMask = 0x8000000000000000L; + static final long expMask = 0x7ff0000000000000L; + static final long fractMask= ~(signMask|expMask); + static final int expShift = 52; + static final int expBias = 1023; + static final long fractHOB = ( 1L< 0L ) { // i.e. while ((v&highbit) == 0L ) + v <<= 1; + } + + int n = 0; + while (( v & lowbytes ) != 0L ){ + v <<= 8; + n += 8; + } + while ( v != 0L ){ + v <<= 1; + n += 1; + } + return n; + } + + /* + * Keep big powers of 5 handy for future reference. + */ + private static FDBigInt b5p[]; + + private static synchronized FDBigInt + big5pow( int p ){ + assert p >= 0 : p; // negative power of 5 + if ( b5p == null ){ + b5p = new FDBigInt[ p+1 ]; + }else if (b5p.length <= p ){ + FDBigInt t[] = new FDBigInt[ p+1 ]; + System.arraycopy( b5p, 0, t, 0, b5p.length ); + b5p = t; + } + if ( b5p[p] != null ) + return b5p[p]; + else if ( p < small5pow.length ) + return b5p[p] = new FDBigInt( small5pow[p] ); + else if ( p < long5pow.length ) + return b5p[p] = new FDBigInt( long5pow[p] ); + else { + // construct the value. + // recursively. + int q, r; + // in order to compute 5^p, + // compute its square root, 5^(p/2) and square. + // or, let q = p / 2, r = p -q, then + // 5^p = 5^(q+r) = 5^q * 5^r + q = p >> 1; + r = p - q; + FDBigInt bigq = b5p[q]; + if ( bigq == null ) + bigq = big5pow ( q ); + if ( r < small5pow.length ){ + return (b5p[p] = bigq.mult( small5pow[r] ) ); + }else{ + FDBigInt bigr = b5p[ r ]; + if ( bigr == null ) + bigr = big5pow( r ); + return (b5p[p] = bigq.mult( bigr ) ); + } + } + } + + // + // a common operation + // + private static FDBigInt + multPow52( FDBigInt v, int p5, int p2 ){ + if ( p5 != 0 ){ + if ( p5 < small5pow.length ){ + v = v.mult( small5pow[p5] ); + } else { + v = v.mult( big5pow( p5 ) ); + } + } + if ( p2 != 0 ){ + v.lshiftMe( p2 ); + } + return v; + } + + // + // another common operation + // + private static FDBigInt + constructPow52( int p5, int p2 ){ + FDBigInt v = new FDBigInt( big5pow( p5 ) ); + if ( p2 != 0 ){ + v.lshiftMe( p2 ); + } + return v; + } + + /* + * This is the easy subcase -- + * all the significant bits, after scaling, are held in lvalue. + * negSign and decExponent tell us what processing and scaling + * has already been done. Exceptional cases have already been + * stripped out. + * In particular: + * lvalue is a finite number (not Inf, nor NaN) + * lvalue > 0L (not zero, nor negative). + * + * The only reason that we develop the digits here, rather than + * calling on Long.toString() is that we can do it a little faster, + * and besides want to treat trailing 0s specially. If Long.toString + * changes, we should re-evaluate this strategy! + */ + private void + developLongDigits( int decExponent, long lvalue, long insignificant ){ + char digits[]; + int ndigits; + int digitno; + int c; + // + // Discard non-significant low-order bits, while rounding, + // up to insignificant value. + int i; + for ( i = 0; insignificant >= 10L; i++ ) + insignificant /= 10L; + if ( i != 0 ){ + long pow10 = long5pow[i] << i; // 10^i == 5^i * 2^i; + long residue = lvalue % pow10; + lvalue /= pow10; + decExponent += i; + if ( residue >= (pow10>>1) ){ + // round up based on the low-order bits we're discarding + lvalue++; + } + } + if ( lvalue <= Integer.MAX_VALUE ){ + assert lvalue > 0L : lvalue; // lvalue <= 0 + // even easier subcase! + // can do int arithmetic rather than long! + int ivalue = (int)lvalue; + ndigits = 10; + digits = (char[])(perThreadBuffer.get()); + digitno = ndigits-1; + c = ivalue%10; + ivalue /= 10; + while ( c == 0 ){ + decExponent++; + c = ivalue%10; + ivalue /= 10; + } + while ( ivalue != 0){ + digits[digitno--] = (char)(c+'0'); + decExponent++; + c = ivalue%10; + ivalue /= 10; + } + digits[digitno] = (char)(c+'0'); + } else { + // same algorithm as above (same bugs, too ) + // but using long arithmetic. + ndigits = 20; + digits = (char[])(perThreadBuffer.get()); + digitno = ndigits-1; + c = (int)(lvalue%10L); + lvalue /= 10L; + while ( c == 0 ){ + decExponent++; + c = (int)(lvalue%10L); + lvalue /= 10L; + } + while ( lvalue != 0L ){ + digits[digitno--] = (char)(c+'0'); + decExponent++; + c = (int)(lvalue%10L); + lvalue /= 10; + } + digits[digitno] = (char)(c+'0'); + } + char result []; + ndigits -= digitno; + result = new char[ ndigits ]; + System.arraycopy( digits, digitno, result, 0, ndigits ); + this.digits = result; + this.decExponent = decExponent+1; + this.nDigits = ndigits; + } + + // + // add one to the least significant digit. + // in the unlikely event there is a carry out, + // deal with it. + // assert that this will only happen where there + // is only one digit, e.g. (float)1e-44 seems to do it. + // + private void + roundup(){ + int i; + int q = digits[ i = (nDigits-1)]; + if ( q == '9' ){ + while ( q == '9' && i > 0 ){ + digits[i] = '0'; + q = digits[--i]; + } + if ( q == '9' ){ + // carryout! High-order 1, rest 0s, larger exp. + decExponent += 1; + digits[0] = '1'; + return; + } + // else fall through. + } + digits[i] = (char)(q+1); + } + + /* + * FIRST IMPORTANT CONSTRUCTOR: DOUBLE + */ + public FloatingDecimal( double d ) + { + long dBits = Double.doubleToLongBits( d ); + long fractBits; + int binExp; + int nSignificantBits; + + // discover and delete sign + if ( (dBits&signMask) != 0 ){ + isNegative = true; + dBits ^= signMask; + } else { + isNegative = false; + } + // Begin to unpack + // Discover obvious special cases of NaN and Infinity. + binExp = (int)( (dBits&expMask) >> expShift ); + fractBits = dBits&fractMask; + if ( binExp == (int)(expMask>>expShift) ) { + isExceptional = true; + if ( fractBits == 0L ){ + digits = infinity; + } else { + digits = notANumber; + isNegative = false; // NaN has no sign! + } + nDigits = digits.length; + return; + } + isExceptional = false; + // Finish unpacking + // Normalize denormalized numbers. + // Insert assumed high-order bit for normalized numbers. + // Subtract exponent bias. + if ( binExp == 0 ){ + if ( fractBits == 0L ){ + // not a denorm, just a 0! + decExponent = 0; + digits = zero; + nDigits = 1; + return; + } + while ( (fractBits&fractHOB) == 0L ){ + fractBits <<= 1; + binExp -= 1; + } + nSignificantBits = expShift + binExp +1; // recall binExp is - shift count. + binExp += 1; + } else { + fractBits |= fractHOB; + nSignificantBits = expShift+1; + } + binExp -= expBias; + // call the routine that actually does all the hard work. + dtoa( binExp, fractBits, nSignificantBits ); + } + + /* + * SECOND IMPORTANT CONSTRUCTOR: SINGLE + */ + public FloatingDecimal( float f ) + { + int fBits = Float.floatToIntBits( f ); + int fractBits; + int binExp; + int nSignificantBits; + + // discover and delete sign + if ( (fBits&singleSignMask) != 0 ){ + isNegative = true; + fBits ^= singleSignMask; + } else { + isNegative = false; + } + // Begin to unpack + // Discover obvious special cases of NaN and Infinity. + binExp = (int)( (fBits&singleExpMask) >> singleExpShift ); + fractBits = fBits&singleFractMask; + if ( binExp == (int)(singleExpMask>>singleExpShift) ) { + isExceptional = true; + if ( fractBits == 0L ){ + digits = infinity; + } else { + digits = notANumber; + isNegative = false; // NaN has no sign! + } + nDigits = digits.length; + return; + } + isExceptional = false; + // Finish unpacking + // Normalize denormalized numbers. + // Insert assumed high-order bit for normalized numbers. + // Subtract exponent bias. + if ( binExp == 0 ){ + if ( fractBits == 0 ){ + // not a denorm, just a 0! + decExponent = 0; + digits = zero; + nDigits = 1; + return; + } + while ( (fractBits&singleFractHOB) == 0 ){ + fractBits <<= 1; + binExp -= 1; + } + nSignificantBits = singleExpShift + binExp +1; // recall binExp is - shift count. + binExp += 1; + } else { + fractBits |= singleFractHOB; + nSignificantBits = singleExpShift+1; + } + binExp -= singleExpBias; + // call the routine that actually does all the hard work. + dtoa( binExp, ((long)fractBits)<<(expShift-singleExpShift), nSignificantBits ); + } + + private void + dtoa( int binExp, long fractBits, int nSignificantBits ) + { + int nFractBits; // number of significant bits of fractBits; + int nTinyBits; // number of these to the right of the point. + int decExp; + + // Examine number. Determine if it is an easy case, + // which we can do pretty trivially using float/long conversion, + // or whether we must do real work. + nFractBits = countBits( fractBits ); + nTinyBits = Math.max( 0, nFractBits - binExp - 1 ); + if ( binExp <= maxSmallBinExp && binExp >= minSmallBinExp ){ + // Look more closely at the number to decide if, + // with scaling by 10^nTinyBits, the result will fit in + // a long. + if ( (nTinyBits < long5pow.length) && ((nFractBits + n5bits[nTinyBits]) < 64 ) ){ + /* + * We can do this: + * take the fraction bits, which are normalized. + * (a) nTinyBits == 0: Shift left or right appropriately + * to align the binary point at the extreme right, i.e. + * where a long int point is expected to be. The integer + * result is easily converted to a string. + * (b) nTinyBits > 0: Shift right by expShift-nFractBits, + * which effectively converts to long and scales by + * 2^nTinyBits. Then multiply by 5^nTinyBits to + * complete the scaling. We know this won't overflow + * because we just counted the number of bits necessary + * in the result. The integer you get from this can + * then be converted to a string pretty easily. + */ + long halfULP; + if ( nTinyBits == 0 ) { + if ( binExp > nSignificantBits ){ + halfULP = 1L << ( binExp-nSignificantBits-1); + } else { + halfULP = 0L; + } + if ( binExp >= expShift ){ + fractBits <<= (binExp-expShift); + } else { + fractBits >>>= (expShift-binExp) ; + } + developLongDigits( 0, fractBits, halfULP ); + return; + } + /* + * The following causes excess digits to be printed + * out in the single-float case. Our manipulation of + * halfULP here is apparently not correct. If we + * better understand how this works, perhaps we can + * use this special case again. But for the time being, + * we do not. + * else { + * fractBits >>>= expShift+1-nFractBits; + * fractBits *= long5pow[ nTinyBits ]; + * halfULP = long5pow[ nTinyBits ] >> (1+nSignificantBits-nFractBits); + * developLongDigits( -nTinyBits, fractBits, halfULP ); + * return; + * } + */ + } + } + /* + * This is the hard case. We are going to compute large positive + * integers B and S and integer decExp, s.t. + * d = ( B / S ) * 10^decExp + * 1 <= B / S < 10 + * Obvious choices are: + * decExp = floor( log10(d) ) + * B = d * 2^nTinyBits * 10^max( 0, -decExp ) + * S = 10^max( 0, decExp) * 2^nTinyBits + * (noting that nTinyBits has already been forced to non-negative) + * I am also going to compute a large positive integer + * M = (1/2^nSignificantBits) * 2^nTinyBits * 10^max( 0, -decExp ) + * i.e. M is (1/2) of the ULP of d, scaled like B. + * When we iterate through dividing B/S and picking off the + * quotient bits, we will know when to stop when the remainder + * is <= M. + * + * We keep track of powers of 2 and powers of 5. + */ + + /* + * Estimate decimal exponent. (If it is small-ish, + * we could double-check.) + * + * First, scale the mantissa bits such that 1 <= d2 < 2. + * We are then going to estimate + * log10(d2) ~=~ (d2-1.5)/1.5 + log(1.5) + * and so we can estimate + * log10(d) ~=~ log10(d2) + binExp * log10(2) + * take the floor and call it decExp. + * FIXME -- use more precise constants here. It costs no more. + */ + double d2 = Double.longBitsToDouble( + expOne | ( fractBits &~ fractHOB ) ); + decExp = (int)Math.floor( + (d2-1.5D)*0.289529654D + 0.176091259 + (double)binExp * 0.301029995663981 ); + int B2, B5; // powers of 2 and powers of 5, respectively, in B + int S2, S5; // powers of 2 and powers of 5, respectively, in S + int M2, M5; // powers of 2 and powers of 5, respectively, in M + int Bbits; // binary digits needed to represent B, approx. + int tenSbits; // binary digits needed to represent 10*S, approx. + FDBigInt Sval, Bval, Mval; + + B5 = Math.max( 0, -decExp ); + B2 = B5 + nTinyBits + binExp; + + S5 = Math.max( 0, decExp ); + S2 = S5 + nTinyBits; + + M5 = B5; + M2 = B2 - nSignificantBits; + + /* + * the long integer fractBits contains the (nFractBits) interesting + * bits from the mantissa of d ( hidden 1 added if necessary) followed + * by (expShift+1-nFractBits) zeros. In the interest of compactness, + * I will shift out those zeros before turning fractBits into a + * FDBigInt. The resulting whole number will be + * d * 2^(nFractBits-1-binExp). + */ + fractBits >>>= (expShift+1-nFractBits); + B2 -= nFractBits-1; + int common2factor = Math.min( B2, S2 ); + B2 -= common2factor; + S2 -= common2factor; + M2 -= common2factor; + + /* + * HACK!! For exact powers of two, the next smallest number + * is only half as far away as we think (because the meaning of + * ULP changes at power-of-two bounds) for this reason, we + * hack M2. Hope this works. + */ + if ( nFractBits == 1 ) + M2 -= 1; + + if ( M2 < 0 ){ + // oops. + // since we cannot scale M down far enough, + // we must scale the other values up. + B2 -= M2; + S2 -= M2; + M2 = 0; + } + /* + * Construct, Scale, iterate. + * Some day, we'll write a stopping test that takes + * account of the asymmetry of the spacing of floating-point + * numbers below perfect powers of 2 + * 26 Sept 96 is not that day. + * So we use a symmetric test. + */ + char digits[] = this.digits = new char[18]; + int ndigit = 0; + boolean low, high; + long lowDigitDifference; + int q; + + /* + * Detect the special cases where all the numbers we are about + * to compute will fit in int or long integers. + * In these cases, we will avoid doing FDBigInt arithmetic. + * We use the same algorithms, except that we "normalize" + * our FDBigInts before iterating. This is to make division easier, + * as it makes our fist guess (quotient of high-order words) + * more accurate! + * + * Some day, we'll write a stopping test that takes + * account of the asymmetry of the spacing of floating-point + * numbers below perfect powers of 2 + * 26 Sept 96 is not that day. + * So we use a symmetric test. + */ + Bbits = nFractBits + B2 + (( B5 < n5bits.length )? n5bits[B5] : ( B5*3 )); + tenSbits = S2+1 + (( (S5+1) < n5bits.length )? n5bits[(S5+1)] : ( (S5+1)*3 )); + if ( Bbits < 64 && tenSbits < 64){ + if ( Bbits < 32 && tenSbits < 32){ + // wa-hoo! They're all ints! + int b = ((int)fractBits * small5pow[B5] ) << B2; + int s = small5pow[S5] << S2; + int m = small5pow[M5] << M2; + int tens = s * 10; + /* + * Unroll the first iteration. If our decExp estimate + * was too high, our first quotient will be zero. In this + * case, we discard it and decrement decExp. + */ + ndigit = 0; + q = b / s; + b = 10 * ( b % s ); + m *= 10; + low = (b < m ); + high = (b+m > tens ); + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + /* + * HACK! Java spec sez that we always have at least + * one digit after the . in either F- or E-form output. + * Thus we will need more than one digit if we're using + * E-form + */ + if ( decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = b / s; + b = 10 * ( b % s ); + m *= 10; + assert q < 10 : q; // excessively large digit + if ( m > 0L ){ + low = (b < m ); + high = (b+m > tens ); + } else { + // hack -- m might overflow! + // in this case, it is certainly > b, + // which won't + // and b+m > tens, too, since that has overflowed + // either! + low = true; + high = true; + } + digits[ndigit++] = (char)('0' + q); + } + lowDigitDifference = (b<<1) - tens; + } else { + // still good! they're all longs! + long b = (fractBits * long5pow[B5] ) << B2; + long s = long5pow[S5] << S2; + long m = long5pow[M5] << M2; + long tens = s * 10L; + /* + * Unroll the first iteration. If our decExp estimate + * was too high, our first quotient will be zero. In this + * case, we discard it and decrement decExp. + */ + ndigit = 0; + q = (int) ( b / s ); + b = 10L * ( b % s ); + m *= 10L; + low = (b < m ); + high = (b+m > tens ); + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + /* + * HACK! Java spec sez that we always have at least + * one digit after the . in either F- or E-form output. + * Thus we will need more than one digit if we're using + * E-form + */ + if ( decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = (int) ( b / s ); + b = 10 * ( b % s ); + m *= 10; + assert q < 10 : q; // excessively large digit + if ( m > 0L ){ + low = (b < m ); + high = (b+m > tens ); + } else { + // hack -- m might overflow! + // in this case, it is certainly > b, + // which won't + // and b+m > tens, too, since that has overflowed + // either! + low = true; + high = true; + } + digits[ndigit++] = (char)('0' + q); + } + lowDigitDifference = (b<<1) - tens; + } + } else { + FDBigInt tenSval; + int shiftBias; + + /* + * We really must do FDBigInt arithmetic. + * Fist, construct our FDBigInt initial values. + */ + Bval = multPow52( new FDBigInt( fractBits ), B5, B2 ); + Sval = constructPow52( S5, S2 ); + Mval = constructPow52( M5, M2 ); + + + // normalize so that division works better + Bval.lshiftMe( shiftBias = Sval.normalizeMe() ); + Mval.lshiftMe( shiftBias ); + tenSval = Sval.mult( 10 ); + /* + * Unroll the first iteration. If our decExp estimate + * was too high, our first quotient will be zero. In this + * case, we discard it and decrement decExp. + */ + ndigit = 0; + q = Bval.quoRemIteration( Sval ); + Mval = Mval.mult( 10 ); + low = (Bval.cmp( Mval ) < 0); + high = (Bval.add( Mval ).cmp( tenSval ) > 0 ); + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + /* + * HACK! Java spec sez that we always have at least + * one digit after the . in either F- or E-form output. + * Thus we will need more than one digit if we're using + * E-form + */ + if ( decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = Bval.quoRemIteration( Sval ); + Mval = Mval.mult( 10 ); + assert q < 10 : q; // excessively large digit + low = (Bval.cmp( Mval ) < 0); + high = (Bval.add( Mval ).cmp( tenSval ) > 0 ); + digits[ndigit++] = (char)('0' + q); + } + if ( high && low ){ + Bval.lshiftMe(1); + lowDigitDifference = Bval.cmp(tenSval); + } else + lowDigitDifference = 0L; // this here only for flow analysis! + } + this.decExponent = decExp+1; + this.digits = digits; + this.nDigits = ndigit; + /* + * Last digit gets rounded based on stopping condition. + */ + if ( high ){ + if ( low ){ + if ( lowDigitDifference == 0L ){ + // it's a tie! + // choose based on which digits we like. + if ( (digits[nDigits-1]&1) != 0 ) roundup(); + } else if ( lowDigitDifference > 0 ){ + roundup(); + } + } else { + roundup(); + } + } + } + + public String + toString(){ + // most brain-dead version + StringBuffer result = new StringBuffer( nDigits+8 ); + if ( isNegative ){ result.append( '-' ); } + if ( isExceptional ){ + result.append( digits, 0, nDigits ); + } else { + result.append( "0."); + result.append( digits, 0, nDigits ); + result.append('e'); + result.append( decExponent ); + } + return new String(result); + } + + public String toJavaFormatString() { + char result[] = (char[])(perThreadBuffer.get()); + int i = getChars(result); + return new String(result, 0, i); + } + + public int getChars(char[] result) { + assert nDigits <= 19 : nDigits; // generous bound on size of nDigits + int i = 0; + if (isNegative) { result[0] = '-'; i = 1; } + if (isExceptional) { + System.arraycopy(digits, 0, result, i, nDigits); + i += nDigits; + } else { + if (decExponent > 0 && decExponent < 8) { + // print digits.digits. + int charLength = Math.min(nDigits, decExponent); + System.arraycopy(digits, 0, result, i, charLength); + i += charLength; + if (charLength < decExponent) { + charLength = decExponent-charLength; + System.arraycopy(zero, 0, result, i, charLength); + i += charLength; + result[i++] = '.'; + result[i++] = '0'; + } else { + result[i++] = '.'; + if (charLength < nDigits) { + int t = nDigits - charLength; + System.arraycopy(digits, charLength, result, i, t); + i += t; + } else { + result[i++] = '0'; + } + } + } else if (decExponent <=0 && decExponent > -3) { + result[i++] = '0'; + result[i++] = '.'; + if (decExponent != 0) { + System.arraycopy(zero, 0, result, i, -decExponent); + i -= decExponent; + } + System.arraycopy(digits, 0, result, i, nDigits); + i += nDigits; + } else { + result[i++] = digits[0]; + result[i++] = '.'; + if (nDigits > 1) { + System.arraycopy(digits, 1, result, i, nDigits-1); + i += nDigits-1; + } else { + result[i++] = '0'; + } + result[i++] = 'E'; + int e; + if (decExponent <= 0) { + result[i++] = '-'; + e = -decExponent+1; + } else { + e = decExponent-1; + } + // decExponent has 1, 2, or 3, digits + if (e <= 9) { + result[i++] = (char)(e+'0'); + } else if (e <= 99) { + result[i++] = (char)(e/10 +'0'); + result[i++] = (char)(e%10 + '0'); + } else { + result[i++] = (char)(e/100+'0'); + e %= 100; + result[i++] = (char)(e/10+'0'); + result[i++] = (char)(e%10 + '0'); + } + } + } + return i; + } + + // Per-thread buffer for string/stringbuffer conversion + @SuppressWarnings("rawtypes") + private static ThreadLocal perThreadBuffer = new ThreadLocal() { + protected synchronized Object initialValue() { + return new char[26]; + } + }; + + private static final int small5pow[] = { + 1, + 5, + 5*5, + 5*5*5, + 5*5*5*5, + 5*5*5*5*5, + 5*5*5*5*5*5, + 5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5*5*5*5*5, + 5*5*5*5*5*5*5*5*5*5*5*5*5 + }; + + + private static final long long5pow[] = { + 1L, + 5L, + 5L*5, + 5L*5*5, + 5L*5*5*5, + 5L*5*5*5*5, + 5L*5*5*5*5*5, + 5L*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5, + }; + + // approximately ceil( log2( long5pow[i] ) ) + private static final int n5bits[] = { + 0, + 3, + 5, + 7, + 10, + 12, + 14, + 17, + 19, + 21, + 24, + 26, + 28, + 31, + 33, + 35, + 38, + 40, + 42, + 45, + 47, + 49, + 52, + 54, + 56, + 59, + 61, + }; + + private static final char infinity[] = { 'I', 'n', 'f', 'i', 'n', 'i', 't', 'y' }; + private static final char notANumber[] = { 'N', 'a', 'N' }; + private static final char zero[] = { '0', '0', '0', '0', '0', '0', '0', '0' }; +} + +/* + * A really, really simple bigint package + * tailored to the needs of floating base conversion. + */ +class FDBigInt { + int nWords; // number of words used + int data[]; // value: data[0] is least significant + + public FDBigInt( long v ){ + data = new int[2]; + data[0] = (int)v; + data[1] = (int)(v>>>32); + nWords = (data[1]==0) ? 1 : 2; + } + + public FDBigInt( FDBigInt other ){ + data = new int[nWords = other.nWords]; + System.arraycopy( other.data, 0, data, 0, nWords ); + } + + private FDBigInt( int [] d, int n ){ + data = d; + nWords = n; + } + + /* + * Left shift by c bits. + * Shifts this in place. + */ + public void + lshiftMe( int c )throws IllegalArgumentException { + if ( c <= 0 ){ + if ( c == 0 ) + return; // silly. + else + throw new IllegalArgumentException("negative shift count"); + } + int wordcount = c>>5; + int bitcount = c & 0x1f; + int anticount = 32-bitcount; + int t[] = data; + int s[] = data; + if ( nWords+wordcount+1 > t.length ){ + // reallocate. + t = new int[ nWords+wordcount+1 ]; + } + int target = nWords+wordcount; + int src = nWords-1; + if ( bitcount == 0 ){ + // special hack, since an anticount of 32 won't go! + System.arraycopy( s, 0, t, wordcount, nWords ); + target = wordcount-1; + } else { + t[target--] = s[src]>>>anticount; + while ( src >= 1 ){ + t[target--] = (s[src]<>>anticount); + } + t[target--] = s[src]<= 0 ){ + t[target--] = 0; + } + data = t; + nWords += wordcount + 1; + // may have constructed high-order word of 0. + // if so, trim it + while ( nWords > 1 && data[nWords-1] == 0 ) + nWords--; + } + + /* + * normalize this number by shifting until + * the MSB of the number is at 0x08000000. + * This is in preparation for quoRemIteration, below. + * The idea is that, to make division easier, we want the + * divisor to be "normalized" -- usually this means shifting + * the MSB into the high words sign bit. But because we know that + * the quotient will be 0 < q < 10, we would like to arrange that + * the dividend not span up into another word of precision. + * (This needs to be explained more clearly!) + */ + public int + normalizeMe() throws IllegalArgumentException { + int src; + int wordcount = 0; + int bitcount = 0; + int v = 0; + for ( src= nWords-1 ; src >= 0 && (v=data[src]) == 0 ; src--){ + wordcount += 1; + } + if ( src < 0 ){ + // oops. Value is zero. Cannot normalize it! + throw new IllegalArgumentException("zero value"); + } + /* + * In most cases, we assume that wordcount is zero. This only + * makes sense, as we try not to maintain any high-order + * words full of zeros. In fact, if there are zeros, we will + * simply SHORTEN our number at this point. Watch closely... + */ + nWords -= wordcount; + /* + * Compute how far left we have to shift v s.t. its highest- + * order bit is in the right place. Then call lshiftMe to + * do the work. + */ + if ( (v & 0xf0000000) != 0 ){ + // will have to shift up into the next word. + // too bad. + for( bitcount = 32 ; (v & 0xf0000000) != 0 ; bitcount-- ) + v >>>= 1; + } else { + while ( v <= 0x000fffff ){ + // hack: byte-at-a-time shifting + v <<= 8; + bitcount += 8; + } + while ( v <= 0x07ffffff ){ + v <<= 1; + bitcount += 1; + } + } + if ( bitcount != 0 ) + lshiftMe( bitcount ); + return bitcount; + } + + /* + * Multiply a FDBigInt by an int. + * Result is a new FDBigInt. + */ + public FDBigInt + mult( int iv ) { + long v = iv; + int r[]; + long p; + + // guess adequate size of r. + r = new int[ ( v * ((long)data[nWords-1]&0xffffffffL) > 0xfffffffL ) ? nWords+1 : nWords ]; + p = 0L; + for( int i=0; i < nWords; i++ ) { + p += v * ((long)data[i]&0xffffffffL); + r[i] = (int)p; + p >>>= 32; + } + if ( p == 0L){ + return new FDBigInt( r, nWords ); + } else { + r[nWords] = (int)p; + return new FDBigInt( r, nWords+1 ); + } + } + + /* + * Multiply a FDBigInt by another FDBigInt. + * Result is a new FDBigInt. + */ + public FDBigInt + mult( FDBigInt other ){ + // crudely guess adequate size for r + int r[] = new int[ nWords + other.nWords ]; + int i; + // I think I am promised zeros... + + for( i = 0; i < this.nWords; i++ ){ + long v = (long)this.data[i] & 0xffffffffL; // UNSIGNED CONVERSION + long p = 0L; + int j; + for( j = 0; j < other.nWords; j++ ){ + p += ((long)r[i+j]&0xffffffffL) + v*((long)other.data[j]&0xffffffffL); // UNSIGNED CONVERSIONS ALL 'ROUND. + r[i+j] = (int)p; + p >>>= 32; + } + r[i+j] = (int)p; + } + // compute how much of r we actually needed for all that. + for ( i = r.length-1; i> 0; i--) + if ( r[i] != 0 ) + break; + return new FDBigInt( r, i+1 ); + } + + /* + * Add one FDBigInt to another. Return a FDBigInt + */ + public FDBigInt + add( FDBigInt other ){ + int i; + int a[], b[]; + int n, m; + long c = 0L; + // arrange such that a.nWords >= b.nWords; + // n = a.nWords, m = b.nWords + if ( this.nWords >= other.nWords ){ + a = this.data; + n = this.nWords; + b = other.data; + m = other.nWords; + } else { + a = other.data; + n = other.nWords; + b = this.data; + m = this.nWords; + } + int r[] = new int[ n ]; + for ( i = 0; i < n; i++ ){ + c += (long)a[i] & 0xffffffffL; + if ( i < m ){ + c += (long)b[i] & 0xffffffffL; + } + r[i] = (int) c; + c >>= 32; // signed shift. + } + if ( c != 0L ){ + // oops -- carry out -- need longer result. + int s[] = new int[ r.length+1 ]; + System.arraycopy( r, 0, s, 0, r.length ); + s[i++] = (int)c; + return new FDBigInt( s, i ); + } + return new FDBigInt( r, i ); + } + + /* + * Compare FDBigInt with another FDBigInt. Return an integer + * >0: this > other + * 0: this == other + * <0: this < other + */ + public int + cmp( FDBigInt other ){ + int i; + if ( this.nWords > other.nWords ){ + // if any of my high-order words is non-zero, + // then the answer is evident + int j = other.nWords-1; + for ( i = this.nWords-1; i > j ; i-- ) + if ( this.data[i] != 0 ) return 1; + }else if ( this.nWords < other.nWords ){ + // if any of other's high-order words is non-zero, + // then the answer is evident + int j = this.nWords-1; + for ( i = other.nWords-1; i > j ; i-- ) + if ( other.data[i] != 0 ) return -1; + } else{ + i = this.nWords-1; + } + for ( ; i > 0 ; i-- ) + if ( this.data[i] != other.data[i] ) + break; + // careful! want unsigned compare! + // use brute force here. + int a = this.data[i]; + int b = other.data[i]; + if ( a < 0 ){ + // a is really big, unsigned + if ( b < 0 ){ + return a-b; // both big, negative + } else { + return 1; // b not big, answer is obvious; + } + } else { + // a is not really big + if ( b < 0 ) { + // but b is really big + return -1; + } else { + return a - b; + } + } + } + + /* + * Compute + * q = (int)( this / S ) + * this = 10 * ( this mod S ) + * Return q. + * This is the iteration step of digit development for output. + * We assume that S has been normalized, as above, and that + * "this" has been lshift'ed accordingly. + * Also assume, of course, that the result, q, can be expressed + * as an integer, 0 <= q < 10. + */ + public int + quoRemIteration( FDBigInt S )throws IllegalArgumentException { + // ensure that this and S have the same number of + // digits. If S is properly normalized and q < 10 then + // this must be so. + if ( nWords != S.nWords ){ + throw new IllegalArgumentException("disparate values"); + } + // estimate q the obvious way. We will usually be + // right. If not, then we're only off by a little and + // will re-add. + int n = nWords-1; + long q = ((long)data[n]&0xffffffffL) / (long)S.data[n]; + long diff = 0L; + for ( int i = 0; i <= n ; i++ ){ + diff += ((long)data[i]&0xffffffffL) - q*((long)S.data[i]&0xffffffffL); + data[i] = (int)diff; + diff >>= 32; // N.B. SIGNED shift. + } + if ( diff != 0L ) { + // damn, damn, damn. q is too big. + // add S back in until this turns +. This should + // not be very many times! + long sum = 0L; + while ( sum == 0L ){ + sum = 0L; + for ( int i = 0; i <= n; i++ ){ + sum += ((long)data[i]&0xffffffffL) + ((long)S.data[i]&0xffffffffL); + data[i] = (int) sum; + sum >>= 32; // Signed or unsigned, answer is 0 or 1 + } + /* + * Originally the following line read + * "if ( sum !=0 && sum != -1 )" + * but that would be wrong, because of the + * treatment of the two values as entirely unsigned, + * it would be impossible for a carry-out to be interpreted + * as -1 -- it would have to be a single-bit carry-out, or + * +1. + */ + assert sum == 0 || sum == 1 : sum; // carry out of division correction + q -= 1; + } + } + // finally, we can multiply this by 10. + // it cannot overflow, right, as the high-order word has + // at least 4 high-order zeros! + long p = 0L; + for ( int i = 0; i <= n; i++ ){ + p += 10*((long)data[i]&0xffffffffL); + data[i] = (int)p; + p >>= 32; // SIGNED shift. + } + assert p == 0L : p; // Carry out of *10 + return (int)q; + } + + public String + toString() { + StringBuffer r = new StringBuffer(30); + r.append('['); + int i = Math.min( nWords-1, data.length-1) ; + if ( nWords > data.length ){ + r.append( "("+data.length+"<"+nWords+"!)" ); + } + for( ; i> 0 ; i-- ){ + r.append( Integer.toHexString( data[i] ) ); + r.append(' '); + } + r.append( Integer.toHexString( data[0] ) ); + r.append(']'); + return new String( r ); + } +} diff --git a/src/main/java/com/jfinal/template/io/FloatingWriter.java b/src/main/java/com/jfinal/template/io/FloatingWriter.java new file mode 100644 index 0000000..254a6b2 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/FloatingWriter.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.io.IOException; + +/** + * FloatingWriter + */ +public class FloatingWriter { + + public static void write(ByteWriter byteWriter, double doubleValue) throws IOException { + FloatingDecimal fd = new FloatingDecimal(doubleValue); + char[] chars = byteWriter.chars; + byte[] bytes = byteWriter.bytes; + int len = fd.getChars(chars); + for (int i=0; i= 65536) { + q = i / 100; + // really: r = i - (q * 100); + r = i - ((q << 6) + (q << 5) + (q << 2)); + i = q; + buf [--charPos] = DigitOnes[r]; + buf [--charPos] = DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i <= 65536, i); + for (;;) { + q = (i * 52429) >>> (16+3); + r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... + buf [--charPos] = digits [r]; + i = q; + if (i == 0) break; + } + if (sign != 0) { + buf [--charPos] = sign; + } + } +} + + diff --git a/src/main/java/com/jfinal/template/io/JdkEncoder.java b/src/main/java/com/jfinal/template/io/JdkEncoder.java new file mode 100644 index 0000000..cf4371d --- /dev/null +++ b/src/main/java/com/jfinal/template/io/JdkEncoder.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; + +/** + * JdkEncoder + */ +public class JdkEncoder extends Encoder { + + private CharsetEncoder ce; + + public JdkEncoder(Charset charset) { + this.ce = charset.newEncoder(); + } + + public float maxBytesPerChar() { + return ce.maxBytesPerChar(); + } + + public int encode(char[] chars, int offset, int len, byte[] bytes) { + ce.reset(); + ByteBuffer bb = ByteBuffer.wrap(bytes); + CharBuffer cb = CharBuffer.wrap(chars, offset, len); + try { + CoderResult cr = ce.encode(cb, bb, true); + if (!cr.isUnderflow()) + cr.throwException(); + cr = ce.flush(bb); + if (!cr.isUnderflow()) + cr.throwException(); + return bb.position(); + } catch (CharacterCodingException x) { + // Substitution is always enabled, + // so this shouldn't happen + throw new RuntimeException("Encode error: " + x.getMessage(), x); + } + } +} + + + + + + diff --git a/src/main/java/com/jfinal/template/io/LongWriter.java b/src/main/java/com/jfinal/template/io/LongWriter.java new file mode 100644 index 0000000..d4eb60a --- /dev/null +++ b/src/main/java/com/jfinal/template/io/LongWriter.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + */ + +package com.jfinal.template.io; + +import java.io.IOException; + +public class LongWriter { + + private static final byte[] minValueBytes = "-9223372036854775808".getBytes(); + private static final char[] minValueChars = "-9223372036854775808".toCharArray(); + + public static void write(ByteWriter byteWriter, long value) throws IOException { + if (value == Long.MIN_VALUE) { + byteWriter.out.write(minValueBytes, 0, minValueBytes.length); + return ; + } + + int size = (value < 0) ? stringSize(-value) + 1 : stringSize(value); + char[] chars = byteWriter.chars; + byte[] bytes = byteWriter.bytes; + getChars(value, size, chars); + + // int len = Utf8Encoder.me.encode(chars, 0, size, bytes); + // byteWriter.out.write(bytes, 0, len); + + for (int j=0; j Integer.MAX_VALUE) { + q = i / 100; + // really: r = i - (q * 100); + r = (int)(i - ((q << 6) + (q << 5) + (q << 2))); + i = q; + buf[--charPos] = IntegerWriter.DigitOnes[r]; + buf[--charPos] = IntegerWriter.DigitTens[r]; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 >= 65536) { + q2 = i2 / 100; + // really: r = i2 - (q * 100); + r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); + i2 = q2; + buf[--charPos] = IntegerWriter.DigitOnes[r]; + buf[--charPos] = IntegerWriter.DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i2 <= 65536, i2); + for (;;) { + q2 = (i2 * 52429) >>> (16+3); + r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... + buf[--charPos] = IntegerWriter.digits[r]; + i2 = q2; + if (i2 == 0) break; + } + if (sign != 0) { + buf[--charPos] = sign; + } + } +} + + diff --git a/src/main/java/com/jfinal/template/io/Utf8Encoder.java b/src/main/java/com/jfinal/template/io/Utf8Encoder.java new file mode 100644 index 0000000..f7a1e14 --- /dev/null +++ b/src/main/java/com/jfinal/template/io/Utf8Encoder.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.nio.charset.MalformedInputException; + +/** + * Utf8Encoder + * + * http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/nio/cs/UTF_8.java?av=f + * http://grepcode.com/search?query=ArrayEncoder&start=0&entity=type&n= + */ +public class Utf8Encoder extends Encoder { + + public static final Utf8Encoder me = new Utf8Encoder(); + + public float maxBytesPerChar() { + return 3.0F; + } + + public int encode(char[] chars, int offset, int len, byte[] bytes) { + int sl = offset + len; + int dp = 0; + int dlASCII = dp + Math.min(len, bytes.length); + + // ASCII only optimized loop + while (dp < dlASCII && chars[offset] < '\u0080') { + bytes[dp++] = (byte) chars[offset++]; + } + + while (offset < sl) { + char c = chars[offset++]; + if (c < 0x80) { + // Have at most seven bits + bytes[dp++] = (byte) c; + } else if (c < 0x800) { + // 2 bytes, 11 bits + bytes[dp++] = (byte) (0xc0 | (c >> 6)); + bytes[dp++] = (byte) (0x80 | (c & 0x3f)); + } else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7 + final int uc; + int ip = offset - 1; + if (Character.isHighSurrogate(c)) { + if (sl - ip < 2) { + uc = -1; + } else { + char d = chars[ip + 1]; + if (Character.isLowSurrogate(d)) { + uc = Character.toCodePoint(c, d); + } else { + throw new RuntimeException("encode UTF8 error", new MalformedInputException(1)); + } + } + } else { + if (Character.isLowSurrogate(c)) { + throw new RuntimeException("encode UTF8 error", new MalformedInputException(1)); + } else { + uc = c; + } + } + + if (uc < 0) { + bytes[dp++] = (byte) '?'; + } else { + bytes[dp++] = (byte) (0xf0 | ((uc >> 18))); + bytes[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f)); + bytes[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f)); + bytes[dp++] = (byte) (0x80 | (uc & 0x3f)); + offset++; // 2 chars + } + } else { + // 3 bytes, 16 bits + bytes[dp++] = (byte) (0xe0 | ((c >> 12))); + bytes[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f)); + bytes[dp++] = (byte) (0x80 | (c & 0x3f)); + } + } + return dp; + } +} + + + + diff --git a/src/main/java/com/jfinal/template/io/Writer.java b/src/main/java/com/jfinal/template/io/Writer.java new file mode 100644 index 0000000..86fb10c --- /dev/null +++ b/src/main/java/com/jfinal/template/io/Writer.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +import java.io.IOException; +import java.util.Date; + +/** + * Writer + */ +public abstract class Writer { + + protected DateFormats formats = new DateFormats(); + + public abstract void flush() throws IOException; + + public abstract void close(); + + public abstract void write(IWritable writable) throws IOException; + + public abstract void write(String string, int offset, int length) throws IOException; + + public abstract void write(String string) throws IOException; + + public abstract void write(StringBuilder stringBuilder, int offset, int length) throws IOException; + + public abstract void write(StringBuilder stringBuilder) throws IOException; + + public abstract void write(boolean booleanValue) throws IOException; + + public abstract void write(int intValue) throws IOException; + + public abstract void write(long longValue) throws IOException; + + public abstract void write(double doubleValue) throws IOException; + + public abstract void write(float floatValue) throws IOException; + + public void write(short shortValue) throws IOException { + write((int)shortValue); + } + + public void write(Date date, String datePattern) throws IOException { + String str = formats.getDateFormat(datePattern).format(date); + write(str, 0, str.length()); + } +} + + + + + + + diff --git a/src/main/java/com/jfinal/template/io/WriterBuffer.java b/src/main/java/com/jfinal/template/io/WriterBuffer.java new file mode 100644 index 0000000..ba13e4d --- /dev/null +++ b/src/main/java/com/jfinal/template/io/WriterBuffer.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2011-2017, 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.io; + +/** + * WriterBuffer + */ +public class WriterBuffer { + + private static final int MIN_BUFFER_SIZE = 64; // 缓冲区最小 64 字节 + private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节 + + private int bufferSize = 2048; // 缓冲区大小 + + private EncoderFactory encoderFactory = new EncoderFactory(); + + private final ThreadLocal byteWriters = new ThreadLocal() { + protected ByteWriter initialValue() { + return new ByteWriter(encoderFactory.getEncoder(), bufferSize); + } + }; + + private final ThreadLocal charWriters = new ThreadLocal() { + protected CharWriter initialValue() { + return new CharWriter(bufferSize); + } + }; + + private final ThreadLocal fastStringWriters = new ThreadLocal() { + protected FastStringWriter initialValue() { + return new FastStringWriter(); + } + }; + + public ByteWriter getByteWriter(java.io.OutputStream outputStream) { + return byteWriters.get().init(outputStream); + } + + public CharWriter getCharWriter(java.io.Writer writer) { + return charWriters.get().init(writer); + } + + public FastStringWriter getFastStringWriter() { + return fastStringWriters.get(); + } + + public void setBufferSize(int bufferSize) { + if (bufferSize < MIN_BUFFER_SIZE || bufferSize > MAX_BUFFER_SIZE) { + throw new IllegalArgumentException("bufferSize must between " + (MIN_BUFFER_SIZE-1) + " and " + (MAX_BUFFER_SIZE+1)); + } + this.bufferSize = bufferSize; + } + + public void setEncoderFactory(EncoderFactory encoderFactory) { + if (encoderFactory == null) { + throw new IllegalArgumentException("encoderFactory can not be null"); + } + this.encoderFactory = encoderFactory; + } + + public void setEncoding(String encoding) { + encoderFactory.setEncoding(encoding); + } +} + + + + + + + + diff --git a/src/main/java/com/jfinal/template/stat/Lexer.java b/src/main/java/com/jfinal/template/stat/Lexer.java index 0a65251..3a83cff 100644 --- a/src/main/java/com/jfinal/template/stat/Lexer.java +++ b/src/main/java/com/jfinal/template/stat/Lexer.java @@ -95,7 +95,7 @@ class Lexer { StringBuilder para = null; Token idToken = null; Token paraToken = null; - while(true) { + while (true) { switch (state) { case 0: if (peek() == '#') { // # @@ -141,6 +141,14 @@ class Lexer { continue ; } + // 在支持 #seleif 的基础上,支持 #else if + if (symbol == Symbol.ELSE) { + if (foundFollowingIf()) { + id = "else if"; + symbol = Symbol.ELSEIF; + } + } + // 无参关键字指令 if (symbol.noPara()) { return addNoParaToken(new Token(symbol, id, beginRow)); @@ -199,6 +207,22 @@ class Lexer { } } + boolean foundFollowingIf() { + int p = forward; + while (CharTable.isBlank(buf[p])) {p++;} + if (buf[p++] == 'i') { + if (buf[p++] == 'f') { + while (CharTable.isBlank(buf[p])) {p++;} + // 要求出现 '(' 才认定解析成功,为了支持这种场景: #else if you ... + if (buf[p] == '(') { + forward = p; + return true; + } + } + } + return false; + } + /** * 调用者已确定以字母或下划线开头,故一定可以获取到 id值 */ @@ -405,7 +429,7 @@ class Lexer { } void skipBlanks() { - while(CharTable.isBlank(buf[forward])) { + while (CharTable.isBlank(buf[forward])) { next(); } } diff --git a/src/main/java/com/jfinal/template/stat/Parser.java b/src/main/java/com/jfinal/template/stat/Parser.java index 7a9d47f..4bb009e 100644 --- a/src/main/java/com/jfinal/template/stat/Parser.java +++ b/src/main/java/com/jfinal/template/stat/Parser.java @@ -18,6 +18,7 @@ package com.jfinal.template.stat; import java.util.ArrayList; import java.util.List; +import com.jfinal.template.Directive; import com.jfinal.template.Env; import com.jfinal.template.expr.ExprParser; import com.jfinal.template.expr.ast.ExprList; @@ -84,10 +85,10 @@ public class Parser { throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row)); } - public Stat parse() { + public StatList parse() { tokenList = new Lexer(content, fileName).scan(); tokenList.add(EOF); - Stat statList = statList(); + StatList statList = statList(); if (peek() != EOF) { throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row)); } @@ -122,7 +123,7 @@ public class Parser { switch (name.symbol) { case TEXT: move(); - return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row)); + return new Text(((TextToken)name).getContent(), env.getEngineConfig().getEncoding()).setLocation(getLocation(name.row)); case OUTPUT: move(); Token para = matchPara(name); @@ -171,9 +172,9 @@ public class Parser { String functionName = name.value(); move(); para = matchPara(name); - Stat stat = statList(); + statList = statList(); matchEnd(name); - return new Define(functionName, parseExprList(para), stat, getLocation(name.row)); + return new Define(functionName, parseExprList(para), statList, getLocation(name.row)); case CALL: functionName = name.value(); move(); @@ -206,7 +207,7 @@ public class Parser { move(); return Return.me; case ID: - Stat dire = env.getEngineConfig().getDirective(name.value()); + Class dire = env.getEngineConfig().getDirective(name.value()); if (dire == null) { throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row)); } @@ -215,9 +216,9 @@ public class Parser { para = matchPara(name); ret.setExprList(parseExprList(para)); - if (dire.hasEnd()) { + if (ret.hasEnd()) { statList = statList(); - ret.setStat(statList); + ret.setStat(statList.getActualStat()); matchEnd(name); } return ret; @@ -236,9 +237,9 @@ public class Parser { return new Location(fileName, row); } - private Stat createDirective(Stat dire, Token name) { + private Stat createDirective(Class dire, Token name) { try { - return dire.getClass().newInstance(); + return dire.newInstance(); } catch (Exception e) { throw new ParseException(e.getMessage(), getLocation(name.row), e); } diff --git a/src/main/java/com/jfinal/template/stat/Scope.java b/src/main/java/com/jfinal/template/stat/Scope.java index 95650a6..619aba6 100644 --- a/src/main/java/com/jfinal/template/stat/Scope.java +++ b/src/main/java/com/jfinal/template/stat/Scope.java @@ -90,8 +90,19 @@ public class Scope { */ public Object get(Object key) { for (Scope cur=this; cur!=null; cur=cur.parent) { - if (cur.data != null && cur.data.containsKey(key)) { - return cur.data.get(key); +// if (cur.data != null && cur.data.containsKey(key)) { +// return cur.data.get(key); +// } + + if (cur.data != null) { + Object ret = cur.data.get(key); + if (ret != null) { + return ret; + } + + if (cur.data.containsKey(key)) { + return null; + } } } // return null; @@ -229,6 +240,18 @@ public class Scope { } } } + + /** + * 自内向外在作用域栈中查找变量是否存在 + */ + public boolean exists(Object key) { + for (Scope cur=this; cur!=null; cur=cur.parent) { + if (cur.data != null && cur.data.containsKey(key)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/com/jfinal/template/stat/ast/Break.java b/src/main/java/com/jfinal/template/stat/ast/Break.java index c8cd24f..319ddd0 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Break.java +++ b/src/main/java/com/jfinal/template/stat/ast/Break.java @@ -16,8 +16,8 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** diff --git a/src/main/java/com/jfinal/template/stat/ast/Call.java b/src/main/java/com/jfinal/template/stat/ast/Call.java index 39af894..9dfcf2b 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Call.java +++ b/src/main/java/com/jfinal/template/stat/ast/Call.java @@ -16,10 +16,10 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** diff --git a/src/main/java/com/jfinal/template/stat/ast/Continue.java b/src/main/java/com/jfinal/template/stat/ast/Continue.java index 713475c..56be097 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Continue.java +++ b/src/main/java/com/jfinal/template/stat/ast/Continue.java @@ -16,8 +16,8 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** diff --git a/src/main/java/com/jfinal/template/stat/ast/Define.java b/src/main/java/com/jfinal/template/stat/ast/Define.java index 2f866d0..f83dda4 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Define.java +++ b/src/main/java/com/jfinal/template/stat/ast/Define.java @@ -16,7 +16,6 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.stat.Location; @@ -25,6 +24,7 @@ import com.jfinal.template.stat.Scope; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.Id; +import com.jfinal.template.io.Writer; /** * Define 定义模板函数: @@ -50,10 +50,10 @@ public class Define extends Stat { private String[] parameterNames; private Stat stat; - public Define(String functionName, ExprList exprList, Stat stat, Location location) { + public Define(String functionName, ExprList exprList, StatList statList, Location location) { setLocation(location); this.functionName = functionName; - this.stat = stat; + this.stat = statList.getActualStat(); Expr[] exprArray = exprList.getExprArray(); if (exprArray.length == 0) { diff --git a/src/main/java/com/jfinal/template/stat/ast/Else.java b/src/main/java/com/jfinal/template/stat/ast/Else.java index e10e8b2..b717c5a 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Else.java +++ b/src/main/java/com/jfinal/template/stat/ast/Else.java @@ -16,8 +16,8 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** @@ -27,8 +27,8 @@ public class Else extends Stat { private Stat stat; - public Else(Stat stat) { - this.stat = stat; + public Else(StatList statList) { + this.stat = statList.getActualStat(); } public void exec(Env env, Scope scope, Writer writer) { diff --git a/src/main/java/com/jfinal/template/stat/ast/ElseIf.java b/src/main/java/com/jfinal/template/stat/ast/ElseIf.java index 2a5db45..7d0ba85 100644 --- a/src/main/java/com/jfinal/template/stat/ast/ElseIf.java +++ b/src/main/java/com/jfinal/template/stat/ast/ElseIf.java @@ -16,10 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.Logic; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; @@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope; */ public class ElseIf extends Stat { - private ExprList cond; + private Expr cond; private Stat stat; private Stat elseIfOrElse; - public ElseIf(ExprList cond, Stat stat, Location location) { + public ElseIf(ExprList cond, StatList statList, Location location) { if (cond.length() == 0) { - throw new ParseException("The condition expression of #elseif statement can not be blank", location); + throw new ParseException("The condition expression of #else if statement can not be blank", location); } - this.cond = cond; - this.stat = stat; + this.cond = cond.getActualExpr(); + this.stat = statList.getActualStat(); } /** diff --git a/src/main/java/com/jfinal/template/stat/ast/For.java b/src/main/java/com/jfinal/template/stat/ast/For.java index d2b425b..1502ef1 100644 --- a/src/main/java/com/jfinal/template/stat/ast/For.java +++ b/src/main/java/com/jfinal/template/stat/ast/For.java @@ -16,12 +16,12 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import java.util.Iterator; import com.jfinal.template.Env; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ForCtrl; import com.jfinal.template.expr.ast.Logic; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Scope; @@ -38,12 +38,12 @@ import com.jfinal.template.stat.Scope; public class For extends Stat { private ForCtrl forCtrl; - private StatList statList; + private Stat stat; private Stat _else; public For(ForCtrl forCtrl, StatList statList, Stat _else) { this.forCtrl = forCtrl; - this.statList = statList; + this.stat = statList.getActualStat(); this._else = _else; } @@ -71,7 +71,7 @@ public class For extends Stat { String itemName = forCtrl.getId(); while(it.hasNext()) { scope.setLocal(itemName, it.next()); - statList.exec(env, scope, writer); + stat.exec(env, scope, writer); forIteratorStatus.nextState(); if (ctrl.isJump()) { @@ -108,7 +108,7 @@ public class For extends Stat { ctrl.setLocalAssignment(); for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) { ctrl.setWisdomAssignment(); - statList.exec(env, scope, writer); + stat.exec(env, scope, writer); ctrl.setLocalAssignment(); forLoopStatus.nextState(); diff --git a/src/main/java/com/jfinal/template/stat/ast/ForEntry.java b/src/main/java/com/jfinal/template/stat/ast/ForEntry.java index a308d56..fc16d7e 100644 --- a/src/main/java/com/jfinal/template/stat/ast/ForEntry.java +++ b/src/main/java/com/jfinal/template/stat/ast/ForEntry.java @@ -25,7 +25,7 @@ public class ForEntry implements Entry { private Entry entry; - public ForEntry(Entry entry) { + public void init(Entry entry) { this.entry = entry; } diff --git a/src/main/java/com/jfinal/template/stat/ast/ForIteratorStatus.java b/src/main/java/com/jfinal/template/stat/ast/ForIteratorStatus.java index 50b2bc9..3b08b86 100644 --- a/src/main/java/com/jfinal/template/stat/ast/ForIteratorStatus.java +++ b/src/main/java/com/jfinal/template/stat/ast/ForIteratorStatus.java @@ -60,11 +60,6 @@ public class ForIteratorStatus { @SuppressWarnings("unchecked") private void init(Object target) { - if (target == null) { - size = 0; - iterator = NullIterator.me; - return ; - } if (target instanceof Collection) { size = ((Collection)target).size(); iterator = ((Collection)target).iterator(); @@ -75,6 +70,11 @@ public class ForIteratorStatus { iterator = new MapIterator(((Map)target).entrySet().iterator()); return ; } + if (target == null) { // 必须放在 target.getClass() 之前,避免空指针异常 + size = 0; + iterator = NullIterator.me; + return ; + } if (target.getClass().isArray()) { size = Array.getLength(target); iterator = new ArrayIterator(target, size); @@ -148,6 +148,7 @@ public class ForIteratorStatus { class MapIterator implements Iterator> { private Iterator> iterator; + private ForEntry forEntry = new ForEntry(); public MapIterator(Iterator> iterator) { this.iterator = iterator; @@ -158,7 +159,8 @@ class MapIterator implements Iterator> { } public Entry next() { - return new ForEntry((Entry)iterator.next()); + forEntry.init(iterator.next()); + return forEntry; } public void remove() { diff --git a/src/main/java/com/jfinal/template/stat/ast/If.java b/src/main/java/com/jfinal/template/stat/ast/If.java index 116fb7a..473804f 100644 --- a/src/main/java/com/jfinal/template/stat/ast/If.java +++ b/src/main/java/com/jfinal/template/stat/ast/If.java @@ -16,10 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.Logic; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; @@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope; */ public class If extends Stat { - private ExprList cond; + private Expr cond; private Stat stat; private Stat elseIfOrElse; - public If(ExprList cond, Stat stat, Location location) { + public If(ExprList cond, StatList statList, Location location) { if (cond.length() == 0) { throw new ParseException("The condition expression of #if statement can not be blank", location); } - this.cond = cond; - this.stat = stat; + this.cond = cond.getActualExpr(); + this.stat = statList.getActualStat(); } /** diff --git a/src/main/java/com/jfinal/template/stat/ast/Include.java b/src/main/java/com/jfinal/template/stat/ast/Include.java index 7b6599d..f89dec9 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Include.java +++ b/src/main/java/com/jfinal/template/stat/ast/Include.java @@ -16,13 +16,13 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.EngineConfig; import com.jfinal.template.Env; import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Const; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.source.ISource; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Location; @@ -94,7 +94,7 @@ public class Include extends Stat { if (config.isDevMode()) { env.addSource(fileSource); } - this.stat = parser.parse(); + this.stat = parser.parse().getActualStat(); } catch (Exception e) { // 文件路径不正确抛出异常时添加 location 信息 throw new ParseException(e.getMessage(), location, e); diff --git a/src/main/java/com/jfinal/template/stat/ast/NullStat.java b/src/main/java/com/jfinal/template/stat/ast/NullStat.java new file mode 100644 index 0000000..5c62fb8 --- /dev/null +++ b/src/main/java/com/jfinal/template/stat/ast/NullStat.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2011-2017, 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.stat.ast; + +import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; +import com.jfinal.template.stat.Scope; + +/** + * NullStat + */ +public class NullStat extends Stat { + + public static final NullStat me = new NullStat(); + + private NullStat() {} + + public void exec(Env env, Scope scope, Writer writer) { + + } +} + + + + + 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 585aa8f..edfee36 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Output.java +++ b/src/main/java/com/jfinal/template/stat/ast/Output.java @@ -16,10 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; +import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; @@ -34,21 +35,41 @@ import com.jfinal.template.stat.Scope; */ public class Output extends Stat { - private ExprList exprList; + private Expr expr; public Output(ExprList exprList, Location location) { if (exprList.length() == 0) { throw new ParseException("The expression of output directive like #(expression) can not be blank", location); } - this.exprList = exprList; + this.expr = exprList.getActualExpr(); } public void exec(Env env, Scope scope, Writer writer) { try { - Object value = exprList.eval(scope); - if (value != null) { - String str = value.toString(); + Object value = expr.eval(scope); + + if (value instanceof String) { + String str = (String)value; writer.write(str, 0, str.length()); + } 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 instanceof Boolean) { + writer.write((Boolean)value); + } else if (value != null) { + writer.write(value.toString()); } } catch(TemplateException e) { throw e; diff --git a/src/main/java/com/jfinal/template/stat/ast/Return.java b/src/main/java/com/jfinal/template/stat/ast/Return.java index 8d2df73..f7d099c 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Return.java +++ b/src/main/java/com/jfinal/template/stat/ast/Return.java @@ -16,8 +16,8 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import com.jfinal.template.Env; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; /** 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 6b676d2..eb7502c 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Set.java +++ b/src/main/java/com/jfinal/template/stat/ast/Set.java @@ -16,11 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; 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.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; @@ -36,7 +36,7 @@ import com.jfinal.template.stat.Scope; */ public class Set extends Stat { - private ExprList exprList; + private Expr expr; public Set(ExprList exprList, Location location) { if (exprList.length() == 0) { @@ -48,12 +48,12 @@ public class Set extends Stat { throw new ParseException("#set directive only supports assignment expressions", location); } } - this.exprList = exprList; + this.expr = exprList.getActualExpr(); } public void exec(Env env, Scope scope, Writer writer) { scope.getCtrl().setWisdomAssignment(); - exprList.eval(scope); + expr.eval(scope); } } 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 b332b63..4810679 100644 --- a/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java +++ b/src/main/java/com/jfinal/template/stat/ast/SetGlobal.java @@ -16,11 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; 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.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; @@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope; */ public class SetGlobal extends Stat { - private ExprList exprList; + private Expr expr; public SetGlobal(ExprList exprList, Location location) { if (exprList.length() == 0) { @@ -45,14 +45,14 @@ public class SetGlobal extends Stat { throw new ParseException("#setGlobal directive only supports assignment expressions", location); } } - this.exprList = exprList; + this.expr = exprList.getActualExpr(); } public void exec(Env env, Scope scope, Writer writer) { Ctrl ctrl = scope.getCtrl(); try { ctrl.setGlobalAssignment(); - exprList.eval(scope); + expr.eval(scope); } finally { ctrl.setWisdomAssignment(); } 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 1cfedb4..4cf1c36 100644 --- a/src/main/java/com/jfinal/template/stat/ast/SetLocal.java +++ b/src/main/java/com/jfinal/template/stat/ast/SetLocal.java @@ -16,11 +16,11 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; 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.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ParseException; @@ -34,7 +34,7 @@ import com.jfinal.template.stat.Scope; */ public class SetLocal extends Stat { - final ExprList exprList; + final Expr expr; public SetLocal(ExprList exprList, Location location) { if (exprList.length() == 0) { @@ -46,14 +46,14 @@ public class SetLocal extends Stat { throw new ParseException("#setLocal directive only supports assignment expressions", location); } } - this.exprList = exprList; + this.expr = exprList.getActualExpr(); } public void exec(Env env, Scope scope, Writer writer) { Ctrl ctrl = scope.getCtrl(); try { ctrl.setLocalAssignment(); - exprList.eval(scope); + expr.eval(scope); } finally { ctrl.setWisdomAssignment(); } diff --git a/src/main/java/com/jfinal/template/stat/ast/Stat.java b/src/main/java/com/jfinal/template/stat/ast/Stat.java index dff9a24..52ce0da 100644 --- a/src/main/java/com/jfinal/template/stat/ast/Stat.java +++ b/src/main/java/com/jfinal/template/stat/ast/Stat.java @@ -17,10 +17,10 @@ package com.jfinal.template.stat.ast; import java.io.IOException; -import java.io.Writer; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.ExprList; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Scope; @@ -59,19 +59,6 @@ public abstract class Stat { throw new TemplateException(e.getMessage(), location, e); } } - - protected void write(Writer writer, char[] chars) { - try { - writer.write(chars, 0, chars.length); - } catch (IOException e) { - throw new TemplateException(e.getMessage(), location, e); - } - } } - - - - - diff --git a/src/main/java/com/jfinal/template/stat/ast/StatList.java b/src/main/java/com/jfinal/template/stat/ast/StatList.java index 0fc26ef..0658ed2 100644 --- a/src/main/java/com/jfinal/template/stat/ast/StatList.java +++ b/src/main/java/com/jfinal/template/stat/ast/StatList.java @@ -16,10 +16,10 @@ package com.jfinal.template.stat.ast; -import java.io.Writer; import java.util.List; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; +import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Scope; @@ -28,24 +28,44 @@ import com.jfinal.template.stat.Scope; */ public class StatList extends Stat { - public static final Stat[] NULL_STATS = new Stat[0]; + public static final Stat NULL_STAT = NullStat.me; + public static final Stat[] NULL_STAT_ARRAY = new Stat[0]; + private Stat[] statArray; public StatList(List statList) { if (statList.size() > 0) { this.statArray = statList.toArray(new Stat[statList.size()]); } else { - this.statArray = NULL_STATS; + this.statArray = NULL_STAT_ARRAY; + } + } + + /** + * 持有 StatList 的指令可以通过此方法提升 AST 执行性能 + * 1:当 statArray.length > 1 时返回 StatList 自身 + * 2:当 statArray.length == 1 时返回 statArray[0] + * 3:其它情况返回 NullStat + * + * 意义在于,当满足前面两个条件时,避免掉了 StatList.exec(...) 方法中的判断与循环 + */ + public Stat getActualStat() { + if (statArray.length > 1) { + return this; + } else if (statArray.length == 1) { + return statArray[0]; + } else { + return NULL_STAT; } } public void exec(Env env, Scope scope, Writer writer) { Ctrl ctrl = scope.getCtrl(); - for (Stat stat : statArray) { + for (int i=0; i