enjoy 3.3 release ^_^

This commit is contained in:
James 2017-11-21 22:43:34 +08:00
parent 28eb105ffa
commit 61aa1d2082
70 changed files with 3378 additions and 299 deletions

View File

@ -4,7 +4,7 @@
<artifactId>enjoy</artifactId> <artifactId>enjoy</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>enjoy</name> <name>enjoy</name>
<version>3.3-SNAPSHOT</version> <version>3.3</version>
<url>http://www.jfinal.com</url> <url>http://www.jfinal.com</url>
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description> <description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
@ -121,7 +121,7 @@
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<skip>false</skip> <skip>true</skip>
</configuration> </configuration>
</plugin> </plugin>

View File

@ -16,12 +16,12 @@
package com.jfinal.kit; package com.jfinal.kit;
import java.io.Writer;
import java.util.Map; import java.util.Map;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Engine; import com.jfinal.template.Engine;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.Template; import com.jfinal.template.Template;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**
@ -42,7 +42,7 @@ public class ElKit {
private static final String RETURN_VALUE_KEY = "_RETURN_VALUE_"; private static final String RETURN_VALUE_KEY = "_RETURN_VALUE_";
static { static {
engine.addDirective("eval", new InnerEvalDirective()); engine.addDirective("eval", InnerEvalDirective.class);
} }
public Engine getEngine() { public Engine getEngine() {
@ -57,7 +57,7 @@ public class ElKit {
public static <T> T eval(String expr, Map<?, ?> data) { public static <T> T eval(String expr, Map<?, ?> data) {
String stringTemplate = "#eval(" + expr + ")"; String stringTemplate = "#eval(" + expr + ")";
Template template = engine.getTemplateByString(stringTemplate); Template template = engine.getTemplateByString(stringTemplate);
template.render(data, null); template.render(data, (java.io.Writer)null);
return (T)data.get(RETURN_VALUE_KEY); return (T)data.get(RETURN_VALUE_KEY);
} }

View File

@ -20,10 +20,22 @@ import java.security.MessageDigest;
public class HashKit { 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 java.security.SecureRandom random = new java.security.SecureRandom();
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
private static final char[] CHAR_ARRAY = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".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<size; i++) {
hash ^= key.charAt(i);
hash *= FNV_PRIME_64;
}
return hash;
}
public static String md5(String srcStr){ public static String md5(String srcStr){
return hash("MD5", srcStr); return hash("MD5", srcStr);
} }

View File

@ -281,12 +281,24 @@ public class Engine {
/** /**
* Add directive * Add directive
* <pre>
* 示例
* addDirective("now", NowDirective.class)
* </pre>
*/ */
public Engine addDirective(String directiveName, Directive directive) { public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass) {
config.addDirective(directiveName, directive); config.addDirective(directiveName, directiveClass);
return this; return this;
} }
/**
* 该方法已被 addDirective(String, Class<? extends Directive>) 所代替
*/
@Deprecated
public Engine addDirective(String directiveName, Directive directive) {
return addDirective(directiveName, directive.getClass());
}
/** /**
* Remove directive * Remove directive
*/ */
@ -445,6 +457,11 @@ public class Engine {
return config.getEncoding(); return config.getEncoding();
} }
public Engine setWriterBufferSize(int bufferSize) {
config.setWriterBufferSize(bufferSize);
return this;
}
/** /**
* Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效 * Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效
* 但如果在 devMode 之下并不希望对 addSharedFunction(...) * 但如果在 devMode 之下并不希望对 addSharedFunction(...)
@ -466,7 +483,7 @@ public class Engine {
} }
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) { public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);; MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);
} }
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) { public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {

View File

@ -26,7 +26,9 @@ import com.jfinal.kit.StrKit;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.SharedMethodKit; import com.jfinal.template.expr.ast.SharedMethodKit;
import com.jfinal.template.ext.directive.*; 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.FileSource;
import com.jfinal.template.source.FileSourceFactory; import com.jfinal.template.source.FileSourceFactory;
import com.jfinal.template.source.ISource; 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.Parser;
import com.jfinal.template.stat.ast.Define; import com.jfinal.template.stat.ast.Define;
import com.jfinal.template.stat.ast.Output; import com.jfinal.template.stat.ast.Output;
import com.jfinal.template.stat.ast.Stat;
/** /**
* EngineConfig * EngineConfig
@ -45,6 +46,8 @@ public class EngineConfig {
public static final String DEFAULT_ENCODING = "UTF-8"; public static final String DEFAULT_ENCODING = "UTF-8";
WriterBuffer writerBuffer = new WriterBuffer();
private Map<String, Define> sharedFunctionMap = new HashMap<String, Define>(); private Map<String, Define> sharedFunctionMap = new HashMap<String, Define>();
private List<ISource> sharedFunctionSourceList = new ArrayList<ISource>(); // for devMode only private List<ISource> sharedFunctionSourceList = new ArrayList<ISource>(); // for devMode only
@ -52,7 +55,7 @@ public class EngineConfig {
private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me; private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me;
private ISourceFactory sourceFactory = new FileSourceFactory(); private ISourceFactory sourceFactory = new FileSourceFactory();
private Map<String, Stat> directiveMap = new HashMap<String, Stat>(); private Map<String, Class<? extends Directive>> directiveMap = new HashMap<String, Class<? extends Directive>>();
private SharedMethodKit sharedMethodKit = new SharedMethodKit(); private SharedMethodKit sharedMethodKit = new SharedMethodKit();
private boolean devMode = false; private boolean devMode = false;
@ -63,14 +66,16 @@ public class EngineConfig {
public EngineConfig() { public EngineConfig() {
// Add official directive of Template Engine // Add official directive of Template Engine
addDirective("render", new RenderDirective()); addDirective("render", RenderDirective.class);
addDirective("date", new DateDirective()); addDirective("date", DateDirective.class);
addDirective("escape", new EscapeDirective()); addDirective("escape", EscapeDirective.class);
addDirective("string", new StringDirective()); addDirective("string", StringDirective.class);
addDirective("random", new RandomDirective()); addDirective("random", RandomDirective.class);
addDirective("number", NumberDirective.class);
// Add official shared method of Template Engine // Add official shared method of Template Engine
// addSharedMethod(new Json()); // addSharedMethod(new Json());
addSharedMethod(new SharedMethodLib());
} }
/** /**
@ -268,6 +273,17 @@ public class EngineConfig {
throw new IllegalArgumentException("encoding can not be blank"); throw new IllegalArgumentException("encoding can not be blank");
} }
this.encoding = encoding; 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() { public String getEncoding() {
@ -289,20 +305,25 @@ public class EngineConfig {
this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode; 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<? extends Directive> directiveClass) {
if (StrKit.isBlank(directiveName)) { if (StrKit.isBlank(directiveName)) {
throw new IllegalArgumentException("directive name can not be blank"); throw new IllegalArgumentException("directive name can not be blank");
} }
if (directive == null) { if (directiveClass == null) {
throw new IllegalArgumentException("directive can not be null"); throw new IllegalArgumentException("directiveClass can not be null");
} }
if (directiveMap.containsKey(directiveName)) { if (directiveMap.containsKey(directiveName)) {
throw new IllegalArgumentException("directive already exists : " + directiveName); throw new IllegalArgumentException("directive already exists : " + directiveName);
} }
directiveMap.put(directiveName, directive); directiveMap.put(directiveName, directiveClass);
} }
public Stat getDirective(String directiveName) { public Class<? extends Directive> getDirective(String directiveName) {
return directiveMap.get(directiveName); return directiveMap.get(directiveName);
} }

View File

@ -48,6 +48,10 @@ public class Env {
return engineConfig; return engineConfig;
} }
public boolean isDevMode() {
return engineConfig.isDevMode();
}
/** /**
* Add template function * Add template function
*/ */

View File

@ -16,8 +16,12 @@
package com.jfinal.template; package com.jfinal.template;
import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import java.util.Map; 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.Scope;
import com.jfinal.template.stat.ast.Stat; import com.jfinal.template.stat.ast.Stat;
@ -42,27 +46,57 @@ public class Template {
this.ast = ast; 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 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
*/
public void render(OutputStream outputStream) {
render(null, outputStream);
}
/** /**
* 渲染到 Writer 中去 * 渲染到 Writer 中去
*/ */
public void render(Map<?, ?> data, Writer 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 中去 <br> * 支持无 data 参数渲染到 Writer 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景<br> * 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
* 此外其它所有 render 方法也支持传入 null data 参数
*/ */
public void render(Writer writer) { 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) { public String renderToString(Map<?, ?> data) {
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), fastStringWriter); 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(); return fsw.getBuffer();
} }
/**
* 渲染到 String 中去
*/
public String renderToString(Map<?, ?> data) {
return renderToStringBuilder(data).toString();
}
public boolean isModified() { public boolean isModified() {
return env.isSourceListModified(); return env.isSourceListModified();
} }

View File

@ -100,6 +100,8 @@ public class ExprParser {
public ForCtrl parseForCtrl() { public ForCtrl parseForCtrl() {
Expr forCtrl = parse(false); Expr forCtrl = parse(false);
// 可能返回 ExprList.NULL_EXPR_LIST必须做判断
if (forCtrl instanceof ForCtrl) { if (forCtrl instanceof ForCtrl) {
return (ForCtrl)forCtrl; return (ForCtrl)forCtrl;
} else { } else {
@ -124,7 +126,7 @@ public class ExprParser {
/** /**
* exprList : expr (',' expr)* * exprList : expr (',' expr)*
*/ */
Expr exprList() { ExprList exprList() {
List<Expr> exprList = new ArrayList<Expr>(); List<Expr> exprList = new ArrayList<Expr>();
while (true) { while (true) {
Expr stat = expr(); Expr stat = expr();
@ -346,7 +348,7 @@ public class ExprParser {
return new StaticMethod(clazz, memberName, location); return new StaticMethod(clazz, memberName, location);
} }
ExprList exprList = (ExprList)exprList(); ExprList exprList = exprList();
match(Sym.RPAREN); match(Sym.RPAREN);
return new StaticMethod(clazz, memberName, exprList, location); return new StaticMethod(clazz, memberName, exprList, location);
} }
@ -383,7 +385,7 @@ public class ExprParser {
return indexMethodField(sharedMethod); return indexMethodField(sharedMethod);
} }
ExprList exprList = (ExprList)exprList(); ExprList exprList = exprList();
SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location); SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location);
match(Sym.RPAREN); match(Sym.RPAREN);
return indexMethodField(sharedMethod); return indexMethodField(sharedMethod);
@ -433,7 +435,7 @@ public class ExprParser {
} }
// expr '.' ID '(' exprList ')' // expr '.' ID '(' exprList ')'
ExprList exprList = (ExprList)exprList(); ExprList exprList = exprList();
match(Sym.RPAREN); match(Sym.RPAREN);
expr = new Method(expr, tok.value(), exprList, location); expr = new Method(expr, tok.value(), exprList, location);
} }
@ -498,7 +500,7 @@ public class ExprParser {
move(); move();
return new Array(ExprList.NULL_EXPR_ARRAY, location); return new Array(ExprList.NULL_EXPR_ARRAY, location);
} }
ExprList exprList = (ExprList)exprList(); ExprList exprList = exprList();
if (exprList.length() == 1 && peek().sym == Sym.RANGE) { if (exprList.length() == 1 && peek().sym == Sym.RANGE) {
move(); move();
Expr end = expr(); Expr end = expr();
@ -560,13 +562,13 @@ public class ExprParser {
/** /**
* forControl : ID : expr | exprList? ';' expr? ';' exprList? * forControl : ID : expr | exprList? ';' expr? ';' exprList?
*/ */
Expr forCtrl() { ForCtrl forCtrl() {
ExprList exprList = (ExprList)exprList(); ExprList exprList = exprList();
if (peek().sym == Sym.SEMICOLON) { if (peek().sym == Sym.SEMICOLON) {
move(); move();
Expr cond = expr(); Expr cond = expr();
match(Sym.SEMICOLON); match(Sym.SEMICOLON);
Expr update = exprList(); ExprList update = exprList();
return new ForCtrl(exprList, cond, update, location); return new ForCtrl(exprList, cond, update, location);
} }

View File

@ -94,9 +94,11 @@ public class Compare extends Expr {
case Arith.FLOAT: case Arith.FLOAT:
// 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级 // 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级
// return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue()); // return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue());
return l.floatValue() == r.floatValue();
case Arith.DOUBLE: case Arith.DOUBLE:
// 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级 // 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级
// return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue()); // return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() == r.doubleValue();
case Arith.BIGDECIMAL: case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r); BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) == 0; return (bd[0]).compareTo(bd[1]) == 0;
@ -120,8 +122,10 @@ public class Compare extends Expr {
return l.longValue() > r.longValue(); return l.longValue() > r.longValue();
case Arith.FLOAT: case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue()); // return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue());
return l.floatValue() > r.floatValue();
case Arith.DOUBLE: case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue()); // return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() > r.doubleValue();
case Arith.BIGDECIMAL: case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r); BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) > 0; return (bd[0]).compareTo(bd[1]) > 0;
@ -150,8 +154,10 @@ public class Compare extends Expr {
return l.longValue() >= r.longValue(); return l.longValue() >= r.longValue();
case Arith.FLOAT: case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue()); // return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue());
return l.floatValue() >= r.floatValue();
case Arith.DOUBLE: case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue()); // return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() >= r.doubleValue();
case Arith.BIGDECIMAL: case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r); BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) >= 0; return (bd[0]).compareTo(bd[1]) >= 0;
@ -180,8 +186,10 @@ public class Compare extends Expr {
return l.longValue() < r.longValue(); return l.longValue() < r.longValue();
case Arith.FLOAT: case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue()); // return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue());
return l.floatValue() < r.floatValue();
case Arith.DOUBLE: case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue()); // return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() < r.doubleValue();
case Arith.BIGDECIMAL: case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r); BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) < 0; return (bd[0]).compareTo(bd[1]) < 0;
@ -210,8 +218,10 @@ public class Compare extends Expr {
return l.longValue() <= r.longValue(); return l.longValue() <= r.longValue();
case Arith.FLOAT: case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue()); // return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue());
return l.floatValue() <= r.floatValue();
case Arith.DOUBLE: case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue()); // return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() <= r.doubleValue();
case Arith.BIGDECIMAL: case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r); BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) <= 0; return (bd[0]).compareTo(bd[1]) <= 0;

View File

@ -72,10 +72,6 @@ public class Const extends Expr {
return value; return value;
} }
public String toString() {
return value.toString();
}
public boolean isStr() { public boolean isStr() {
return type == Sym.STR; return type == Sym.STR;
} }
@ -139,6 +135,10 @@ public class Const extends Expr {
public Double getDouble() { public Double getDouble() {
return (Double)value; return (Double)value;
} }
public String toString() {
return value != null ? value.toString() : "null";
}
} }

View File

@ -16,6 +16,7 @@
package com.jfinal.template.expr.ast; package com.jfinal.template.expr.ast;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -25,24 +26,40 @@ import com.jfinal.template.stat.Scope;
*/ */
public class ExprList extends Expr { 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 Expr[] NULL_EXPR_ARRAY = new Expr[0];
public static final ExprList NULL_EXPR_LIST = new ExprList(new ArrayList<Expr>(0));
public static final Object[] NULL_OBJECT_ARRAY = new Object[0]; public static final Object[] NULL_OBJECT_ARRAY = new Object[0];
public static final ExprList NULL_EXPR_LIST = new ExprList();
private Expr[] exprArray; private Expr[] exprArray;
private ExprList() {
this.exprArray = NULL_EXPR_ARRAY;
}
public ExprList(List<Expr> exprList) { public ExprList(List<Expr> exprList) {
if (exprList != null && exprList.size() > 0) { if (exprList.size() > 0) {
exprArray = exprList.toArray(new Expr[exprList.size()]); exprArray = exprList.toArray(new Expr[exprList.size()]);
} else { } else {
exprArray = NULL_EXPR_ARRAY; 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() { public Expr[] getExprArray() {
return exprArray; return exprArray;
} }
@ -54,6 +71,14 @@ public class ExprList extends Expr {
return exprArray[index]; 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() { public int length() {
return exprArray.length; return exprArray.length;
} }
@ -62,11 +87,20 @@ public class ExprList extends Expr {
* 对所有表达式求值只返回最后一个表达式的值 * 对所有表达式求值只返回最后一个表达式的值
*/ */
public Object eval(Scope scope) { public Object eval(Scope scope) {
Object ret = null; // 优化绝大多数情况下 length 等于 1
for (Expr expr : exprArray) { if (exprArray.length == 1) {
ret = expr.eval(scope); return exprArray[0].eval(scope);
} }
return ret;
if (exprArray.length == 0) {
return null;
}
int end = exprArray.length - 1;
for (int i=0; i<end; i++) {
exprArray[i].eval(scope);
}
return exprArray[end].eval(scope);
} }
/** /**
@ -87,3 +121,4 @@ public class ExprList extends Expr {

View File

@ -17,6 +17,7 @@
package com.jfinal.template.expr.ast; package com.jfinal.template.expr.ast;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit; import com.jfinal.kit.StrKit;
// import com.jfinal.plugin.activerecord.Model; // import com.jfinal.plugin.activerecord.Model;
// import com.jfinal.plugin.activerecord.Record; // import com.jfinal.plugin.activerecord.Record;
@ -40,6 +41,7 @@ public class Field extends Expr {
private Expr expr; private Expr expr;
private String fieldName; private String fieldName;
private String getterName; private String getterName;
private long getterNameHash;
public Field(Expr expr, String fieldName, Location location) { public Field(Expr expr, String fieldName, Location location) {
if (expr == null) { if (expr == null) {
@ -48,6 +50,8 @@ public class Field extends Expr {
this.expr = expr; this.expr = expr;
this.fieldName = fieldName; this.fieldName = fieldName;
this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName); this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
// fnv1a64 hash 到比 String.hashCode() 更大的 long 值范围
this.getterNameHash = HashKit.fnv1a64(getterName);
this.location = location; this.location = location;
} }
@ -65,7 +69,8 @@ public class Field extends Expr {
} }
Class<?> targetClass = target.getClass(); Class<?> targetClass = target.getClass();
String key = FieldKit.getFieldKey(targetClass, getterName); Long key = buildFieldKey(targetClass);
MethodInfo getter; MethodInfo getter;
try { try {
getter = MethodKit.getGetterMethod(key, targetClass, getterName); 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); throw new TemplateException("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location);
} }
private Long buildFieldKey(Class<?> targetClass) {
return targetClass.getName().hashCode() ^ getterNameHash;
}
} }

View File

@ -17,21 +17,21 @@
package com.jfinal.template.expr.ast; package com.jfinal.template.expr.ast;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap; import java.util.HashMap;
/** /**
* FieldKit * FieldKit
*/ */
public class FieldKit { public class FieldKit {
private static final ConcurrentHashMap<String, Object> fieldCache = new ConcurrentHashMap<String, Object>(); private static final HashMap<Long, Object> fieldCache = new HashMap<Long, Object>();
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); Object field = fieldCache.get(key);
if (field == null) { if (field == null) {
field = doGetField(targetClass, fieldName); field = doGetField(targetClass, fieldName);
if (field != null) { if (field != null) {
fieldCache.putIfAbsent(key, field); fieldCache.put(key, field);
} else { } else {
// 对于不存在的 Field只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险 // 对于不存在的 Field只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险
fieldCache.put(key, Boolean.FALSE); fieldCache.put(key, Boolean.FALSE);
@ -49,14 +49,6 @@ public class FieldKit {
} }
return null; return null;
} }
/**
* 获取 Field 用于缓存的 key
*/
public static String getFieldKey(Class<?> targetClass, String getterName) {
return new StringBuilder(64).append(targetClass.getName())
.append('.').append(getterName).toString();
}
} }

View File

@ -58,10 +58,10 @@ public class ForCtrl extends Expr {
/** /**
* exprList? ';' expr? ';' exprList? * exprList? ';' expr? ';' exprList?
*/ */
public ForCtrl(Expr init, Expr cond, Expr update, Location location) { public ForCtrl(ExprList init, Expr cond, ExprList update, Location location) {
this.init = init; this.init = init.getActualExpr();
this.cond = cond; this.cond = cond;
this.update = update; this.update = update.getActualExpr();
this.id = null; this.id = null;
this.expr = null; this.expr = null;
this.location = location; this.location = location;

View File

@ -37,6 +37,16 @@ public class Logic extends Expr {
private Expr left; // ! 运算没有 left 参数 private Expr left; // ! 运算没有 left 参数
private Expr right; private Expr right;
// 默认为新工作模式
private static boolean newWorkMode = true;
/**
* 设置为旧工作模式为了兼容 jfinal 3.3 之前的版本
*/
@Deprecated
public static void setToOldWorkMode() {
newWorkMode = false;
}
/** /**
* 构造 || && 结点 * 构造 || && 结点
*/ */
@ -95,32 +105,29 @@ public class Logic extends Expr {
* 规则 * 规则
* 1null 返回 false * 1null 返回 false
* 2boolean 类型原值返回 * 2boolean 类型原值返回
* 3MapConnection(List被包括在内) 返回 size() > 0 * 3StringStringBuilder 等一切继承自 CharSequence 类的对象返回 length > 0
* 4数组返回 length > 0 * 4其它返回 true
* 5StringStringBuilderStringBuffer 等继承自 CharSequence 类的对象返回 length > 0 *
* 6Number 类型返回 value != 0 * 通过 Logic.setToOldWorkMode() 设置可支持老版本中的以下四个规则
* 7Iterator 返回 hasNext() * 1Number 类型返回 value != 0
* 8其它返回 true * 2MapCollection(List被包括在内) 返回 size() > 0
* 3数组返回 length > 0
* 4Iterator 返回 hasNext()
*/ */
public static boolean isTrue(Object v) { public static boolean isTrue(Object v) {
if (v == null) { if (v == null) {
return false; return false;
} }
if (v instanceof Boolean) { if (v instanceof Boolean) {
return (Boolean)v; 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) { if (v instanceof CharSequence) {
return ((CharSequence)v).length() > 0; return ((CharSequence)v).length() > 0;
} }
// 如果不是新工作模式则对下面类型进行判断
if ( !newWorkMode ) {
if (v instanceof Number) { if (v instanceof Number) {
if (v instanceof Double) { if (v instanceof Double) {
return ((Number)v).doubleValue() != 0; return ((Number)v).doubleValue() != 0;
@ -130,9 +137,23 @@ public class Logic extends Expr {
} }
return ((Number)v).intValue() != 0; return ((Number)v).intValue() != 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) { if (v instanceof Iterator) {
return ((Iterator<?>)v).hasNext(); return ((Iterator<?>)v).hasNext();
} }
}
return true; return true;
} }

View File

@ -26,14 +26,14 @@ import java.lang.reflect.Modifier;
*/ */
public class MethodInfo { public class MethodInfo {
protected final String key; protected final Long key;
protected final Class<?> clazz; protected final Class<?> clazz;
protected final Method method; protected final Method method;
protected final boolean isVarArgs; protected final boolean isVarArgs;
protected final Class<?>[] paraTypes; protected final Class<?>[] paraTypes;
public MethodInfo(String key, Class<?> clazz, Method method) { public MethodInfo(Long key, Class<?> clazz, Method method) {
this.key = key; this.key = key;
this.clazz = clazz; this.clazz = clazz;
this.method = method; this.method = method;
@ -64,7 +64,7 @@ public class MethodInfo {
return method.invoke(target, finalArgValues); return method.invoke(target, finalArgValues);
} }
public String getKey() { public Long getKey() {
return key; return key;
} }

View File

@ -26,7 +26,7 @@ public class MethodInfoExt extends MethodInfo {
protected Object objectOfExtensionClass; 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); super(key, clazz, method);
this.objectOfExtensionClass = objectOfExtensionClass; this.objectOfExtensionClass = objectOfExtensionClass;

View File

@ -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
*
* <pre>
* 特别注意
* 如果希望将 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
*
* </pre>
*/
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
*
* targetClassmethodNameargTypes 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<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash ^= type.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
}
}
}
return hash;
}
}
/**
* StrictMethodKeyBuilder
*
* targetClassmethodNameargTypes 三部分全部使用 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<argTypes.length; i++) {
Class<?> 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<size; i++) {
hash ^= key.charAt(i);
hash *= HashKit.FNV_PRIME_64;
}
return hash;
}
}
}

View File

@ -22,8 +22,6 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.ReflectKit; import com.jfinal.kit.ReflectKit;
import com.jfinal.template.ext.extensionmethod.ByteExt; import com.jfinal.template.ext.extensionmethod.ByteExt;
import com.jfinal.template.ext.extensionmethod.DoubleExt; import com.jfinal.template.ext.extensionmethod.DoubleExt;
@ -42,7 +40,7 @@ public class MethodKit {
private static final Set<String> forbiddenMethods = new HashSet<String>(); private static final Set<String> forbiddenMethods = new HashSet<String>();
private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>(); private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>();
private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>(); private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>();
private static final ConcurrentHashMap<String, Object> methodCache = new ConcurrentHashMap<String, Object>(); private static final HashMap<Long, Object> methodCache = new HashMap<Long, Object>();
// 初始化在模板中调用 method 时所在的被禁止使用类 // 初始化在模板中调用 method 时所在的被禁止使用类
static { static {
@ -105,12 +103,12 @@ public class MethodKit {
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) { public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
Class<?>[] argTypes = getArgTypes(argValues); Class<?>[] argTypes = getArgTypes(argValues);
String key = getMethodKey(targetClass, methodName, argTypes); Long key = getMethodKey(targetClass, methodName, argTypes);
Object method = methodCache.get(key); Object method = methodCache.get(key);
if (method == null) { if (method == null) {
method = doGetMethod(key, targetClass, methodName, argTypes); method = doGetMethod(key, targetClass, methodName, argTypes);
if (method != null) { if (method != null) {
methodCache.putIfAbsent(key, method); methodCache.put(key, method);
} else { } else {
// 对于不存在的 Method只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险 // 对于不存在的 Method只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险
methodCache.put(key, Boolean.FALSE); methodCache.put(key, Boolean.FALSE);
@ -123,12 +121,12 @@ public class MethodKit {
* 获取 getter 方法 * 获取 getter 方法
* 使用与 Field 相同的 key避免生成两次 key值 * 使用与 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); Object getterMethod = methodCache.get(key);
if (getterMethod == null) { if (getterMethod == null) {
getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES); getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES);
if (getterMethod != null) { if (getterMethod != null) {
methodCache.putIfAbsent(key, getterMethod); methodCache.put(key, getterMethod);
} else { } else {
methodCache.put(key, Boolean.FALSE); methodCache.put(key, Boolean.FALSE);
} }
@ -147,7 +145,7 @@ public class MethodKit {
return argTypes; 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)) { if (forbiddenClasses.contains(targetClass)) {
throw new RuntimeException("Forbidden class: " + targetClass.getName()); throw new RuntimeException("Forbidden class: " + targetClass.getName());
} }
@ -229,23 +227,8 @@ public class MethodKit {
/** /**
* 获取方法用于缓存的 key * 获取方法用于缓存的 key
*/ */
private static String getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) { private static Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
StringBuilder key = new StringBuilder(96); return MethodKeyBuilder.instance.getMethodKey(targetClass, methodName, argTypes);
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<argTypes.length; i++) {
Class<?> type = argTypes[i];
argTypesDigest.append(type != null ? type.getName() : "null");
}
key.append(HashKit.md5(argTypesDigest.toString()));
} }
// 以下代码实现 extension method 功能 -------------------- // 以下代码实现 extension method 功能 --------------------
@ -290,7 +273,7 @@ public class MethodKit {
throw new RuntimeException("Extension method \"" + methodName + "\" is already exists in class \"" + targetClass.getName() + "\""); throw new RuntimeException("Extension method \"" + methodName + "\" is already exists in class \"" + targetClass.getName() + "\"");
} }
} catch (NoSuchMethodException e) { // Method 找不到才能添加该扩展方法 } catch (NoSuchMethodException e) { // Method 找不到才能添加该扩展方法
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes)); Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
if (methodCache.containsKey(key)) { if (methodCache.containsKey(key)) {
throw new RuntimeException(buildMethodSignatureForException("The extension method is already exists: " + extensionClass.getName() + ".", methodName, targetParaTypes)); 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]; Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length); 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); methodCache.remove(key);
} }
} }

View File

@ -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;
}
}

View File

@ -49,15 +49,18 @@ public class NullSafe extends Expr {
Ctrl ctrl = scope.getCtrl(); Ctrl ctrl = scope.getCtrl();
boolean oldNullSafeValue = ctrl.isNullSafe(); boolean oldNullSafeValue = ctrl.isNullSafe();
Object ret;
try { try {
ctrl.setNullSafe(true); ctrl.setNullSafe(true);
ret = left.eval(scope); Object ret = left.eval(scope);
if (ret != null) {
return ret;
}
} finally { } finally {
ctrl.setNullSafe(oldNullSafeValue); 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 {

View File

@ -21,10 +21,11 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.HashMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.ReflectKit; import com.jfinal.kit.ReflectKit;
/** /**
@ -32,27 +33,27 @@ import com.jfinal.kit.ReflectKit;
*/ */
public class SharedMethodKit { public class SharedMethodKit {
private static final Set<String> excludedMethodKey = new HashSet<String>(); private static final Set<Long> excludedMethodKey = new HashSet<Long>();
static { static {
Method[] methods = Object.class.getMethods(); Method[] methods = Object.class.getMethods();
for (Method method : methods) { for (Method method : methods) {
String key = getSharedMethodKey(method.getName(), method.getParameterTypes()); Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
excludedMethodKey.add(key); excludedMethodKey.add(key);
} }
} }
private final List<SharedMethodInfo> sharedMethodList = new ArrayList<SharedMethodInfo>(); private final List<SharedMethodInfo> sharedMethodList = new ArrayList<SharedMethodInfo>();
private final ConcurrentHashMap<String, SharedMethodInfo> methodCache = new ConcurrentHashMap<String, SharedMethodInfo>(); private final HashMap<Long, SharedMethodInfo> methodCache = new HashMap<Long, SharedMethodInfo>();
public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) { public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) {
Class<?>[] argTypes = MethodKit.getArgTypes(argValues); Class<?>[] argTypes = MethodKit.getArgTypes(argValues);
String key = getSharedMethodKey(methodName, argTypes); Long key = getSharedMethodKey(methodName, argTypes);
SharedMethodInfo method = methodCache.get(key); SharedMethodInfo method = methodCache.get(key);
if (method == null) { if (method == null) {
method = doGetSharedMethodInfo(methodName, argTypes); method = doGetSharedMethodInfo(methodName, argTypes);
if (method != null) { if (method != null) {
methodCache.putIfAbsent(key, method); methodCache.put(key, method);
} }
// shared method 不支持 null safe不缓存: methodCache.put(key, Boolean.FALSE) // shared method 不支持 null safe不缓存: methodCache.put(key, Boolean.FALSE)
} }
@ -110,7 +111,7 @@ public class SharedMethodKit {
SharedMethodInfo current = it.next(); SharedMethodInfo current = it.next();
String methodName = method.getName(); String methodName = method.getName();
if (current.getName().equals(methodName)) { if (current.getName().equals(methodName)) {
String key = getSharedMethodKey(methodName, method.getParameterTypes()); Long key = getSharedMethodKey(methodName, method.getParameterTypes());
if (current.getKey().equals(key)) { if (current.getKey().equals(key)) {
it.remove(); it.remove();
} }
@ -125,7 +126,7 @@ public class SharedMethodKit {
Method[] methods = sharedClass.getMethods(); Method[] methods = sharedClass.getMethods();
for (Method method : methods) { for (Method method : methods) {
String key = getSharedMethodKey(method.getName(), method.getParameterTypes()); Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
if (excludedMethodKey.contains(key)) { if (excludedMethodKey.contains(key)) {
continue ; continue ;
} }
@ -144,19 +145,27 @@ public class SharedMethodKit {
} }
} }
private static String getSharedMethodKey(String methodName, Class<?>[] argTypes) { private static Long getSharedMethodKey(String methodName, Class<?>[] argTypes) {
StringBuilder key = new StringBuilder(64); long hash = HashKit.FNV_OFFSET_BASIS_64;
key.append(methodName); hash ^= methodName.hashCode();
if (argTypes != null && argTypes.length > 0) { hash *= HashKit.FNV_PRIME_64;
MethodKit.createArgTypesDigest(argTypes, key);
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> 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 { static class SharedMethodInfo extends MethodInfo {
final Object target; 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); super(key, clazz, method);
this.target = target; this.target = target;
} }

View File

@ -16,24 +16,28 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer; import java.io.IOException;
import java.text.SimpleDateFormat; import java.util.Date;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; 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 * 1#date 指令中的参数可以是变量例如#date(d, p) 中的 d p 可以全都是变量
* 第二个为 date 表示当第一个为 null 时的默认值 * 2默认 datePattern 可通过 Engine.setDatePattern(...) 进行配置
*/ */
public class DateDirective extends Directive { public class DateDirective extends Directive {
@ -51,34 +55,32 @@ public class DateDirective extends Directive {
this.valueExpr = null; this.valueExpr = null;
this.datePatternExpr = null; this.datePatternExpr = null;
} else if (paraNum == 1) { } else if (paraNum == 1) {
this.valueExpr = exprList.getExprArray()[0]; this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = null; this.datePatternExpr = null;
} else if (paraNum == 2) { } else if (paraNum == 2) {
this.valueExpr = exprList.getExprArray()[0]; this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = exprList.getExprArray()[1]; this.datePatternExpr = exprList.getExpr(1);
} }
} }
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {
if (paraNum == 0) { if (paraNum == 1) {
outputToday(env, writer);
} else if (paraNum == 1) {
outputWithoutDatePattern(env, scope, writer); outputWithoutDatePattern(env, scope, writer);
} else if (paraNum == 2) { } else if (paraNum == 2) {
outputWithDatePattern(env, scope, writer); outputWithDatePattern(env, scope, writer);
} else {
outputToday(env, writer);
} }
} }
private void outputToday(Env env, Writer writer) { private void outputToday(Env env, Writer writer) {
Object value = format(new java.util.Date(), env.getEngineConfig().getDatePattern()); write(writer, new Date(), env.getEngineConfig().getDatePattern());
write(writer, value.toString());
} }
private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) { private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) {
Object value = valueExpr.eval(scope); Object value = valueExpr.eval(scope);
if (value != null) { if (value != null) {
value = format(value, env.getEngineConfig().getDatePattern()); write(writer, (Date)value, env.getEngineConfig().getDatePattern());
write(writer, value.toString());
} }
} }
@ -88,18 +90,18 @@ public class DateDirective extends Directive {
return ; return ;
} }
Object dp = this.datePatternExpr.eval(scope); Object datePattern = this.datePatternExpr.eval(scope);
if ( !(dp instanceof String) ) { if ( !(datePattern instanceof String) ) {
throw new TemplateException("The sencond parameter dataPattern of #date directive must be String", location); throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location);
}
value = format(value, (String)dp);
write(writer, value.toString());
} }
private String format(Object value, String datePattern) { write(writer, (Date)value, (String)datePattern);
}
private void write(Writer writer, Date date, String datePattern) {
try { try {
return new SimpleDateFormat(datePattern).format(value); writer.write(date, datePattern);
} catch (Exception e) { } catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e); throw new TemplateException(e.getMessage(), location, e);
} }
} }

View File

@ -16,9 +16,9 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,12 +16,13 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer; import java.io.IOException;
import java.text.SimpleDateFormat; import java.util.Date;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -39,22 +40,21 @@ public class NowDirective extends Directive {
} }
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {
String dataPattern; String datePattern;
if (exprList.length() == 0) { if (exprList.length() == 0) {
dataPattern = env.getEngineConfig().getDatePattern(); datePattern = env.getEngineConfig().getDatePattern();
} else { } else {
Object dp = exprList.eval(scope); Object dp = exprList.eval(scope);
if (dp instanceof String) { if (dp instanceof String) {
dataPattern = (String)dp; datePattern = (String)dp;
} else { } 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 { try {
String value = new SimpleDateFormat(dataPattern).format(new java.util.Date()); writer.write(new Date(), datePattern);
write(writer, value); } catch (IOException e) {
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e); throw new TemplateException(e.getMessage(), location, e);
} }
} }

View File

@ -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 输出变量中的值
*
* 注意
* 1pattern 的使用与 java.text.DecimalFormat 的完全一样
* 在拿不定主意的时候可以在搜索引擎中搜索关键字DecimalFormat
* 2#number 指令中的参数可以是变量例如#number(n, p) 中的 n p 可以全都是变量
*
* <pre>
* 示例
* #number(3.1415926, "#.##")
* #number(0.9518, "#.##%")
* #number(300000, "光速为每秒 ,### 公里。")
*
* #set(n = 1.234)
* #set(p = "#.##")
* #number(n, p)
* </pre>
*/
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);
}
}

View File

@ -16,9 +16,9 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,7 +16,6 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
@ -25,6 +24,7 @@ import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.source.ISource; import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.ParseException; 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.Define;
import com.jfinal.template.stat.ast.Include; import com.jfinal.template.stat.ast.Include;
import com.jfinal.template.stat.ast.Stat; import com.jfinal.template.stat.ast.Stat;
import com.jfinal.template.stat.ast.StatList;
/** /**
* #render 指令用于动态渲染子模板作为 include 指令的补充 * #render 指令用于动态渲染子模板作为 include 指令的补充
@ -111,7 +112,7 @@ public class RenderDirective extends Directive {
if (statInfo == null) { if (statInfo == null) {
statInfo = parseStatInfo(env, subFileName); statInfo = parseStatInfo(env, subFileName);
statInfoCache.put(subFileName, statInfo); statInfoCache.put(subFileName, statInfo);
} else if (env.getEngineConfig().isDevMode()) { } else if (env.isDevMode()) {
// statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载 // statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载
if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) { if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) {
statInfo = parseStatInfo(env, subFileName); statInfo = parseStatInfo(env, subFileName);
@ -130,8 +131,8 @@ public class RenderDirective extends Directive {
try { try {
EnvSub envSub = new EnvSub(env); EnvSub envSub = new EnvSub(env);
Stat stat = new Parser(envSub, fileSource.getContent(), subFileName).parse(); StatList statList = new Parser(envSub, fileSource.getContent(), subFileName).parse();
return new StatInfo(envSub, stat, fileSource); return new StatInfo(envSub, statList.getActualStat(), fileSource);
} catch (Exception e) { } catch (Exception e) {
throw new ParseException(e.getMessage(), location, e); throw new ParseException(e.getMessage(), location, e);
} }

View File

@ -16,10 +16,11 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.Writer;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; 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.Const;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; 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) { public void exec(Env env, Scope scope, Writer writer) {
CharWriter charWriter = new CharWriter(64);
FastStringWriter fsw = new FastStringWriter(); FastStringWriter fsw = new FastStringWriter();
stat.exec(env, scope, fsw); charWriter.init(fsw);
try {
stat.exec(env, scope, charWriter);
} finally {
charWriter.close();
}
if (this.isLocalAssignment) { if (this.isLocalAssignment) {
scope.setLocal(name, fsw.toString()); scope.setLocal(name, fsw.toString());

View File

@ -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 共享方法库逐步添加一些最常用的共享方法
*
* <br>
* 3.3 版本之前的 Logic.isTrue(Object) 方法不再对 Collection
* Map数组IteratorIterable 进行为空的判断这部分逻辑已转移至
* SharedMethodLib.isEmpty(Object)
*/
public class SharedMethodLib {
/**
* 判断 CollectionMap数组IteratorIterable 类型对象中的元素个数是否为 0
* 规则
* 1null 返回 true
* 2ListSet 等一切继承自 Collection 返回 isEmpty()
* 3Map 返回 isEmpty()
* 4数组返回 length == 0
* 5Iterator 返回 ! hasNext()
* 6Iterable 返回 ! 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);
}
}

View File

@ -16,7 +16,7 @@
package com.jfinal.template.ext.spring; package com.jfinal.template.ext.spring;
import java.io.Writer; import java.io.OutputStream;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -56,9 +56,8 @@ public class JFinalView extends AbstractTemplateView {
} }
} }
Writer writer = response.getWriter(); OutputStream os = response.getOutputStream();
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, writer); JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os);
writer.flush();
} }
} }

View File

@ -18,7 +18,9 @@ package com.jfinal.template.ext.spring;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver; import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
import com.jfinal.kit.StrKit; import com.jfinal.kit.StrKit;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
@ -54,6 +56,15 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
static boolean sessionInView = false; static boolean sessionInView = false;
static boolean createSession = true; static boolean createSession = true;
private static JFinalViewResolver me = null;
/**
* me 会保存在第一次被创建对象
*/
public static JFinalViewResolver me() {
return me;
}
public Engine getEngine() { public Engine getEngine() {
return engine; return engine;
} }
@ -83,6 +94,24 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
} }
} }
/**
* 通过 List 配置多个 shared function file
* <pre>
* 配置示例
* <property name="sharedFunctionList">
* <list>
* <value>_layout.html</value>
* <value>_paginate.html</value>
* </list>
* </property>
* </pre>
*/
public void setSharedFunctionList(List<String> sharedFunctionList) {
if (sharedFunctionList != null) {
JFinalViewResolver.sharedFunctionFiles.addAll(sharedFunctionList);
}
}
/** /**
* 添加 shared function 文件可调用多次添加多个文件 * 添加 shared function 文件可调用多次添加多个文件
*/ */
@ -94,8 +123,16 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
/** /**
* 添加自定义指令 * 添加自定义指令
*/ */
public void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
engine.addDirective(directiveName, directiveClass);
}
/**
* 添加自定义指令已被 addDirective(String, Class<? extends Directive>) 方法取代
*/
@Deprecated
public void addDirective(String directiveName, Directive directive) { 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() { public JFinalViewResolver() {
synchronized(JFinalViewResolver.class) {
if (me == null) {
me = this;
}
}
setViewClass(requiredViewClass()); setViewClass(requiredViewClass());
setOrder(0); setOrder(0);
setContentType("text/html;charset=UTF-8"); setContentType("text/html;charset=UTF-8");
@ -208,12 +251,30 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
return JFinalView.class; return JFinalView.class;
} }
/**
* 支持 jfinal enjoyjspfreemarkervelocity 四类模板共存于一个项目中
*
* 注意这里采用识别 ".jsp"".ftl"".vm" 模板后缀名的方式来实现功能
* 所以 jfinal enjoy 模板不要采用上述三种后缀名否则功能将失效
* 还要注意与 jspfreemarkervelocity 以外类型模板共存使用时
* 需要改造该方法
*/
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 做必要的初始化工作 * spring 回调利用 ServletContext 做必要的初始化工作
*/ */
@Override @Override
protected void initServletContext(ServletContext servletContext) { protected void initServletContext(ServletContext servletContext) {
super.initServletContext(servletContext); super.initServletContext(servletContext);
super.setExposeRequestAttributes(true);
initBaseTemplatePath(servletContext); initBaseTemplatePath(servletContext);
initSharedFunction(); initSharedFunction();

View File

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

View File

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

View File

@ -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<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
public SimpleDateFormat getDateFormat(String datePattern) {
SimpleDateFormat ret = map.get(datePattern);
if (ret == null) {
ret = new SimpleDateFormat(datePattern);
map.put(datePattern, ret);
}
return ret;
}
}

View File

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

View File

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

View File

@ -14,16 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package com.jfinal.template; package com.jfinal.template.io;
import java.io.IOException;
import java.io.Writer; import java.io.Writer;
/** /**
* FastStringWriter * FastStringWriter
* *
* JDK StringWriter 改造而成 StringBuffer 属性替换为 * <pre>
* StringBuilder避免 StringBuffer synchronized 操作 * JDK StringWriter 改造而来在其基础之上做了如下改变
* 1StringBuffer 属性改为 StringBuilder避免了前者的 synchronized 操作
* 2添加了 MAX_SIZE 属性
* 3去掉了 close() 方法声明中的 throws IOException并添加了代码原先该方法中无任何代码
* </pre>
*/ */
public class FastStringWriter extends Writer { 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);
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(ByteWriter byteWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
int len = fd.getChars(chars);
for (int i=0; i<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(CharWriter charWriter, double doubleValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(doubleValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
public static void write(CharWriter charWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
}

View File

@ -0,0 +1,38 @@
/**
* 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;
/**
* IWritable 支持 OutputStreamWriter 双模式动态切换输出
*
* 详见 com.jfinal.template.stat.ast.Text 中的用法
*/
public interface IWritable {
/**
* OutputStream 模式下的 ByteWrite 使用
*/
public byte[] getBytes();
/**
* Writer 模式下的 CharWrite 使用
*/
public char[] getChars();
}

View File

@ -0,0 +1,126 @@
/*
* 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 IntegerWriter {
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
final static char [] DigitOnes = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
} ;
final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
} ;
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
private static final byte[] minValueBytes = "-2147483648".getBytes();
private static final char[] minValueChars = "-2147483648".toCharArray();
public static void write(ByteWriter byteWriter, int i) throws IOException {
if (i == Integer.MIN_VALUE) {
byteWriter.out.write(minValueBytes, 0, minValueBytes.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
getChars(i, size, chars);
// int len = Utf8Encoder.me.encode(chars, 0, size, bytes);
// byteWriter.out.write(bytes, 0, len);
for (int j=0; j<size; j++) {
bytes[j] = (byte)chars[j];
}
byteWriter.out.write(bytes, 0, size);
}
public static void write(CharWriter charWriter, int i) throws IOException {
if (i == Integer.MIN_VALUE) {
charWriter.out.write(minValueChars, 0, minValueChars.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = charWriter.chars;
getChars(i, size, chars);
charWriter.out.write(chars, 0, size);
}
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
while (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;
}
}
}

View File

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

View File

@ -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<size; j++) {
bytes[j] = (byte)chars[j];
}
byteWriter.out.write(bytes, 0, size);
}
public static void write(CharWriter charWriter, long i) throws IOException {
if (i == Long.MIN_VALUE) {
charWriter.out.write(minValueChars, 0, minValueChars.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = charWriter.chars;
getChars(i, size, chars);
charWriter.out.write(chars, 0, size);
}
static int stringSize(long x) {
long p = 10;
for (int i=1; i<19; i++) {
if (x < p)
return i;
p = 10*p;
}
return 19;
}
static void getChars(long i, int index, char[] buf) {
long q;
int r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Get 2 digits/iteration using longs until quotient fits into an int
while (i > 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;
}
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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<ByteWriter> byteWriters = new ThreadLocal<ByteWriter>() {
protected ByteWriter initialValue() {
return new ByteWriter(encoderFactory.getEncoder(), bufferSize);
}
};
private final ThreadLocal<CharWriter> charWriters = new ThreadLocal<CharWriter>() {
protected CharWriter initialValue() {
return new CharWriter(bufferSize);
}
};
private final ThreadLocal<FastStringWriter> fastStringWriters = new ThreadLocal<FastStringWriter>() {
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);
}
}

View File

@ -141,6 +141,14 @@ class Lexer {
continue ; continue ;
} }
// 在支持 #seleif 的基础上支持 #else if
if (symbol == Symbol.ELSE) {
if (foundFollowingIf()) {
id = "else if";
symbol = Symbol.ELSEIF;
}
}
// 无参关键字指令 // 无参关键字指令
if (symbol.noPara()) { if (symbol.noPara()) {
return addNoParaToken(new Token(symbol, id, beginRow)); 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值 * 调用者已确定以字母或下划线开头故一定可以获取到 id值
*/ */

View File

@ -18,6 +18,7 @@ package com.jfinal.template.stat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ExprParser; import com.jfinal.template.expr.ExprParser;
import com.jfinal.template.expr.ast.ExprList; 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)); 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 = new Lexer(content, fileName).scan();
tokenList.add(EOF); tokenList.add(EOF);
Stat statList = statList(); StatList statList = statList();
if (peek() != EOF) { if (peek() != EOF) {
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row)); throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
} }
@ -122,7 +123,7 @@ public class Parser {
switch (name.symbol) { switch (name.symbol) {
case TEXT: case TEXT:
move(); 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: case OUTPUT:
move(); move();
Token para = matchPara(name); Token para = matchPara(name);
@ -171,9 +172,9 @@ public class Parser {
String functionName = name.value(); String functionName = name.value();
move(); move();
para = matchPara(name); para = matchPara(name);
Stat stat = statList(); statList = statList();
matchEnd(name); matchEnd(name);
return new Define(functionName, parseExprList(para), stat, getLocation(name.row)); return new Define(functionName, parseExprList(para), statList, getLocation(name.row));
case CALL: case CALL:
functionName = name.value(); functionName = name.value();
move(); move();
@ -206,7 +207,7 @@ public class Parser {
move(); move();
return Return.me; return Return.me;
case ID: case ID:
Stat dire = env.getEngineConfig().getDirective(name.value()); Class<? extends Directive> dire = env.getEngineConfig().getDirective(name.value());
if (dire == null) { if (dire == null) {
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row)); throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
} }
@ -215,9 +216,9 @@ public class Parser {
para = matchPara(name); para = matchPara(name);
ret.setExprList(parseExprList(para)); ret.setExprList(parseExprList(para));
if (dire.hasEnd()) { if (ret.hasEnd()) {
statList = statList(); statList = statList();
ret.setStat(statList); ret.setStat(statList.getActualStat());
matchEnd(name); matchEnd(name);
} }
return ret; return ret;
@ -236,9 +237,9 @@ public class Parser {
return new Location(fileName, row); return new Location(fileName, row);
} }
private Stat createDirective(Stat dire, Token name) { private Stat createDirective(Class<? extends Directive> dire, Token name) {
try { try {
return dire.getClass().newInstance(); return dire.newInstance();
} catch (Exception e) { } catch (Exception e) {
throw new ParseException(e.getMessage(), getLocation(name.row), e); throw new ParseException(e.getMessage(), getLocation(name.row), e);
} }

View File

@ -90,8 +90,19 @@ public class Scope {
*/ */
public Object get(Object key) { public Object get(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) { for (Scope cur=this; cur!=null; cur=cur.parent) {
if (cur.data != null && cur.data.containsKey(key)) { // if (cur.data != null && cur.data.containsKey(key)) {
return cur.data.get(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; // 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;
}
} }

View File

@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,10 +16,10 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,7 +16,6 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Location; 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.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Id; import com.jfinal.template.expr.ast.Id;
import com.jfinal.template.io.Writer;
/** /**
* Define 定义模板函数 * Define 定义模板函数
@ -50,10 +50,10 @@ public class Define extends Stat {
private String[] parameterNames; private String[] parameterNames;
private Stat stat; 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); setLocation(location);
this.functionName = functionName; this.functionName = functionName;
this.stat = stat; this.stat = statList.getActualStat();
Expr[] exprArray = exprList.getExprArray(); Expr[] exprArray = exprList.getExprArray();
if (exprArray.length == 0) { if (exprArray.length == 0) {

View File

@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**
@ -27,8 +27,8 @@ public class Else extends Stat {
private Stat stat; private Stat stat;
public Else(Stat stat) { public Else(StatList statList) {
this.stat = stat; this.stat = statList.getActualStat();
} }
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {

View File

@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; 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.ExprList;
import com.jfinal.template.expr.ast.Logic; import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
*/ */
public class ElseIf extends Stat { public class ElseIf extends Stat {
private ExprList cond; private Expr cond;
private Stat stat; private Stat stat;
private Stat elseIfOrElse; private Stat elseIfOrElse;
public ElseIf(ExprList cond, Stat stat, Location location) { public ElseIf(ExprList cond, StatList statList, Location location) {
if (cond.length() == 0) { if (cond.length() == 0) {
throw new ParseException("The condition expression of #else if 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.cond = cond.getActualExpr();
this.stat = stat; this.stat = statList.getActualStat();
} }
/** /**

View File

@ -16,12 +16,12 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import java.util.Iterator; import java.util.Iterator;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ForCtrl; import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.expr.ast.Logic; import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -38,12 +38,12 @@ import com.jfinal.template.stat.Scope;
public class For extends Stat { public class For extends Stat {
private ForCtrl forCtrl; private ForCtrl forCtrl;
private StatList statList; private Stat stat;
private Stat _else; private Stat _else;
public For(ForCtrl forCtrl, StatList statList, Stat _else) { public For(ForCtrl forCtrl, StatList statList, Stat _else) {
this.forCtrl = forCtrl; this.forCtrl = forCtrl;
this.statList = statList; this.stat = statList.getActualStat();
this._else = _else; this._else = _else;
} }
@ -71,7 +71,7 @@ public class For extends Stat {
String itemName = forCtrl.getId(); String itemName = forCtrl.getId();
while(it.hasNext()) { while(it.hasNext()) {
scope.setLocal(itemName, it.next()); scope.setLocal(itemName, it.next());
statList.exec(env, scope, writer); stat.exec(env, scope, writer);
forIteratorStatus.nextState(); forIteratorStatus.nextState();
if (ctrl.isJump()) { if (ctrl.isJump()) {
@ -108,7 +108,7 @@ public class For extends Stat {
ctrl.setLocalAssignment(); ctrl.setLocalAssignment();
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) { for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
ctrl.setWisdomAssignment(); ctrl.setWisdomAssignment();
statList.exec(env, scope, writer); stat.exec(env, scope, writer);
ctrl.setLocalAssignment(); ctrl.setLocalAssignment();
forLoopStatus.nextState(); forLoopStatus.nextState();

View File

@ -25,7 +25,7 @@ public class ForEntry implements Entry<Object, Object> {
private Entry<Object, Object> entry; private Entry<Object, Object> entry;
public ForEntry(Entry<Object, Object> entry) { public void init(Entry<Object, Object> entry) {
this.entry = entry; this.entry = entry;
} }

View File

@ -60,11 +60,6 @@ public class ForIteratorStatus {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void init(Object target) { private void init(Object target) {
if (target == null) {
size = 0;
iterator = NullIterator.me;
return ;
}
if (target instanceof Collection) { if (target instanceof Collection) {
size = ((Collection<?>)target).size(); size = ((Collection<?>)target).size();
iterator = ((Collection<?>)target).iterator(); iterator = ((Collection<?>)target).iterator();
@ -75,6 +70,11 @@ public class ForIteratorStatus {
iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator()); iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator());
return ; return ;
} }
if (target == null) { // 必须放在 target.getClass() 之前避免空指针异常
size = 0;
iterator = NullIterator.me;
return ;
}
if (target.getClass().isArray()) { if (target.getClass().isArray()) {
size = Array.getLength(target); size = Array.getLength(target);
iterator = new ArrayIterator(target, size); iterator = new ArrayIterator(target, size);
@ -148,6 +148,7 @@ public class ForIteratorStatus {
class MapIterator implements Iterator<Entry<Object, Object>> { class MapIterator implements Iterator<Entry<Object, Object>> {
private Iterator<Entry<Object, Object>> iterator; private Iterator<Entry<Object, Object>> iterator;
private ForEntry forEntry = new ForEntry();
public MapIterator(Iterator<Entry<Object, Object>> iterator) { public MapIterator(Iterator<Entry<Object, Object>> iterator) {
this.iterator = iterator; this.iterator = iterator;
@ -158,7 +159,8 @@ class MapIterator implements Iterator<Entry<Object, Object>> {
} }
public Entry<Object, Object> next() { public Entry<Object, Object> next() {
return new ForEntry((Entry<Object, Object>)iterator.next()); forEntry.init(iterator.next());
return forEntry;
} }
public void remove() { public void remove() {

View File

@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; 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.ExprList;
import com.jfinal.template.expr.ast.Logic; import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
*/ */
public class If extends Stat { public class If extends Stat {
private ExprList cond; private Expr cond;
private Stat stat; private Stat stat;
private Stat elseIfOrElse; private Stat elseIfOrElse;
public If(ExprList cond, Stat stat, Location location) { public If(ExprList cond, StatList statList, Location location) {
if (cond.length() == 0) { if (cond.length() == 0) {
throw new ParseException("The condition expression of #if statement can not be blank", location); throw new ParseException("The condition expression of #if statement can not be blank", location);
} }
this.cond = cond; this.cond = cond.getActualExpr();
this.stat = stat; this.stat = statList.getActualStat();
} }
/** /**

View File

@ -16,13 +16,13 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.EngineConfig; import com.jfinal.template.EngineConfig;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Const; import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.source.ISource; import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
@ -94,7 +94,7 @@ public class Include extends Stat {
if (config.isDevMode()) { if (config.isDevMode()) {
env.addSource(fileSource); env.addSource(fileSource);
} }
this.stat = parser.parse(); this.stat = parser.parse().getActualStat();
} catch (Exception e) { } catch (Exception e) {
// 文件路径不正确抛出异常时添加 location 信息 // 文件路径不正确抛出异常时添加 location 信息
throw new ParseException(e.getMessage(), location, e); throw new ParseException(e.getMessage(), location, e);

View File

@ -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) {
}
}

View File

@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -34,21 +35,41 @@ import com.jfinal.template.stat.Scope;
*/ */
public class Output extends Stat { public class Output extends Stat {
private ExprList exprList; private Expr expr;
public Output(ExprList exprList, Location location) { public Output(ExprList exprList, Location location) {
if (exprList.length() == 0) { if (exprList.length() == 0) {
throw new ParseException("The expression of output directive like #(expression) can not be blank", location); 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) { public void exec(Env env, Scope scope, Writer writer) {
try { try {
Object value = exprList.eval(scope); Object value = expr.eval(scope);
if (value != null) {
String str = value.toString(); if (value instanceof String) {
String str = (String)value;
writer.write(str, 0, str.length()); 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) { } catch(TemplateException e) {
throw e; throw e;

View File

@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**

View File

@ -16,11 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -36,7 +36,7 @@ import com.jfinal.template.stat.Scope;
*/ */
public class Set extends Stat { public class Set extends Stat {
private ExprList exprList; private Expr expr;
public Set(ExprList exprList, Location location) { public Set(ExprList exprList, Location location) {
if (exprList.length() == 0) { if (exprList.length() == 0) {
@ -48,12 +48,12 @@ public class Set extends Stat {
throw new ParseException("#set directive only supports assignment expressions", location); 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) { public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setWisdomAssignment(); scope.getCtrl().setWisdomAssignment();
exprList.eval(scope); expr.eval(scope);
} }
} }

View File

@ -16,11 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope;
*/ */
public class SetGlobal extends Stat { public class SetGlobal extends Stat {
private ExprList exprList; private Expr expr;
public SetGlobal(ExprList exprList, Location location) { public SetGlobal(ExprList exprList, Location location) {
if (exprList.length() == 0) { if (exprList.length() == 0) {
@ -45,14 +45,14 @@ public class SetGlobal extends Stat {
throw new ParseException("#setGlobal directive only supports assignment expressions", location); 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) { public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl(); Ctrl ctrl = scope.getCtrl();
try { try {
ctrl.setGlobalAssignment(); ctrl.setGlobalAssignment();
exprList.eval(scope); expr.eval(scope);
} finally { } finally {
ctrl.setWisdomAssignment(); ctrl.setWisdomAssignment();
} }

View File

@ -16,11 +16,11 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
@ -34,7 +34,7 @@ import com.jfinal.template.stat.Scope;
*/ */
public class SetLocal extends Stat { public class SetLocal extends Stat {
final ExprList exprList; final Expr expr;
public SetLocal(ExprList exprList, Location location) { public SetLocal(ExprList exprList, Location location) {
if (exprList.length() == 0) { if (exprList.length() == 0) {
@ -46,14 +46,14 @@ public class SetLocal extends Stat {
throw new ParseException("#setLocal directive only supports assignment expressions", location); 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) { public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl(); Ctrl ctrl = scope.getCtrl();
try { try {
ctrl.setLocalAssignment(); ctrl.setLocalAssignment();
exprList.eval(scope); expr.eval(scope);
} finally { } finally {
ctrl.setWisdomAssignment(); ctrl.setWisdomAssignment();
} }

View File

@ -17,10 +17,10 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.IOException; import java.io.IOException;
import java.io.Writer;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -59,19 +59,6 @@ public abstract class Stat {
throw new TemplateException(e.getMessage(), location, e); 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);
}
}
} }

View File

@ -16,10 +16,10 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.Writer;
import java.util.List; import java.util.List;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
@ -28,24 +28,44 @@ import com.jfinal.template.stat.Scope;
*/ */
public class StatList extends Stat { 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; private Stat[] statArray;
public StatList(List<Stat> statList) { public StatList(List<Stat> statList) {
if (statList.size() > 0) { if (statList.size() > 0) {
this.statArray = statList.toArray(new Stat[statList.size()]); this.statArray = statList.toArray(new Stat[statList.size()]);
} else { } 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) { public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl(); Ctrl ctrl = scope.getCtrl();
for (Stat stat : statArray) { for (int i=0; i<statArray.length; i++) {
if (ctrl.isJump()) { if (ctrl.isJump()) {
break ; break ;
} }
stat.exec(env, scope, writer); statArray[i].exec(env, scope, writer);
} }
} }

View File

@ -17,41 +17,98 @@
package com.jfinal.template.stat.ast; package com.jfinal.template.stat.ast;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.nio.charset.Charset;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException; import com.jfinal.template.TemplateException;
import com.jfinal.template.io.IWritable;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**
* Text 输出纯文本块以及使用 "#[[" "]]#" 指定的非解析块 * Text 输出纯文本块以及使用 "#[[" "]]#" 指定的非解析块
*/ */
public class Text extends Stat { public class Text extends Stat implements IWritable {
private char[] text; // contentbyteschars 三者必有一者不为 null
// OutputStreamWriter 混合模式下 byteschars 同时不为null
private StringBuilder content;
private Charset charset;
private byte[] bytes;
private char[] chars;
public Text(StringBuilder content) { // content 初始值在 Lexer 中已确保不为 null
this.text = new char[content.length()]; public Text(StringBuilder content, String encoding) {
content.getChars(0, content.length(), this.text, 0); this.content = content;
this.charset = Charset.forName(encoding);
this.bytes = null;
this.chars = null;
} }
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {
try { try {
writer.write(text, 0, text.length); writer.write(this);
} catch (IOException e) { } catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e); throw new TemplateException(e.getMessage(), location, e);
} }
} }
public boolean isEmpty() { public byte[] getBytes() {
return text.length == 0; if (bytes != null) {
return bytes;
} }
public String getContent() { if (content != null) {
return text != null ? new String(text) : null; bytes = content.toString().getBytes(charset);
content = null;
return bytes;
} }
bytes = new String(chars).getBytes(charset);
return bytes;
}
public char[] getChars() {
if (chars != null) {
return chars;
}
if (content != null) {
char[] charsTemp = new char[content.length()];
content.getChars(0, content.length(), charsTemp, 0);
chars = charsTemp;
content = null;
return chars;
}
String strTemp = new String(bytes, charset);
char[] charsTemp = new char[strTemp.length()];
strTemp.getChars(0, strTemp.length(), charsTemp, 0);
chars = charsTemp;
return chars;
}
public boolean isEmpty() {
if (content != null) {
return content.length() == 0;
} else if (bytes != null) {
return bytes.length == 0;
} else {
return chars.length == 0;
}
}
// public String getContent() {
// return text != null ? new String(text) : null;
// }
public String toString() { public String toString() {
return text != null ? new String(text) : ""; if (bytes != null) {
return new String(bytes, charset);
} else if (chars != null) {
return new String(chars);
} else {
return content.toString();
}
} }
} }