enjoy 3.3 release ^_^
This commit is contained in:
parent
28eb105ffa
commit
61aa1d2082
4
pom.xml
4
pom.xml
@ -4,7 +4,7 @@
|
||||
<artifactId>enjoy</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>enjoy</name>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
<version>3.3</version>
|
||||
<url>http://www.jfinal.com</url>
|
||||
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
package com.jfinal.kit;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Engine;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.Template;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
@ -42,7 +42,7 @@ public class ElKit {
|
||||
private static final String RETURN_VALUE_KEY = "_RETURN_VALUE_";
|
||||
|
||||
static {
|
||||
engine.addDirective("eval", new InnerEvalDirective());
|
||||
engine.addDirective("eval", InnerEvalDirective.class);
|
||||
}
|
||||
|
||||
public Engine getEngine() {
|
||||
@ -57,7 +57,7 @@ public class ElKit {
|
||||
public static <T> T eval(String expr, Map<?, ?> data) {
|
||||
String stringTemplate = "#eval(" + expr + ")";
|
||||
Template template = engine.getTemplateByString(stringTemplate);
|
||||
template.render(data, null);
|
||||
template.render(data, (java.io.Writer)null);
|
||||
return (T)data.get(RETURN_VALUE_KEY);
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,22 @@ import java.security.MessageDigest;
|
||||
|
||||
public class HashKit {
|
||||
|
||||
public static final long FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325L;
|
||||
public static final long FNV_PRIME_64 = 0x100000001b3L;
|
||||
|
||||
private static final java.security.SecureRandom random = new java.security.SecureRandom();
|
||||
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
|
||||
private static final char[] CHAR_ARRAY = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
|
||||
|
||||
public static long fnv1a64(String key) {
|
||||
long hash = FNV_OFFSET_BASIS_64;
|
||||
for(int i=0, size=key.length(); i<size; i++) {
|
||||
hash ^= key.charAt(i);
|
||||
hash *= FNV_PRIME_64;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static String md5(String srcStr){
|
||||
return hash("MD5", srcStr);
|
||||
}
|
||||
|
@ -281,12 +281,24 @@ public class Engine {
|
||||
|
||||
/**
|
||||
* Add directive
|
||||
* <pre>
|
||||
* 示例:
|
||||
* addDirective("now", NowDirective.class)
|
||||
* </pre>
|
||||
*/
|
||||
public Engine addDirective(String directiveName, Directive directive) {
|
||||
config.addDirective(directiveName, directive);
|
||||
public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass) {
|
||||
config.addDirective(directiveName, directiveClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法已被 addDirective(String, Class<? extends Directive>) 所代替
|
||||
*/
|
||||
@Deprecated
|
||||
public Engine addDirective(String directiveName, Directive directive) {
|
||||
return addDirective(directiveName, directive.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove directive
|
||||
*/
|
||||
@ -445,6 +457,11 @@ public class Engine {
|
||||
return config.getEncoding();
|
||||
}
|
||||
|
||||
public Engine setWriterBufferSize(int bufferSize) {
|
||||
config.setWriterBufferSize(bufferSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效,
|
||||
* 但如果在 devMode 之下并不希望对 addSharedFunction(...),
|
||||
@ -466,7 +483,7 @@ public class Engine {
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);;
|
||||
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
|
@ -26,7 +26,9 @@ import com.jfinal.kit.StrKit;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.SharedMethodKit;
|
||||
import com.jfinal.template.ext.directive.*;
|
||||
// import com.jfinal.template.ext.sharedmethod.Json;
|
||||
import com.jfinal.template.ext.sharedmethod.SharedMethodLib;
|
||||
import com.jfinal.template.io.EncoderFactory;
|
||||
import com.jfinal.template.io.WriterBuffer;
|
||||
import com.jfinal.template.source.FileSource;
|
||||
import com.jfinal.template.source.FileSourceFactory;
|
||||
import com.jfinal.template.source.ISource;
|
||||
@ -36,7 +38,6 @@ import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.Parser;
|
||||
import com.jfinal.template.stat.ast.Define;
|
||||
import com.jfinal.template.stat.ast.Output;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
|
||||
/**
|
||||
* EngineConfig
|
||||
@ -45,6 +46,8 @@ public class EngineConfig {
|
||||
|
||||
public static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
WriterBuffer writerBuffer = new WriterBuffer();
|
||||
|
||||
private Map<String, Define> sharedFunctionMap = new HashMap<String, Define>();
|
||||
private List<ISource> sharedFunctionSourceList = new ArrayList<ISource>(); // for devMode only
|
||||
|
||||
@ -52,7 +55,7 @@ public class EngineConfig {
|
||||
|
||||
private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me;
|
||||
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 boolean devMode = false;
|
||||
@ -63,14 +66,16 @@ public class EngineConfig {
|
||||
|
||||
public EngineConfig() {
|
||||
// Add official directive of Template Engine
|
||||
addDirective("render", new RenderDirective());
|
||||
addDirective("date", new DateDirective());
|
||||
addDirective("escape", new EscapeDirective());
|
||||
addDirective("string", new StringDirective());
|
||||
addDirective("random", new RandomDirective());
|
||||
addDirective("render", RenderDirective.class);
|
||||
addDirective("date", DateDirective.class);
|
||||
addDirective("escape", EscapeDirective.class);
|
||||
addDirective("string", StringDirective.class);
|
||||
addDirective("random", RandomDirective.class);
|
||||
addDirective("number", NumberDirective.class);
|
||||
|
||||
// Add official shared method of Template Engine
|
||||
// addSharedMethod(new Json());
|
||||
addSharedMethod(new SharedMethodLib());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,6 +273,17 @@ public class EngineConfig {
|
||||
throw new IllegalArgumentException("encoding can not be blank");
|
||||
}
|
||||
this.encoding = encoding;
|
||||
|
||||
writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding
|
||||
}
|
||||
|
||||
public void setEncoderFactory(EncoderFactory encoderFactory) {
|
||||
writerBuffer.setEncoderFactory(encoderFactory);
|
||||
writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding
|
||||
}
|
||||
|
||||
public void setWriterBufferSize(int bufferSize) {
|
||||
writerBuffer.setBufferSize(bufferSize);
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
@ -289,20 +305,25 @@ public class EngineConfig {
|
||||
this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode;
|
||||
}
|
||||
|
||||
public synchronized void addDirective(String directiveName, Directive directive) {
|
||||
@Deprecated
|
||||
public void addDirective(String directiveName, Directive directive) {
|
||||
addDirective(directiveName, directive.getClass());
|
||||
}
|
||||
|
||||
public synchronized void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
|
||||
if (StrKit.isBlank(directiveName)) {
|
||||
throw new IllegalArgumentException("directive name can not be blank");
|
||||
}
|
||||
if (directive == null) {
|
||||
throw new IllegalArgumentException("directive can not be null");
|
||||
if (directiveClass == null) {
|
||||
throw new IllegalArgumentException("directiveClass can not be null");
|
||||
}
|
||||
if (directiveMap.containsKey(directiveName)) {
|
||||
throw new IllegalArgumentException("directive already exists : " + directiveName);
|
||||
}
|
||||
directiveMap.put(directiveName, directive);
|
||||
directiveMap.put(directiveName, directiveClass);
|
||||
}
|
||||
|
||||
public Stat getDirective(String directiveName) {
|
||||
public Class<? extends Directive> getDirective(String directiveName) {
|
||||
return directiveMap.get(directiveName);
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,10 @@ public class Env {
|
||||
return engineConfig;
|
||||
}
|
||||
|
||||
public boolean isDevMode() {
|
||||
return engineConfig.isDevMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template function
|
||||
*/
|
||||
|
@ -16,8 +16,12 @@
|
||||
|
||||
package com.jfinal.template;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.io.ByteWriter;
|
||||
import com.jfinal.template.io.CharWriter;
|
||||
import com.jfinal.template.io.FastStringWriter;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
|
||||
@ -42,27 +46,57 @@ public class Template {
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 OutputStream 中去
|
||||
*/
|
||||
public void render(Map<?, ?> data, OutputStream outputStream) {
|
||||
ByteWriter byteWriter = env.engineConfig.writerBuffer.getByteWriter(outputStream);
|
||||
try {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), byteWriter);
|
||||
} finally {
|
||||
byteWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持无 data 参数,渲染到 OutputStream 中去 <br>
|
||||
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
|
||||
*/
|
||||
public void render(OutputStream outputStream) {
|
||||
render(null, outputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 Writer 中去
|
||||
*/
|
||||
public void render(Map<?, ?> data, Writer writer) {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), writer);
|
||||
CharWriter charWriter = env.engineConfig.writerBuffer.getCharWriter(writer);
|
||||
try {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), charWriter);
|
||||
} finally {
|
||||
charWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持无 data 参数,渲染到 Writer 中去 <br>
|
||||
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景<br>
|
||||
* 此外,其它所有 render 方法也支持传入 null 值 data 参数
|
||||
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
|
||||
*/
|
||||
public void render(Writer writer) {
|
||||
ast.exec(env, new Scope(null, env.engineConfig.sharedObjectMap), writer);
|
||||
render(null, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 FastStringWriter 中去
|
||||
* 渲染到 String 中去
|
||||
*/
|
||||
public void render(Map<?, ?> data, FastStringWriter fastStringWriter) {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), fastStringWriter);
|
||||
public String renderToString(Map<?, ?> data) {
|
||||
FastStringWriter fsw = env.engineConfig.writerBuffer.getFastStringWriter();
|
||||
try {
|
||||
render(data, fsw);
|
||||
return fsw.toString();
|
||||
} finally {
|
||||
fsw.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,13 +108,6 @@ public class Template {
|
||||
return fsw.getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 String 中去
|
||||
*/
|
||||
public String renderToString(Map<?, ?> data) {
|
||||
return renderToStringBuilder(data).toString();
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return env.isSourceListModified();
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ public class ExprParser {
|
||||
|
||||
public ForCtrl parseForCtrl() {
|
||||
Expr forCtrl = parse(false);
|
||||
|
||||
// 可能返回 ExprList.NULL_EXPR_LIST,必须做判断
|
||||
if (forCtrl instanceof ForCtrl) {
|
||||
return (ForCtrl)forCtrl;
|
||||
} else {
|
||||
@ -124,7 +126,7 @@ public class ExprParser {
|
||||
/**
|
||||
* exprList : expr (',' expr)*
|
||||
*/
|
||||
Expr exprList() {
|
||||
ExprList exprList() {
|
||||
List<Expr> exprList = new ArrayList<Expr>();
|
||||
while (true) {
|
||||
Expr stat = expr();
|
||||
@ -346,7 +348,7 @@ public class ExprParser {
|
||||
return new StaticMethod(clazz, memberName, location);
|
||||
}
|
||||
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
ExprList exprList = exprList();
|
||||
match(Sym.RPAREN);
|
||||
return new StaticMethod(clazz, memberName, exprList, location);
|
||||
}
|
||||
@ -383,7 +385,7 @@ public class ExprParser {
|
||||
return indexMethodField(sharedMethod);
|
||||
}
|
||||
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
ExprList exprList = exprList();
|
||||
SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location);
|
||||
match(Sym.RPAREN);
|
||||
return indexMethodField(sharedMethod);
|
||||
@ -433,7 +435,7 @@ public class ExprParser {
|
||||
}
|
||||
|
||||
// expr '.' ID '(' exprList ')'
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
ExprList exprList = exprList();
|
||||
match(Sym.RPAREN);
|
||||
expr = new Method(expr, tok.value(), exprList, location);
|
||||
}
|
||||
@ -498,7 +500,7 @@ public class ExprParser {
|
||||
move();
|
||||
return new Array(ExprList.NULL_EXPR_ARRAY, location);
|
||||
}
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
ExprList exprList = exprList();
|
||||
if (exprList.length() == 1 && peek().sym == Sym.RANGE) {
|
||||
move();
|
||||
Expr end = expr();
|
||||
@ -560,13 +562,13 @@ public class ExprParser {
|
||||
/**
|
||||
* forControl : ID : expr | exprList? ';' expr? ';' exprList?
|
||||
*/
|
||||
Expr forCtrl() {
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
ForCtrl forCtrl() {
|
||||
ExprList exprList = exprList();
|
||||
if (peek().sym == Sym.SEMICOLON) {
|
||||
move();
|
||||
Expr cond = expr();
|
||||
match(Sym.SEMICOLON);
|
||||
Expr update = exprList();
|
||||
ExprList update = exprList();
|
||||
return new ForCtrl(exprList, cond, update, location);
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,11 @@ public class Compare extends Expr {
|
||||
case Arith.FLOAT:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue());
|
||||
return l.floatValue() == r.floatValue();
|
||||
case Arith.DOUBLE:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue());
|
||||
return l.doubleValue() == r.doubleValue();
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) == 0;
|
||||
@ -120,8 +122,10 @@ public class Compare extends Expr {
|
||||
return l.longValue() > r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue());
|
||||
return l.floatValue() > r.floatValue();
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue());
|
||||
return l.doubleValue() > r.doubleValue();
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) > 0;
|
||||
@ -150,8 +154,10 @@ public class Compare extends Expr {
|
||||
return l.longValue() >= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue());
|
||||
return l.floatValue() >= r.floatValue();
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue());
|
||||
return l.doubleValue() >= r.doubleValue();
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) >= 0;
|
||||
@ -180,8 +186,10 @@ public class Compare extends Expr {
|
||||
return l.longValue() < r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue());
|
||||
return l.floatValue() < r.floatValue();
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue());
|
||||
return l.doubleValue() < r.doubleValue();
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) < 0;
|
||||
@ -210,8 +218,10 @@ public class Compare extends Expr {
|
||||
return l.longValue() <= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue());
|
||||
return l.floatValue() <= r.floatValue();
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue());
|
||||
return l.doubleValue() <= r.doubleValue();
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) <= 0;
|
||||
|
@ -72,10 +72,6 @@ public class Const extends Expr {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
public boolean isStr() {
|
||||
return type == Sym.STR;
|
||||
}
|
||||
@ -139,6 +135,10 @@ public class Const extends Expr {
|
||||
public Double getDouble() {
|
||||
return (Double)value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value != null ? value.toString() : "null";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
@ -25,24 +26,40 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class ExprList extends Expr {
|
||||
|
||||
public static final Expr NULL_EXPR = NullExpr.me;
|
||||
public static final Expr[] NULL_EXPR_ARRAY = new Expr[0];
|
||||
public static final ExprList NULL_EXPR_LIST = new ExprList(new ArrayList<Expr>(0));
|
||||
|
||||
public static final Object[] NULL_OBJECT_ARRAY = new Object[0];
|
||||
public static final ExprList NULL_EXPR_LIST = new ExprList();
|
||||
|
||||
private Expr[] exprArray;
|
||||
|
||||
private ExprList() {
|
||||
this.exprArray = NULL_EXPR_ARRAY;
|
||||
}
|
||||
|
||||
public ExprList(List<Expr> exprList) {
|
||||
if (exprList != null && exprList.size() > 0) {
|
||||
if (exprList.size() > 0) {
|
||||
exprArray = exprList.toArray(new Expr[exprList.size()]);
|
||||
} else {
|
||||
exprArray = NULL_EXPR_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 持有 ExprList 的指令可以通过此方法提升 AST 执行性能
|
||||
* 1:当 exprArray.length == 1 时返回 exprArray[0]
|
||||
* 2:当 exprArray.length == 0 时返回 NullExpr
|
||||
* 3:其它情况返回 ExprList 自身
|
||||
*
|
||||
* 意义在于,当满足前面两个条件时,避免掉了 ExprList.eval(...) 方法中的判断与循环
|
||||
*/
|
||||
public Expr getActualExpr() {
|
||||
if (exprArray.length == 1) {
|
||||
return exprArray[0];
|
||||
} else if (exprArray.length == 0) {
|
||||
return NULL_EXPR;
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Expr[] getExprArray() {
|
||||
return exprArray;
|
||||
}
|
||||
@ -54,6 +71,14 @@ public class ExprList extends Expr {
|
||||
return exprArray[index];
|
||||
}
|
||||
|
||||
public Expr getFirstExpr() {
|
||||
return exprArray.length > 0 ? exprArray[0] : null;
|
||||
}
|
||||
|
||||
public Expr getLastExpr() {
|
||||
return exprArray.length > 0 ? exprArray[exprArray.length - 1] : null;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return exprArray.length;
|
||||
}
|
||||
@ -62,11 +87,20 @@ public class ExprList extends Expr {
|
||||
* 对所有表达式求值,只返回最后一个表达式的值
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
Object ret = null;
|
||||
for (Expr expr : exprArray) {
|
||||
ret = expr.eval(scope);
|
||||
// 优化:绝大多数情况下 length 等于 1
|
||||
if (exprArray.length == 1) {
|
||||
return exprArray[0].eval(scope);
|
||||
}
|
||||
return ret;
|
||||
|
||||
if (exprArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int end = exprArray.length - 1;
|
||||
for (int i=0; i<end; i++) {
|
||||
exprArray[i].eval(scope);
|
||||
}
|
||||
return exprArray[end].eval(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,3 +121,4 @@ public class ExprList extends Expr {
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import com.jfinal.kit.HashKit;
|
||||
import com.jfinal.kit.StrKit;
|
||||
// import com.jfinal.plugin.activerecord.Model;
|
||||
// import com.jfinal.plugin.activerecord.Record;
|
||||
@ -40,6 +41,7 @@ public class Field extends Expr {
|
||||
private Expr expr;
|
||||
private String fieldName;
|
||||
private String getterName;
|
||||
private long getterNameHash;
|
||||
|
||||
public Field(Expr expr, String fieldName, Location location) {
|
||||
if (expr == null) {
|
||||
@ -48,6 +50,8 @@ public class Field extends Expr {
|
||||
this.expr = expr;
|
||||
this.fieldName = fieldName;
|
||||
this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
|
||||
// fnv1a64 hash 到比 String.hashCode() 更大的 long 值范围
|
||||
this.getterNameHash = HashKit.fnv1a64(getterName);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@ -65,7 +69,8 @@ public class Field extends Expr {
|
||||
}
|
||||
|
||||
Class<?> targetClass = target.getClass();
|
||||
String key = FieldKit.getFieldKey(targetClass, getterName);
|
||||
Long key = buildFieldKey(targetClass);
|
||||
|
||||
MethodInfo getter;
|
||||
try {
|
||||
getter = MethodKit.getGetterMethod(key, targetClass, getterName);
|
||||
@ -112,6 +117,10 @@ public class Field extends Expr {
|
||||
}
|
||||
throw new TemplateException("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location);
|
||||
}
|
||||
|
||||
private Long buildFieldKey(Class<?> targetClass) {
|
||||
return targetClass.getName().hashCode() ^ getterNameHash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,21 +17,21 @@
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* FieldKit
|
||||
*/
|
||||
public class FieldKit {
|
||||
|
||||
private static final ConcurrentHashMap<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);
|
||||
if (field == null) {
|
||||
field = doGetField(targetClass, fieldName);
|
||||
if (field != null) {
|
||||
fieldCache.putIfAbsent(key, field);
|
||||
fieldCache.put(key, field);
|
||||
} else {
|
||||
// 对于不存在的 Field,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
|
||||
fieldCache.put(key, Boolean.FALSE);
|
||||
@ -49,14 +49,6 @@ public class FieldKit {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Field 用于缓存的 key
|
||||
*/
|
||||
public static String getFieldKey(Class<?> targetClass, String getterName) {
|
||||
return new StringBuilder(64).append(targetClass.getName())
|
||||
.append('.').append(getterName).toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -58,10 +58,10 @@ public class ForCtrl extends Expr {
|
||||
/**
|
||||
* exprList? ';' expr? ';' exprList?
|
||||
*/
|
||||
public ForCtrl(Expr init, Expr cond, Expr update, Location location) {
|
||||
this.init = init;
|
||||
public ForCtrl(ExprList init, Expr cond, ExprList update, Location location) {
|
||||
this.init = init.getActualExpr();
|
||||
this.cond = cond;
|
||||
this.update = update;
|
||||
this.update = update.getActualExpr();
|
||||
this.id = null;
|
||||
this.expr = null;
|
||||
this.location = location;
|
||||
|
@ -37,6 +37,16 @@ public class Logic extends Expr {
|
||||
private Expr left; // ! 运算没有 left 参数
|
||||
private Expr right;
|
||||
|
||||
// 默认为新工作模式
|
||||
private static boolean newWorkMode = true;
|
||||
/**
|
||||
* 设置为旧工作模式,为了兼容 jfinal 3.3 之前的版本
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setToOldWorkMode() {
|
||||
newWorkMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 || && 结点
|
||||
*/
|
||||
@ -95,44 +105,55 @@ public class Logic extends Expr {
|
||||
* 规则:
|
||||
* 1:null 返回 false
|
||||
* 2:boolean 类型,原值返回
|
||||
* 3:Map、Connection(List被包括在内) 返回 size() > 0
|
||||
* 4:数组,返回 length > 0
|
||||
* 5:String、StringBuilder、StringBuffer 等继承自 CharSequence 类的对象,返回 length > 0
|
||||
* 6:Number 类型,返回 value != 0
|
||||
* 7:Iterator 返回 hasNext() 值
|
||||
* 8:其它返回 true
|
||||
* 3:String、StringBuilder 等一切继承自 CharSequence 类的对象,返回 length > 0
|
||||
* 4:其它返回 true
|
||||
*
|
||||
* 通过 Logic.setToOldWorkMode() 设置,可支持老版本中的以下四个规则:
|
||||
* 1:Number 类型,返回 value != 0
|
||||
* 2:Map、Collection(List被包括在内) 返回 size() > 0
|
||||
* 3:数组,返回 length > 0
|
||||
* 4:Iterator 返回 hasNext() 值
|
||||
*/
|
||||
public static boolean isTrue(Object v) {
|
||||
if (v == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v instanceof Boolean) {
|
||||
return (Boolean)v;
|
||||
}
|
||||
if (v instanceof Collection) {
|
||||
return ((Collection<?>)v).size() > 0;
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
return ((Map<?, ?>)v).size() > 0;
|
||||
}
|
||||
if (v.getClass().isArray()) {
|
||||
return Array.getLength(v) > 0;
|
||||
}
|
||||
if (v instanceof CharSequence) {
|
||||
return ((CharSequence)v).length() > 0;
|
||||
}
|
||||
if (v instanceof Number) {
|
||||
if (v instanceof Double) {
|
||||
return ((Number)v).doubleValue() != 0;
|
||||
|
||||
// 如果不是新工作模式,则对下面类型进行判断
|
||||
if ( !newWorkMode ) {
|
||||
if (v instanceof Number) {
|
||||
if (v instanceof Double) {
|
||||
return ((Number)v).doubleValue() != 0;
|
||||
}
|
||||
if (v instanceof Float) {
|
||||
return ((Number)v).floatValue() != 0;
|
||||
}
|
||||
return ((Number)v).intValue() != 0;
|
||||
}
|
||||
if (v instanceof Float) {
|
||||
return ((Number)v).floatValue() != 0;
|
||||
|
||||
// 下面四种类型的判断已提供了 shared method 扩展,用法如下:
|
||||
// #if(notEmpty(object)) 以及 #if(isEmpty(object))
|
||||
if (v instanceof Collection) {
|
||||
return ((Collection<?>)v).size() > 0;
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
return ((Map<?, ?>)v).size() > 0;
|
||||
}
|
||||
if (v.getClass().isArray()) {
|
||||
return Array.getLength(v) > 0;
|
||||
}
|
||||
if (v instanceof Iterator) {
|
||||
return ((Iterator<?>)v).hasNext();
|
||||
}
|
||||
return ((Number)v).intValue() != 0;
|
||||
}
|
||||
if (v instanceof Iterator) {
|
||||
return ((Iterator<?>)v).hasNext();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -26,14 +26,14 @@ import java.lang.reflect.Modifier;
|
||||
*/
|
||||
public class MethodInfo {
|
||||
|
||||
protected final String key;
|
||||
protected final Long key;
|
||||
protected final Class<?> clazz;
|
||||
protected final Method method;
|
||||
|
||||
protected final boolean isVarArgs;
|
||||
protected final Class<?>[] paraTypes;
|
||||
|
||||
public MethodInfo(String key, Class<?> clazz, Method method) {
|
||||
public MethodInfo(Long key, Class<?> clazz, Method method) {
|
||||
this.key = key;
|
||||
this.clazz = clazz;
|
||||
this.method = method;
|
||||
@ -64,7 +64,7 @@ public class MethodInfo {
|
||||
return method.invoke(target, finalArgValues);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
public Long getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ public class MethodInfoExt extends MethodInfo {
|
||||
|
||||
protected Object objectOfExtensionClass;
|
||||
|
||||
public MethodInfoExt(Object objectOfExtensionClass, String key, Class<?> clazz, Method method) {
|
||||
public MethodInfoExt(Object objectOfExtensionClass, Long key, Class<?> clazz, Method method) {
|
||||
super(key, clazz, method);
|
||||
this.objectOfExtensionClass = objectOfExtensionClass;
|
||||
|
||||
|
132
src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java
Normal file
132
src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java
Normal 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
|
||||
*
|
||||
* targetClass、methodName、argTypes 的 hash 直接使用 String.hashCode()
|
||||
* String.hashCode() 会被缓存,性能更好
|
||||
*/
|
||||
public static class FastMethodKeyBuilder extends MethodKeyBuilder {
|
||||
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
long hash = HashKit.FNV_OFFSET_BASIS_64;
|
||||
hash ^= targetClass.getName().hashCode();
|
||||
hash *= HashKit.FNV_PRIME_64;
|
||||
|
||||
hash ^= methodName.hashCode();
|
||||
hash *= HashKit.FNV_PRIME_64;
|
||||
|
||||
if (argTypes != null) {
|
||||
for (int i=0; i<argTypes.length; i++) {
|
||||
Class<?> type = argTypes[i];
|
||||
if (type != null) {
|
||||
hash ^= type.getName().hashCode();
|
||||
hash *= HashKit.FNV_PRIME_64;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StrictMethodKeyBuilder
|
||||
*
|
||||
* targetClass、methodName、argTypes 三部分全部使用 fnv1a64 算法计算 hash
|
||||
*/
|
||||
public static class StrictMethodKeyBuilder extends MethodKeyBuilder {
|
||||
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
long hash = HashKit.FNV_OFFSET_BASIS_64;
|
||||
|
||||
hash = fnv1a64(hash, targetClass.getName());
|
||||
hash = fnv1a64(hash, methodName);
|
||||
if (argTypes != null) {
|
||||
for (int i=0; i<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,8 +22,6 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.jfinal.kit.HashKit;
|
||||
import com.jfinal.kit.ReflectKit;
|
||||
import com.jfinal.template.ext.extensionmethod.ByteExt;
|
||||
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<Class<?>> forbiddenClasses = new HashSet<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 时所在的被禁止使用类
|
||||
static {
|
||||
@ -105,12 +103,12 @@ public class MethodKit {
|
||||
|
||||
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
|
||||
Class<?>[] argTypes = getArgTypes(argValues);
|
||||
String key = getMethodKey(targetClass, methodName, argTypes);
|
||||
Long key = getMethodKey(targetClass, methodName, argTypes);
|
||||
Object method = methodCache.get(key);
|
||||
if (method == null) {
|
||||
method = doGetMethod(key, targetClass, methodName, argTypes);
|
||||
if (method != null) {
|
||||
methodCache.putIfAbsent(key, method);
|
||||
methodCache.put(key, method);
|
||||
} else {
|
||||
// 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
|
||||
methodCache.put(key, Boolean.FALSE);
|
||||
@ -123,12 +121,12 @@ public class MethodKit {
|
||||
* 获取 getter 方法
|
||||
* 使用与 Field 相同的 key,避免生成两次 key值
|
||||
*/
|
||||
public static MethodInfo getGetterMethod(String key, Class<?> targetClass, String methodName) {
|
||||
public static MethodInfo getGetterMethod(Long key, Class<?> targetClass, String methodName) {
|
||||
Object getterMethod = methodCache.get(key);
|
||||
if (getterMethod == null) {
|
||||
getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES);
|
||||
if (getterMethod != null) {
|
||||
methodCache.putIfAbsent(key, getterMethod);
|
||||
methodCache.put(key, getterMethod);
|
||||
} else {
|
||||
methodCache.put(key, Boolean.FALSE);
|
||||
}
|
||||
@ -147,7 +145,7 @@ public class MethodKit {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
private static MethodInfo doGetMethod(String key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
private static MethodInfo doGetMethod(Long key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
if (forbiddenClasses.contains(targetClass)) {
|
||||
throw new RuntimeException("Forbidden class: " + targetClass.getName());
|
||||
}
|
||||
@ -229,23 +227,8 @@ public class MethodKit {
|
||||
/**
|
||||
* 获取方法用于缓存的 key
|
||||
*/
|
||||
private static String getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
StringBuilder key = new StringBuilder(96);
|
||||
key.append(targetClass.getName());
|
||||
key.append('.').append(methodName);
|
||||
if (argTypes != null && argTypes.length > 0) {
|
||||
createArgTypesDigest(argTypes, key);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
static void createArgTypesDigest(Class<?>[] argTypes, StringBuilder key) {
|
||||
StringBuilder argTypesDigest = new StringBuilder(64);
|
||||
for (int i=0; i<argTypes.length; i++) {
|
||||
Class<?> type = argTypes[i];
|
||||
argTypesDigest.append(type != null ? type.getName() : "null");
|
||||
}
|
||||
key.append(HashKit.md5(argTypesDigest.toString()));
|
||||
private static Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
return MethodKeyBuilder.instance.getMethodKey(targetClass, methodName, argTypes);
|
||||
}
|
||||
|
||||
// 以下代码实现 extension method 功能 --------------------
|
||||
@ -290,7 +273,7 @@ public class MethodKit {
|
||||
throw new RuntimeException("Extension method \"" + methodName + "\" is already exists in class \"" + targetClass.getName() + "\"");
|
||||
}
|
||||
} catch (NoSuchMethodException e) { // Method 找不到才能添加该扩展方法
|
||||
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
if (methodCache.containsKey(key)) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("The extension method is already exists: " + extensionClass.getName() + ".", methodName, targetParaTypes));
|
||||
}
|
||||
@ -319,7 +302,7 @@ public class MethodKit {
|
||||
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
|
||||
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
|
||||
|
||||
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
methodCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
36
src/main/java/com/jfinal/template/expr/ast/NullExpr.java
Normal file
36
src/main/java/com/jfinal/template/expr/ast/NullExpr.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,15 +49,18 @@ public class NullSafe extends Expr {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
boolean oldNullSafeValue = ctrl.isNullSafe();
|
||||
|
||||
Object ret;
|
||||
try {
|
||||
ctrl.setNullSafe(true);
|
||||
ret = left.eval(scope);
|
||||
Object ret = left.eval(scope);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
} finally {
|
||||
ctrl.setNullSafe(oldNullSafeValue);
|
||||
}
|
||||
|
||||
return ret == null && right != null ? right.eval(scope) : ret;
|
||||
// right 表达式处于 null safe 区域之外
|
||||
return right != null ? right.eval(scope) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,3 +69,4 @@ public class NullSafe extends Expr {
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -21,10 +21,11 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import com.jfinal.kit.HashKit;
|
||||
import com.jfinal.kit.ReflectKit;
|
||||
|
||||
/**
|
||||
@ -32,27 +33,27 @@ import com.jfinal.kit.ReflectKit;
|
||||
*/
|
||||
public class SharedMethodKit {
|
||||
|
||||
private static final Set<String> excludedMethodKey = new HashSet<String>();
|
||||
private static final Set<Long> excludedMethodKey = new HashSet<Long>();
|
||||
|
||||
static {
|
||||
Method[] methods = Object.class.getMethods();
|
||||
for (Method method : methods) {
|
||||
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
excludedMethodKey.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<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) {
|
||||
Class<?>[] argTypes = MethodKit.getArgTypes(argValues);
|
||||
String key = getSharedMethodKey(methodName, argTypes);
|
||||
Long key = getSharedMethodKey(methodName, argTypes);
|
||||
SharedMethodInfo method = methodCache.get(key);
|
||||
if (method == null) {
|
||||
method = doGetSharedMethodInfo(methodName, argTypes);
|
||||
if (method != null) {
|
||||
methodCache.putIfAbsent(key, method);
|
||||
methodCache.put(key, method);
|
||||
}
|
||||
// shared method 不支持 null safe,不缓存: methodCache.put(key, Boolean.FALSE)
|
||||
}
|
||||
@ -110,7 +111,7 @@ public class SharedMethodKit {
|
||||
SharedMethodInfo current = it.next();
|
||||
String methodName = method.getName();
|
||||
if (current.getName().equals(methodName)) {
|
||||
String key = getSharedMethodKey(methodName, method.getParameterTypes());
|
||||
Long key = getSharedMethodKey(methodName, method.getParameterTypes());
|
||||
if (current.getKey().equals(key)) {
|
||||
it.remove();
|
||||
}
|
||||
@ -125,7 +126,7 @@ public class SharedMethodKit {
|
||||
|
||||
Method[] methods = sharedClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
if (excludedMethodKey.contains(key)) {
|
||||
continue ;
|
||||
}
|
||||
@ -144,19 +145,27 @@ public class SharedMethodKit {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSharedMethodKey(String methodName, Class<?>[] argTypes) {
|
||||
StringBuilder key = new StringBuilder(64);
|
||||
key.append(methodName);
|
||||
if (argTypes != null && argTypes.length > 0) {
|
||||
MethodKit.createArgTypesDigest(argTypes, key);
|
||||
private static Long getSharedMethodKey(String methodName, Class<?>[] argTypes) {
|
||||
long hash = HashKit.FNV_OFFSET_BASIS_64;
|
||||
hash ^= methodName.hashCode();
|
||||
hash *= HashKit.FNV_PRIME_64;
|
||||
|
||||
if (argTypes != null) {
|
||||
for (int i=0; i<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 {
|
||||
final Object target;
|
||||
|
||||
private SharedMethodInfo(String key, Class<?> clazz, Method method, Object target) {
|
||||
private SharedMethodInfo(Long key, Class<?> clazz, Method method, Object target) {
|
||||
super(key, clazz, method);
|
||||
this.target = target;
|
||||
}
|
||||
|
@ -16,24 +16,28 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 不带参时,按默认 pattern 输出当前日期
|
||||
* #date 日期格式化输出指令
|
||||
*
|
||||
* #date() 指令支持无参时获取当前指令,第一个参数 string 当成是 pattern
|
||||
* 三种用法:
|
||||
* 1:#date(createAt) 用默认 datePattern 配置,输出 createAt 变量中的日期值
|
||||
* 2:#date(createAt, "yyyy-MM-dd HH:mm:ss") 用第二个参数指定的 datePattern,输出 createAt 变量中的日期值
|
||||
* 3:#date() 用默认 datePattern 配置,输出 “当前” 日期值
|
||||
*
|
||||
* 日期输出指令,第一个参数是被输出的 java.util.Date 对象或其子类对象
|
||||
* 无第二个参数时按默认 patter 输出,第二个参数为 expr 表达式,表示 pattern
|
||||
* 第二个为 date 时,表示当第一个为 null 时的默认值
|
||||
* 注意:
|
||||
* 1:#date 指令中的参数可以是变量,例如:#date(d, p) 中的 d 与 p 可以全都是变量
|
||||
* 2:默认 datePattern 可通过 Engine.setDatePattern(...) 进行配置
|
||||
*/
|
||||
public class DateDirective extends Directive {
|
||||
|
||||
@ -51,34 +55,32 @@ public class DateDirective extends Directive {
|
||||
this.valueExpr = null;
|
||||
this.datePatternExpr = null;
|
||||
} else if (paraNum == 1) {
|
||||
this.valueExpr = exprList.getExprArray()[0];
|
||||
this.valueExpr = exprList.getExpr(0);
|
||||
this.datePatternExpr = null;
|
||||
} else if (paraNum == 2) {
|
||||
this.valueExpr = exprList.getExprArray()[0];
|
||||
this.datePatternExpr = exprList.getExprArray()[1];
|
||||
this.valueExpr = exprList.getExpr(0);
|
||||
this.datePatternExpr = exprList.getExpr(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
if (paraNum == 0) {
|
||||
outputToday(env, writer);
|
||||
} else if (paraNum == 1) {
|
||||
if (paraNum == 1) {
|
||||
outputWithoutDatePattern(env, scope, writer);
|
||||
} else if (paraNum == 2) {
|
||||
outputWithDatePattern(env, scope, writer);
|
||||
} else {
|
||||
outputToday(env, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void outputToday(Env env, Writer writer) {
|
||||
Object value = format(new java.util.Date(), env.getEngineConfig().getDatePattern());
|
||||
write(writer, value.toString());
|
||||
write(writer, new Date(), env.getEngineConfig().getDatePattern());
|
||||
}
|
||||
|
||||
private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) {
|
||||
Object value = valueExpr.eval(scope);
|
||||
if (value != null) {
|
||||
value = format(value, env.getEngineConfig().getDatePattern());
|
||||
write(writer, value.toString());
|
||||
write(writer, (Date)value, env.getEngineConfig().getDatePattern());
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,18 +90,18 @@ public class DateDirective extends Directive {
|
||||
return ;
|
||||
}
|
||||
|
||||
Object dp = this.datePatternExpr.eval(scope);
|
||||
if ( !(dp instanceof String) ) {
|
||||
throw new TemplateException("The sencond parameter dataPattern of #date directive must be String", location);
|
||||
Object datePattern = this.datePatternExpr.eval(scope);
|
||||
if ( !(datePattern instanceof String) ) {
|
||||
throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location);
|
||||
}
|
||||
value = format(value, (String)dp);
|
||||
write(writer, value.toString());
|
||||
|
||||
write(writer, (Date)value, (String)datePattern);
|
||||
}
|
||||
|
||||
private String format(Object value, String datePattern) {
|
||||
private void write(Writer writer, Date date, String datePattern) {
|
||||
try {
|
||||
return new SimpleDateFormat(datePattern).format(value);
|
||||
} catch (Exception e) {
|
||||
writer.write(date, datePattern);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,12 +16,13 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
@ -39,22 +40,21 @@ public class NowDirective extends Directive {
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
String dataPattern;
|
||||
String datePattern;
|
||||
if (exprList.length() == 0) {
|
||||
dataPattern = env.getEngineConfig().getDatePattern();
|
||||
datePattern = env.getEngineConfig().getDatePattern();
|
||||
} else {
|
||||
Object dp = exprList.eval(scope);
|
||||
if (dp instanceof String) {
|
||||
dataPattern = (String)dp;
|
||||
datePattern = (String)dp;
|
||||
} else {
|
||||
throw new TemplateException("The parameter of #new directive must be String", location);
|
||||
throw new TemplateException("The parameter of #now directive must be String", location);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String value = new SimpleDateFormat(dataPattern).format(new java.util.Date());
|
||||
write(writer, value);
|
||||
} catch (Exception e) {
|
||||
writer.write(new Date(), datePattern);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* #number 数字格式化输出指令
|
||||
*
|
||||
* 两种用法:
|
||||
* 1:#number(n) 用默认 pattern 输出变量中的值
|
||||
* 2:#number(n, "#.##") 用第二个参数指定的 pattern 输出变量中的值
|
||||
*
|
||||
* 注意:
|
||||
* 1:pattern 的使用与 java.text.DecimalFormat 的完全一样
|
||||
* 在拿不定主意的时候可以在搜索引擎中搜索关键字:DecimalFormat
|
||||
* 2:#number 指令中的参数可以是变量,例如:#number(n, p) 中的 n 与 p 可以全都是变量
|
||||
*
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.Directive;
|
||||
@ -25,6 +24,7 @@ import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.source.ISource;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
@ -33,6 +33,7 @@ import com.jfinal.template.stat.Scope;
|
||||
import com.jfinal.template.stat.ast.Define;
|
||||
import com.jfinal.template.stat.ast.Include;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
import com.jfinal.template.stat.ast.StatList;
|
||||
|
||||
/**
|
||||
* #render 指令用于动态渲染子模板,作为 include 指令的补充
|
||||
@ -111,7 +112,7 @@ public class RenderDirective extends Directive {
|
||||
if (statInfo == null) {
|
||||
statInfo = parseStatInfo(env, subFileName);
|
||||
statInfoCache.put(subFileName, statInfo);
|
||||
} else if (env.getEngineConfig().isDevMode()) {
|
||||
} else if (env.isDevMode()) {
|
||||
// statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载
|
||||
if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) {
|
||||
statInfo = parseStatInfo(env, subFileName);
|
||||
@ -130,8 +131,8 @@ public class RenderDirective extends Directive {
|
||||
|
||||
try {
|
||||
EnvSub envSub = new EnvSub(env);
|
||||
Stat stat = new Parser(envSub, fileSource.getContent(), subFileName).parse();
|
||||
return new StatInfo(envSub, stat, fileSource);
|
||||
StatList statList = new Parser(envSub, fileSource.getContent(), subFileName).parse();
|
||||
return new StatInfo(envSub, statList.getActualStat(), fileSource);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
package com.jfinal.template.ext.directive;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.FastStringWriter;
|
||||
import com.jfinal.template.io.CharWriter;
|
||||
import com.jfinal.template.io.FastStringWriter;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.expr.ast.Const;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
@ -68,8 +69,14 @@ public class StringDirective extends Directive {
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
CharWriter charWriter = new CharWriter(64);
|
||||
FastStringWriter fsw = new FastStringWriter();
|
||||
stat.exec(env, scope, fsw);
|
||||
charWriter.init(fsw);
|
||||
try {
|
||||
stat.exec(env, scope, charWriter);
|
||||
} finally {
|
||||
charWriter.close();
|
||||
}
|
||||
|
||||
if (this.isLocalAssignment) {
|
||||
scope.setLocal(name, fsw.toString());
|
||||
|
@ -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、数组、Iterator、Iterable 进行为空的判断,这部分逻辑已转移至
|
||||
* SharedMethodLib.isEmpty(Object)
|
||||
*/
|
||||
public class SharedMethodLib {
|
||||
|
||||
/**
|
||||
* 判断 Collection、Map、数组、Iterator、Iterable 类型对象中的元素个数是否为 0
|
||||
* 规则:
|
||||
* 1:null 返回 true
|
||||
* 2:List、Set 等一切继承自 Collection 的,返回 isEmpty()
|
||||
* 3:Map 返回 isEmpty()
|
||||
* 4:数组返回 length == 0
|
||||
* 5:Iterator 返回 ! hasNext()
|
||||
* 6:Iterable 返回 ! iterator().hasNext()
|
||||
*
|
||||
* 注意:原先 Logic.isTrue(Object) 中对集合与数组类型为空的判断转移到此方法中
|
||||
*/
|
||||
public Boolean isEmpty(Object v) {
|
||||
if (v == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (v instanceof Collection) {
|
||||
return ((Collection<?>)v).isEmpty();
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
return ((Map<?, ?>)v).isEmpty();
|
||||
}
|
||||
|
||||
if (v.getClass().isArray()) {
|
||||
return Array.getLength(v) == 0;
|
||||
}
|
||||
|
||||
if (v instanceof Iterator) {
|
||||
return ! ((Iterator<?>)v).hasNext();
|
||||
}
|
||||
if (v instanceof Iterable) {
|
||||
return ! ((Iterable<?>)v).iterator().hasNext();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("isEmpty(...) 方法只能接受 Collection、Map、数组、Iterator、Iterable 类型参数");
|
||||
}
|
||||
|
||||
public Boolean notEmpty(Object v) {
|
||||
return !isEmpty(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
package com.jfinal.template.ext.spring;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -56,9 +56,8 @@ public class JFinalView extends AbstractTemplateView {
|
||||
}
|
||||
}
|
||||
|
||||
Writer writer = response.getWriter();
|
||||
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, writer);
|
||||
writer.flush();
|
||||
OutputStream os = response.getOutputStream();
|
||||
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@ package com.jfinal.template.ext.spring;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.servlet.ServletContext;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
|
||||
import com.jfinal.kit.StrKit;
|
||||
import com.jfinal.template.Directive;
|
||||
@ -54,6 +56,15 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
|
||||
static boolean sessionInView = false;
|
||||
static boolean createSession = true;
|
||||
|
||||
private static JFinalViewResolver me = null;
|
||||
|
||||
/**
|
||||
* me 会保存在第一次被创建对象
|
||||
*/
|
||||
public static JFinalViewResolver me() {
|
||||
return me;
|
||||
}
|
||||
|
||||
public Engine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
@ -83,6 +94,24 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 List 配置多个 shared function file
|
||||
* <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 文件,可调用多次添加多个文件
|
||||
*/
|
||||
@ -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) {
|
||||
engine.addDirective(directiveName, directive);
|
||||
addDirective(directiveName, directive.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,6 +233,12 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public JFinalViewResolver() {
|
||||
synchronized(JFinalViewResolver.class) {
|
||||
if (me == null) {
|
||||
me = this;
|
||||
}
|
||||
}
|
||||
|
||||
setViewClass(requiredViewClass());
|
||||
setOrder(0);
|
||||
setContentType("text/html;charset=UTF-8");
|
||||
@ -208,12 +251,30 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
|
||||
return JFinalView.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 jfinal enjoy、jsp、freemarker、velocity 四类模板共存于一个项目中
|
||||
*
|
||||
* 注意:这里采用识别 ".jsp"、".ftl"、".vm" 模板后缀名的方式来实现功能
|
||||
* 所以 jfinal enjoy 模板不要采用上述三种后缀名,否则功能将失效
|
||||
* 还要注意与 jsp、freemarker、velocity 以外类型模板共存使用时
|
||||
* 需要改造该方法
|
||||
*/
|
||||
protected View loadView(String viewName, Locale locale) throws Exception {
|
||||
String suffix = getSuffix();
|
||||
if (".jsp".equals(suffix) || ".ftl".equals(suffix) || ".vm".equals(suffix)) {
|
||||
return null;
|
||||
} else {
|
||||
return super.loadView(viewName, locale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* spring 回调,利用 ServletContext 做必要的初始化工作
|
||||
*/
|
||||
@Override
|
||||
protected void initServletContext(ServletContext servletContext) {
|
||||
super.initServletContext(servletContext);
|
||||
super.setExposeRequestAttributes(true);
|
||||
|
||||
initBaseTemplatePath(servletContext);
|
||||
initSharedFunction();
|
||||
|
122
src/main/java/com/jfinal/template/io/ByteWriter.java
Normal file
122
src/main/java/com/jfinal/template/io/ByteWriter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
117
src/main/java/com/jfinal/template/io/CharWriter.java
Normal file
117
src/main/java/com/jfinal/template/io/CharWriter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
44
src/main/java/com/jfinal/template/io/DateFormats.java
Normal file
44
src/main/java/com/jfinal/template/io/DateFormats.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
28
src/main/java/com/jfinal/template/io/Encoder.java
Normal file
28
src/main/java/com/jfinal/template/io/Encoder.java
Normal 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);
|
||||
}
|
||||
|
44
src/main/java/com/jfinal/template/io/EncoderFactory.java
Normal file
44
src/main/java/com/jfinal/template/io/EncoderFactory.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -14,16 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template;
|
||||
package com.jfinal.template.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* FastStringWriter
|
||||
*
|
||||
* 由 JDK 中 StringWriter 改造而成,将 StringBuffer 属性替换为
|
||||
* StringBuilder,避免 StringBuffer 的 synchronized 操作
|
||||
* <pre>
|
||||
* 由 JDK 中 StringWriter 改造而来,在其基础之上做了如下改变:
|
||||
* 1:StringBuffer 属性改为 StringBuilder,避免了前者的 synchronized 操作
|
||||
* 2:添加了 MAX_SIZE 属性
|
||||
* 3:去掉了 close() 方法声明中的 throws IOException,并添加了代码,原先该方法中无任何代码
|
||||
* </pre>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1306
src/main/java/com/jfinal/template/io/FloatingDecimal.java
Normal file
1306
src/main/java/com/jfinal/template/io/FloatingDecimal.java
Normal file
File diff suppressed because it is too large
Load Diff
67
src/main/java/com/jfinal/template/io/FloatingWriter.java
Normal file
67
src/main/java/com/jfinal/template/io/FloatingWriter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
38
src/main/java/com/jfinal/template/io/IWritable.java
Normal file
38
src/main/java/com/jfinal/template/io/IWritable.java
Normal 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 支持 OutputStream、Writer 双模式动态切换输出
|
||||
*
|
||||
* 详见 com.jfinal.template.stat.ast.Text 中的用法
|
||||
*/
|
||||
public interface IWritable {
|
||||
|
||||
/**
|
||||
* 供 OutputStream 模式下的 ByteWrite 使用
|
||||
*/
|
||||
public byte[] getBytes();
|
||||
|
||||
/**
|
||||
* 供 Writer 模式下的 CharWrite 使用
|
||||
*/
|
||||
public char[] getChars();
|
||||
}
|
||||
|
||||
|
||||
|
126
src/main/java/com/jfinal/template/io/IntegerWriter.java
Normal file
126
src/main/java/com/jfinal/template/io/IntegerWriter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
65
src/main/java/com/jfinal/template/io/JdkEncoder.java
Normal file
65
src/main/java/com/jfinal/template/io/JdkEncoder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
105
src/main/java/com/jfinal/template/io/LongWriter.java
Normal file
105
src/main/java/com/jfinal/template/io/LongWriter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
98
src/main/java/com/jfinal/template/io/Utf8Encoder.java
Normal file
98
src/main/java/com/jfinal/template/io/Utf8Encoder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
68
src/main/java/com/jfinal/template/io/Writer.java
Normal file
68
src/main/java/com/jfinal/template/io/Writer.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
86
src/main/java/com/jfinal/template/io/WriterBuffer.java
Normal file
86
src/main/java/com/jfinal/template/io/WriterBuffer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -95,7 +95,7 @@ class Lexer {
|
||||
StringBuilder para = null;
|
||||
Token idToken = null;
|
||||
Token paraToken = null;
|
||||
while(true) {
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (peek() == '#') { // #
|
||||
@ -141,6 +141,14 @@ class Lexer {
|
||||
continue ;
|
||||
}
|
||||
|
||||
// 在支持 #seleif 的基础上,支持 #else if
|
||||
if (symbol == Symbol.ELSE) {
|
||||
if (foundFollowingIf()) {
|
||||
id = "else if";
|
||||
symbol = Symbol.ELSEIF;
|
||||
}
|
||||
}
|
||||
|
||||
// 无参关键字指令
|
||||
if (symbol.noPara()) {
|
||||
return addNoParaToken(new Token(symbol, id, beginRow));
|
||||
@ -199,6 +207,22 @@ class Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
boolean foundFollowingIf() {
|
||||
int p = forward;
|
||||
while (CharTable.isBlank(buf[p])) {p++;}
|
||||
if (buf[p++] == 'i') {
|
||||
if (buf[p++] == 'f') {
|
||||
while (CharTable.isBlank(buf[p])) {p++;}
|
||||
// 要求出现 '(' 才认定解析成功,为了支持这种场景: #else if you ...
|
||||
if (buf[p] == '(') {
|
||||
forward = p;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用者已确定以字母或下划线开头,故一定可以获取到 id值
|
||||
*/
|
||||
@ -405,7 +429,7 @@ class Lexer {
|
||||
}
|
||||
|
||||
void skipBlanks() {
|
||||
while(CharTable.isBlank(buf[forward])) {
|
||||
while (CharTable.isBlank(buf[forward])) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package com.jfinal.template.stat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ExprParser;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
@ -84,10 +85,10 @@ public class Parser {
|
||||
throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row));
|
||||
}
|
||||
|
||||
public Stat parse() {
|
||||
public StatList parse() {
|
||||
tokenList = new Lexer(content, fileName).scan();
|
||||
tokenList.add(EOF);
|
||||
Stat statList = statList();
|
||||
StatList statList = statList();
|
||||
if (peek() != EOF) {
|
||||
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
|
||||
}
|
||||
@ -122,7 +123,7 @@ public class Parser {
|
||||
switch (name.symbol) {
|
||||
case TEXT:
|
||||
move();
|
||||
return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row));
|
||||
return new Text(((TextToken)name).getContent(), env.getEngineConfig().getEncoding()).setLocation(getLocation(name.row));
|
||||
case OUTPUT:
|
||||
move();
|
||||
Token para = matchPara(name);
|
||||
@ -171,9 +172,9 @@ public class Parser {
|
||||
String functionName = name.value();
|
||||
move();
|
||||
para = matchPara(name);
|
||||
Stat stat = statList();
|
||||
statList = statList();
|
||||
matchEnd(name);
|
||||
return new Define(functionName, parseExprList(para), stat, getLocation(name.row));
|
||||
return new Define(functionName, parseExprList(para), statList, getLocation(name.row));
|
||||
case CALL:
|
||||
functionName = name.value();
|
||||
move();
|
||||
@ -206,7 +207,7 @@ public class Parser {
|
||||
move();
|
||||
return Return.me;
|
||||
case ID:
|
||||
Stat dire = env.getEngineConfig().getDirective(name.value());
|
||||
Class<? extends Directive> dire = env.getEngineConfig().getDirective(name.value());
|
||||
if (dire == null) {
|
||||
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
|
||||
}
|
||||
@ -215,9 +216,9 @@ public class Parser {
|
||||
para = matchPara(name);
|
||||
ret.setExprList(parseExprList(para));
|
||||
|
||||
if (dire.hasEnd()) {
|
||||
if (ret.hasEnd()) {
|
||||
statList = statList();
|
||||
ret.setStat(statList);
|
||||
ret.setStat(statList.getActualStat());
|
||||
matchEnd(name);
|
||||
}
|
||||
return ret;
|
||||
@ -236,9 +237,9 @@ public class Parser {
|
||||
return new Location(fileName, row);
|
||||
}
|
||||
|
||||
private Stat createDirective(Stat dire, Token name) {
|
||||
private Stat createDirective(Class<? extends Directive> dire, Token name) {
|
||||
try {
|
||||
return dire.getClass().newInstance();
|
||||
return dire.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), getLocation(name.row), e);
|
||||
}
|
||||
|
@ -90,8 +90,19 @@ public class Scope {
|
||||
*/
|
||||
public Object get(Object key) {
|
||||
for (Scope cur=this; cur!=null; cur=cur.parent) {
|
||||
if (cur.data != null && cur.data.containsKey(key)) {
|
||||
return cur.data.get(key);
|
||||
// if (cur.data != null && cur.data.containsKey(key)) {
|
||||
// return cur.data.get(key);
|
||||
// }
|
||||
|
||||
if (cur.data != null) {
|
||||
Object ret = cur.data.get(key);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cur.data.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// return null;
|
||||
@ -229,6 +240,18 @@ public class Scope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自内向外在作用域栈中查找变量是否存在
|
||||
*/
|
||||
public boolean exists(Object key) {
|
||||
for (Scope cur=this; cur!=null; cur=cur.parent) {
|
||||
if (cur.data != null && cur.data.containsKey(key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
@ -25,6 +24,7 @@ import com.jfinal.template.stat.Scope;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.Id;
|
||||
import com.jfinal.template.io.Writer;
|
||||
|
||||
/**
|
||||
* Define 定义模板函数:
|
||||
@ -50,10 +50,10 @@ public class Define extends Stat {
|
||||
private String[] parameterNames;
|
||||
private Stat stat;
|
||||
|
||||
public Define(String functionName, ExprList exprList, Stat stat, Location location) {
|
||||
public Define(String functionName, ExprList exprList, StatList statList, Location location) {
|
||||
setLocation(location);
|
||||
this.functionName = functionName;
|
||||
this.stat = stat;
|
||||
this.stat = statList.getActualStat();
|
||||
|
||||
Expr[] exprArray = exprList.getExprArray();
|
||||
if (exprArray.length == 0) {
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
@ -27,8 +27,8 @@ public class Else extends Stat {
|
||||
|
||||
private Stat stat;
|
||||
|
||||
public Else(Stat stat) {
|
||||
this.stat = stat;
|
||||
public Else(StatList statList) {
|
||||
this.stat = statList.getActualStat();
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class ElseIf extends Stat {
|
||||
|
||||
private ExprList cond;
|
||||
private Expr cond;
|
||||
private Stat stat;
|
||||
private Stat elseIfOrElse;
|
||||
|
||||
public ElseIf(ExprList cond, Stat stat, Location location) {
|
||||
public ElseIf(ExprList cond, StatList statList, Location location) {
|
||||
if (cond.length() == 0) {
|
||||
throw new ParseException("The condition expression of #elseif statement can not be blank", location);
|
||||
throw new ParseException("The condition expression of #else if statement can not be blank", location);
|
||||
}
|
||||
this.cond = cond;
|
||||
this.stat = stat;
|
||||
this.cond = cond.getActualExpr();
|
||||
this.stat = statList.getActualStat();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Iterator;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ForCtrl;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
@ -38,12 +38,12 @@ import com.jfinal.template.stat.Scope;
|
||||
public class For extends Stat {
|
||||
|
||||
private ForCtrl forCtrl;
|
||||
private StatList statList;
|
||||
private Stat stat;
|
||||
private Stat _else;
|
||||
|
||||
public For(ForCtrl forCtrl, StatList statList, Stat _else) {
|
||||
this.forCtrl = forCtrl;
|
||||
this.statList = statList;
|
||||
this.stat = statList.getActualStat();
|
||||
this._else = _else;
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ public class For extends Stat {
|
||||
String itemName = forCtrl.getId();
|
||||
while(it.hasNext()) {
|
||||
scope.setLocal(itemName, it.next());
|
||||
statList.exec(env, scope, writer);
|
||||
stat.exec(env, scope, writer);
|
||||
forIteratorStatus.nextState();
|
||||
|
||||
if (ctrl.isJump()) {
|
||||
@ -108,7 +108,7 @@ public class For extends Stat {
|
||||
ctrl.setLocalAssignment();
|
||||
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
|
||||
ctrl.setWisdomAssignment();
|
||||
statList.exec(env, scope, writer);
|
||||
stat.exec(env, scope, writer);
|
||||
ctrl.setLocalAssignment();
|
||||
forLoopStatus.nextState();
|
||||
|
||||
|
@ -25,7 +25,7 @@ public class ForEntry implements Entry<Object, Object> {
|
||||
|
||||
private Entry<Object, Object> entry;
|
||||
|
||||
public ForEntry(Entry<Object, Object> entry) {
|
||||
public void init(Entry<Object, Object> entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
|
@ -60,11 +60,6 @@ public class ForIteratorStatus {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void init(Object target) {
|
||||
if (target == null) {
|
||||
size = 0;
|
||||
iterator = NullIterator.me;
|
||||
return ;
|
||||
}
|
||||
if (target instanceof Collection) {
|
||||
size = ((Collection<?>)target).size();
|
||||
iterator = ((Collection<?>)target).iterator();
|
||||
@ -75,6 +70,11 @@ public class ForIteratorStatus {
|
||||
iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator());
|
||||
return ;
|
||||
}
|
||||
if (target == null) { // 必须放在 target.getClass() 之前,避免空指针异常
|
||||
size = 0;
|
||||
iterator = NullIterator.me;
|
||||
return ;
|
||||
}
|
||||
if (target.getClass().isArray()) {
|
||||
size = Array.getLength(target);
|
||||
iterator = new ArrayIterator(target, size);
|
||||
@ -148,6 +148,7 @@ public class ForIteratorStatus {
|
||||
class MapIterator implements Iterator<Entry<Object, Object>> {
|
||||
|
||||
private Iterator<Entry<Object, Object>> iterator;
|
||||
private ForEntry forEntry = new ForEntry();
|
||||
|
||||
public MapIterator(Iterator<Entry<Object, Object>> iterator) {
|
||||
this.iterator = iterator;
|
||||
@ -158,7 +159,8 @@ class MapIterator implements Iterator<Entry<Object, Object>> {
|
||||
}
|
||||
|
||||
public Entry<Object, Object> next() {
|
||||
return new ForEntry((Entry<Object, Object>)iterator.next());
|
||||
forEntry.init(iterator.next());
|
||||
return forEntry;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class If extends Stat {
|
||||
|
||||
private ExprList cond;
|
||||
private Expr cond;
|
||||
private Stat stat;
|
||||
private Stat elseIfOrElse;
|
||||
|
||||
public If(ExprList cond, Stat stat, Location location) {
|
||||
public If(ExprList cond, StatList statList, Location location) {
|
||||
if (cond.length() == 0) {
|
||||
throw new ParseException("The condition expression of #if statement can not be blank", location);
|
||||
}
|
||||
this.cond = cond;
|
||||
this.stat = stat;
|
||||
this.cond = cond.getActualExpr();
|
||||
this.stat = statList.getActualStat();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,13 +16,13 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.Const;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.source.ISource;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
@ -94,7 +94,7 @@ public class Include extends Stat {
|
||||
if (config.isDevMode()) {
|
||||
env.addSource(fileSource);
|
||||
}
|
||||
this.stat = parser.parse();
|
||||
this.stat = parser.parse().getActualStat();
|
||||
} catch (Exception e) {
|
||||
// 文件路径不正确抛出异常时添加 location 信息
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/NullStat.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/NullStat.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
@ -34,21 +35,41 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class Output extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
private Expr expr;
|
||||
|
||||
public Output(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
throw new ParseException("The expression of output directive like #(expression) can not be blank", location);
|
||||
}
|
||||
this.exprList = exprList;
|
||||
this.expr = exprList.getActualExpr();
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
try {
|
||||
Object value = exprList.eval(scope);
|
||||
if (value != null) {
|
||||
String str = value.toString();
|
||||
Object value = expr.eval(scope);
|
||||
|
||||
if (value instanceof String) {
|
||||
String str = (String)value;
|
||||
writer.write(str, 0, str.length());
|
||||
} else if (value instanceof Number) {
|
||||
Class<?> c = value.getClass();
|
||||
if (c == Integer.class) {
|
||||
writer.write((Integer)value);
|
||||
} else if (c == Long.class) {
|
||||
writer.write((Long)value);
|
||||
} else if (c == Double.class) {
|
||||
writer.write((Double)value);
|
||||
} else if (c == Float.class) {
|
||||
writer.write((Float)value);
|
||||
} else if (c == Short.class) {
|
||||
writer.write((Short)value);
|
||||
} else {
|
||||
writer.write(value.toString());
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
writer.write((Boolean)value);
|
||||
} else if (value != null) {
|
||||
writer.write(value.toString());
|
||||
}
|
||||
} catch(TemplateException e) {
|
||||
throw e;
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
@ -36,7 +36,7 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class Set extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
private Expr expr;
|
||||
|
||||
public Set(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
@ -48,12 +48,12 @@ public class Set extends Stat {
|
||||
throw new ParseException("#set directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
this.expr = exprList.getActualExpr();
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope.getCtrl().setWisdomAssignment();
|
||||
exprList.eval(scope);
|
||||
expr.eval(scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class SetGlobal extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
private Expr expr;
|
||||
|
||||
public SetGlobal(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
@ -45,14 +45,14 @@ public class SetGlobal extends Stat {
|
||||
throw new ParseException("#setGlobal directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
this.expr = exprList.getActualExpr();
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setGlobalAssignment();
|
||||
exprList.eval(scope);
|
||||
expr.eval(scope);
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.Expr;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
@ -34,7 +34,7 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class SetLocal extends Stat {
|
||||
|
||||
final ExprList exprList;
|
||||
final Expr expr;
|
||||
|
||||
public SetLocal(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
@ -46,14 +46,14 @@ public class SetLocal extends Stat {
|
||||
throw new ParseException("#setLocal directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
this.expr = exprList.getActualExpr();
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setLocalAssignment();
|
||||
exprList.eval(scope);
|
||||
expr.eval(scope);
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
|
@ -17,10 +17,10 @@
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
@ -59,19 +59,6 @@ public abstract class Stat {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(Writer writer, char[] chars) {
|
||||
try {
|
||||
writer.write(chars, 0, chars.length);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
@ -28,24 +28,44 @@ import com.jfinal.template.stat.Scope;
|
||||
*/
|
||||
public class StatList extends Stat {
|
||||
|
||||
public static final Stat[] NULL_STATS = new Stat[0];
|
||||
public static final Stat NULL_STAT = NullStat.me;
|
||||
public static final Stat[] NULL_STAT_ARRAY = new Stat[0];
|
||||
|
||||
private Stat[] statArray;
|
||||
|
||||
public StatList(List<Stat> statList) {
|
||||
if (statList.size() > 0) {
|
||||
this.statArray = statList.toArray(new Stat[statList.size()]);
|
||||
} else {
|
||||
this.statArray = NULL_STATS;
|
||||
this.statArray = NULL_STAT_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 持有 StatList 的指令可以通过此方法提升 AST 执行性能
|
||||
* 1:当 statArray.length > 1 时返回 StatList 自身
|
||||
* 2:当 statArray.length == 1 时返回 statArray[0]
|
||||
* 3:其它情况返回 NullStat
|
||||
*
|
||||
* 意义在于,当满足前面两个条件时,避免掉了 StatList.exec(...) 方法中的判断与循环
|
||||
*/
|
||||
public Stat getActualStat() {
|
||||
if (statArray.length > 1) {
|
||||
return this;
|
||||
} else if (statArray.length == 1) {
|
||||
return statArray[0];
|
||||
} else {
|
||||
return NULL_STAT;
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
for (Stat stat : statArray) {
|
||||
for (int i=0; i<statArray.length; i++) {
|
||||
if (ctrl.isJump()) {
|
||||
break ;
|
||||
}
|
||||
stat.exec(env, scope, writer);
|
||||
statArray[i].exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,41 +17,98 @@
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.io.IWritable;
|
||||
import com.jfinal.template.io.Writer;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Text 输出纯文本块以及使用 "#[[" 与 "]]#" 指定的非解析块
|
||||
*/
|
||||
public class Text extends Stat {
|
||||
public class Text extends Stat implements IWritable {
|
||||
|
||||
private char[] text;
|
||||
// content、bytes、chars 三者必有一者不为 null
|
||||
// 在 OutputStream、Writer 混合模式下 bytes、chars 同时不为null
|
||||
private StringBuilder content;
|
||||
private Charset charset;
|
||||
private byte[] bytes;
|
||||
private char[] chars;
|
||||
|
||||
public Text(StringBuilder content) {
|
||||
this.text = new char[content.length()];
|
||||
content.getChars(0, content.length(), this.text, 0);
|
||||
// content 初始值在 Lexer 中已确保不为 null
|
||||
public Text(StringBuilder content, String encoding) {
|
||||
this.content = content;
|
||||
this.charset = Charset.forName(encoding);
|
||||
this.bytes = null;
|
||||
this.chars = null;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
try {
|
||||
writer.write(text, 0, text.length);
|
||||
writer.write(this);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return text.length == 0;
|
||||
public byte[] getBytes() {
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
bytes = content.toString().getBytes(charset);
|
||||
content = null;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bytes = new String(chars).getBytes(charset);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return text != null ? new String(text) : null;
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user