enjoy 3.3 release ^_^

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

View File

@ -4,7 +4,7 @@
<artifactId>enjoy</artifactId>
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,32 +105,29 @@ public class Logic extends Expr {
* 规则
* 1null 返回 false
* 2boolean 类型原值返回
* 3MapConnection(List被包括在内) 返回 size() > 0
* 4数组返回 length > 0
* 5StringStringBuilderStringBuffer 等继承自 CharSequence 类的对象返回 length > 0
* 6Number 类型返回 value != 0
* 7Iterator 返回 hasNext()
* 8其它返回 true
* 3StringStringBuilder 等一切继承自 CharSequence 类的对象返回 length > 0
* 4其它返回 true
*
* 通过 Logic.setToOldWorkMode() 设置可支持老版本中的以下四个规则
* 1Number 类型返回 value != 0
* 2MapCollection(List被包括在内) 返回 size() > 0
* 3数组返回 length > 0
* 4Iterator 返回 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 ( !newWorkMode ) {
if (v instanceof Number) {
if (v instanceof Double) {
return ((Number)v).doubleValue() != 0;
@ -130,9 +137,23 @@ public class Logic extends Expr {
}
return ((Number)v).intValue() != 0;
}
// 下面四种类型的判断已提供了 shared method 扩展用法如下
// #if(notEmpty(object)) 以及 #if(isEmpty(object))
if (v instanceof Collection) {
return ((Collection<?>)v).size() > 0;
}
if (v instanceof Map) {
return ((Map<?, ?>)v).size() > 0;
}
if (v.getClass().isArray()) {
return Array.getLength(v) > 0;
}
if (v instanceof Iterator) {
return ((Iterator<?>)v).hasNext();
}
}
return true;
}

View File

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

View File

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

View File

@ -0,0 +1,132 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.expr.ast;
import com.jfinal.kit.HashKit;
/**
* MethodKeyBuilder
*/
public abstract class MethodKeyBuilder {
/**
* 生成指定 class指定方法名指定方法形参类型的 key 用于缓存
*/
public abstract Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes);
// 默认使用 FastMethodKeyBuilder
static MethodKeyBuilder instance = new FastMethodKeyBuilder();
public static MethodKeyBuilder getInstance() {
return instance;
}
/**
* 切换到 StrictMethodKeyBuilder
*
* <pre>
* 特别注意
* 如果希望将 configEngine(Engine me) 中的 Engine 切换到 StrictMethodKeyBuilder
* 需要在 YourJFinalConfig extends JFinalConfig 中利用如下代码块才能生效
* static {
* MethodKeyBuilder.useStrictMethodKeyBuilder();
* }
*
* 原因是在 com.jfinal.core.Config new Engine() useStrictMethodKeyBuilder()
* 方法并未生效所以 extension method 生成 method key 时仍然使用的是 FastMethodKeyBuilder
* 以至于在运行时使用 StrictMethodKeyBuilder 生成的 key 找不到 extension method
*
* </pre>
*/
public static void useStrictMethodKeyBuilder() {
MethodKeyBuilder.instance = new StrictMethodKeyBuilder();
}
/**
* 切换到用户自定义 MethodKeyBuilder
*/
public static void setMethodKeyBuilder(MethodKeyBuilder methodKeyBuilder) {
if (methodKeyBuilder == null) {
throw new IllegalArgumentException("methodKeyBuilder can not be null");
}
MethodKeyBuilder.instance = methodKeyBuilder;
}
/**
* FastMethodKeyBuilder
*
* targetClassmethodNameargTypes hash 直接使用 String.hashCode()
* String.hashCode() 会被缓存性能更好
*/
public static class FastMethodKeyBuilder extends MethodKeyBuilder {
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
long hash = HashKit.FNV_OFFSET_BASIS_64;
hash ^= targetClass.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
hash ^= methodName.hashCode();
hash *= HashKit.FNV_PRIME_64;
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash ^= type.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
}
}
}
return hash;
}
}
/**
* StrictMethodKeyBuilder
*
* targetClassmethodNameargTypes 三部分全部使用 fnv1a64 算法计算 hash
*/
public static class StrictMethodKeyBuilder extends MethodKeyBuilder {
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
long hash = HashKit.FNV_OFFSET_BASIS_64;
hash = fnv1a64(hash, targetClass.getName());
hash = fnv1a64(hash, methodName);
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash = fnv1a64(hash, type.getName());
}
}
}
return hash;
}
private long fnv1a64(long offsetBasis, String key) {
long hash = offsetBasis;
for(int i=0, size=key.length(); i<size; i++) {
hash ^= key.charAt(i);
hash *= HashKit.FNV_PRIME_64;
}
return hash;
}
}
}

View File

@ -22,8 +22,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.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);
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.expr.ast;
import com.jfinal.template.stat.Scope;
/**
* NullExpr
*/
public class NullExpr extends Expr {
public static final NullExpr me = new NullExpr();
private NullExpr() {}
public Object eval(Scope scope) {
return null;
}
}

View File

@ -49,15 +49,18 @@ public class NullSafe extends Expr {
Ctrl ctrl = scope.getCtrl();
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 {

View File

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

View File

@ -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);
}
value = format(value, (String)dp);
write(writer, value.toString());
Object datePattern = this.datePatternExpr.eval(scope);
if ( !(datePattern instanceof String) ) {
throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location);
}
private String format(Object value, String datePattern) {
write(writer, (Date)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);
}
}

View File

@ -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;
/**

View File

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

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.ext.directive;
import java.text.DecimalFormat;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* #number 数字格式化输出指令
*
* 两种用法
* 1#number(n) 用默认 pattern 输出变量中的值
* 2#number(n, "#.##") 用第二个参数指定的 pattern 输出变量中的值
*
* 注意
* 1pattern 的使用与 java.text.DecimalFormat 的完全一样
* 在拿不定主意的时候可以在搜索引擎中搜索关键字DecimalFormat
* 2#number 指令中的参数可以是变量例如#number(n, p) 中的 n p 可以全都是变量
*
* <pre>
* 示例
* #number(3.1415926, "#.##")
* #number(0.9518, "#.##%")
* #number(300000, "光速为每秒 ,### 公里。")
*
* #set(n = 1.234)
* #set(p = "#.##")
* #number(n, p)
* </pre>
*/
public class NumberDirective extends Directive {
private Expr valueExpr;
private Expr patternExpr;
private int paraNum;
public void setExprList(ExprList exprList) {
this.paraNum = exprList.length();
if (paraNum == 0) {
throw new ParseException("The parameter of #number directive can not be blank", location);
}
if (paraNum > 2) {
throw new ParseException("Wrong number parameter of #number directive, two parameters allowed at most", location);
}
if (paraNum == 1) {
this.valueExpr = exprList.getExpr(0);
this.patternExpr = null;
} else if (paraNum == 2) {
this.valueExpr = exprList.getExpr(0);
this.patternExpr = exprList.getExpr(1);
}
}
public void exec(Env env, Scope scope, Writer writer) {
Object value = valueExpr.eval(scope);
if (value == null) {
return ;
}
if (paraNum == 1) {
outputWithoutPattern(writer, value);
} else if (paraNum == 2) {
outputWithPattern(scope, writer, value);
}
}
private void outputWithoutPattern(Writer writer, Object value) {
String ret = new DecimalFormat().format(value);
write(writer, ret);
}
private void outputWithPattern(Scope scope, Writer writer, Object value) {
Object pattern = patternExpr.eval(scope);
if ( !(pattern instanceof String) ) {
throw new TemplateException("The sencond parameter pattern of #number directive must be String", location);
}
String ret = new DecimalFormat((String)pattern).format(value);
write(writer, ret);
}
}

View File

@ -16,9 +16,9 @@
package com.jfinal.template.ext.directive;
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;
/**

View File

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

View File

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

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.ext.sharedmethod;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* SharedMethodLib 共享方法库逐步添加一些最常用的共享方法
*
* <br>
* 3.3 版本之前的 Logic.isTrue(Object) 方法不再对 Collection
* Map数组IteratorIterable 进行为空的判断这部分逻辑已转移至
* SharedMethodLib.isEmpty(Object)
*/
public class SharedMethodLib {
/**
* 判断 CollectionMap数组IteratorIterable 类型对象中的元素个数是否为 0
* 规则
* 1null 返回 true
* 2ListSet 等一切继承自 Collection 返回 isEmpty()
* 3Map 返回 isEmpty()
* 4数组返回 length == 0
* 5Iterator 返回 ! hasNext()
* 6Iterable 返回 ! iterator().hasNext()
*
* 注意原先 Logic.isTrue(Object) 中对集合与数组类型为空的判断转移到此方法中
*/
public Boolean isEmpty(Object v) {
if (v == null) {
return true;
}
if (v instanceof Collection) {
return ((Collection<?>)v).isEmpty();
}
if (v instanceof Map) {
return ((Map<?, ?>)v).isEmpty();
}
if (v.getClass().isArray()) {
return Array.getLength(v) == 0;
}
if (v instanceof Iterator) {
return ! ((Iterator<?>)v).hasNext();
}
if (v instanceof Iterable) {
return ! ((Iterable<?>)v).iterator().hasNext();
}
throw new IllegalArgumentException("isEmpty(...) 方法只能接受 Collection、Map、数组、Iterator、Iterable 类型参数");
}
public Boolean notEmpty(Object v) {
return !isEmpty(v);
}
}

View File

@ -16,7 +16,7 @@
package com.jfinal.template.ext.spring;
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);
}
}

View File

@ -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 enjoyjspfreemarkervelocity 四类模板共存于一个项目中
*
* 注意这里采用识别 ".jsp"".ftl"".vm" 模板后缀名的方式来实现功能
* 所以 jfinal enjoy 模板不要采用上述三种后缀名否则功能将失效
* 还要注意与 jspfreemarkervelocity 以外类型模板共存使用时
* 需要改造该方法
*/
protected View loadView(String viewName, Locale locale) throws Exception {
String suffix = getSuffix();
if (".jsp".equals(suffix) || ".ftl".equals(suffix) || ".vm".equals(suffix)) {
return null;
} else {
return super.loadView(viewName, locale);
}
}
/**
* spring 回调利用 ServletContext 做必要的初始化工作
*/
@Override
protected void initServletContext(ServletContext servletContext) {
super.initServletContext(servletContext);
super.setExposeRequestAttributes(true);
initBaseTemplatePath(servletContext);
initSharedFunction();

View File

@ -0,0 +1,122 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.io.IOException;
import java.io.OutputStream;
/**
* ByteWriter
*/
public class ByteWriter extends Writer {
OutputStream out;
Encoder encoder;
char[] chars;
byte[] bytes;
public ByteWriter(Encoder encoder, int bufferSize) {
this.encoder = encoder;
this.chars = new char[bufferSize];
this.bytes = new byte[bufferSize * ((int)encoder.maxBytesPerChar())];
}
public ByteWriter init(OutputStream outputStream) {
this.out = outputStream;
return this;
}
public void flush() throws IOException {
out.flush();
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
}
public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) {
write(str, offset, chars.length);
offset += chars.length;
len -= chars.length;
}
str.getChars(offset, offset + len, chars, 0);
int byteLen = encoder.encode(chars, 0, len, bytes);
out.write(bytes, 0, byteLen);
}
public void write(String str) throws IOException {
write(str, 0, str.length());
}
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
while (len > chars.length) {
write(stringBuilder, offset, chars.length);
offset += chars.length;
len -= chars.length;
}
stringBuilder.getChars(offset, offset + len, chars, 0);
int byteLen = encoder.encode(chars, 0, len, bytes);
out.write(bytes, 0, byteLen);
}
public void write(StringBuilder stringBuilder) throws IOException {
write(stringBuilder, 0, stringBuilder.length());
}
public void write(IWritable writable) throws IOException {
byte[] data = writable.getBytes();
out.write(data, 0, data.length);
}
public void write(int intValue) throws IOException {
IntegerWriter.write(this, intValue);
}
public void write(long longValue) throws IOException {
LongWriter.write(this, longValue);
}
public void write(double doubleValue) throws IOException {
FloatingWriter.write(this, doubleValue);
}
public void write(float floatValue) throws IOException {
FloatingWriter.write(this, floatValue);
}
private static final byte[] TRUE_BYTES = "true".getBytes();
private static final byte[] FALSE_BYTES = "false".getBytes();
public void write(boolean booleanValue) throws IOException {
out.write(booleanValue ? TRUE_BYTES : FALSE_BYTES);
}
}

View File

@ -0,0 +1,117 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.io.IOException;
/**
* CharWriter
*/
public class CharWriter extends Writer {
java.io.Writer out;
char[] chars;
public CharWriter(int bufferSize) {
this.chars = new char[bufferSize];
}
public CharWriter init(java.io.Writer writer) {
this.out = writer;
return this;
}
public void flush() throws IOException {
out.flush();
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
}
public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) {
write(str, offset, chars.length);
offset += chars.length;
len -= chars.length;
}
str.getChars(offset, offset + len, chars, 0);
out.write(chars, 0, len);
}
public void write(String str) throws IOException {
write(str, 0, str.length());
}
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
while (len > chars.length) {
write(stringBuilder, offset, chars.length);
offset += chars.length;
len -= chars.length;
}
stringBuilder.getChars(offset, offset + len, chars, 0);
out.write(chars, 0, len);
}
public void write(StringBuilder stringBuilder) throws IOException {
write(stringBuilder, 0, stringBuilder.length());
}
public void write(IWritable writable) throws IOException {
char[] data = writable.getChars();
out.write(data, 0, data.length);
}
public void write(int intValue) throws IOException {
IntegerWriter.write(this, intValue);
}
public void write(long longValue) throws IOException {
LongWriter.write(this, longValue);
}
public void write(double doubleValue) throws IOException {
FloatingWriter.write(this, doubleValue);
}
public void write(float floatValue) throws IOException {
FloatingWriter.write(this, floatValue);
}
private static final char[] TRUE_CHARS = "true".toCharArray();
private static final char[] FALSE_CHARS = "false".toCharArray();
public void write(boolean booleanValue) throws IOException {
out.write(booleanValue ? TRUE_CHARS : FALSE_CHARS);
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
/**
* DateFormats
*/
public class DateFormats {
private Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
public SimpleDateFormat getDateFormat(String datePattern) {
SimpleDateFormat ret = map.get(datePattern);
if (ret == null) {
ret = new SimpleDateFormat(datePattern);
map.put(datePattern, ret);
}
return ret;
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
/**
* Encoder
*/
public abstract class Encoder {
public abstract float maxBytesPerChar();
public abstract int encode(char[] chars, int offset, int len, byte[] bytes);
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.nio.charset.Charset;
import com.jfinal.template.EngineConfig;
/**
* EncoderFactory
*/
public class EncoderFactory {
protected Charset charset = Charset.forName(EngineConfig.DEFAULT_ENCODING);
void setEncoding(String encoding) {
charset = Charset.forName(encoding);
}
public Encoder getEncoder() {
if (Charset.forName("UTF-8").equals(charset)) {
return Utf8Encoder.me;
} else {
return new JdkEncoder(charset);
}
}
}

View File

@ -14,16 +14,19 @@
* limitations under the License.
*/
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 改造而来在其基础之上做了如下改变
* 1StringBuffer 属性改为 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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.io.IOException;
/**
* FloatingWriter
*/
public class FloatingWriter {
public static void write(ByteWriter byteWriter, double doubleValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(doubleValue);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
int len = fd.getChars(chars);
for (int i=0; i<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(ByteWriter byteWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
int len = fd.getChars(chars);
for (int i=0; i<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(CharWriter charWriter, double doubleValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(doubleValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
public static void write(CharWriter charWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
/**
* IWritable 支持 OutputStreamWriter 双模式动态切换输出
*
* 详见 com.jfinal.template.stat.ast.Text 中的用法
*/
public interface IWritable {
/**
* OutputStream 模式下的 ByteWrite 使用
*/
public byte[] getBytes();
/**
* Writer 模式下的 CharWrite 使用
*/
public char[] getChars();
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
package com.jfinal.template.io;
import java.io.IOException;
public class IntegerWriter {
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
final static char [] DigitOnes = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
} ;
final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
} ;
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
private static final byte[] minValueBytes = "-2147483648".getBytes();
private static final char[] minValueChars = "-2147483648".toCharArray();
public static void write(ByteWriter byteWriter, int i) throws IOException {
if (i == Integer.MIN_VALUE) {
byteWriter.out.write(minValueBytes, 0, minValueBytes.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
getChars(i, size, chars);
// int len = Utf8Encoder.me.encode(chars, 0, size, bytes);
// byteWriter.out.write(bytes, 0, len);
for (int j=0; j<size; j++) {
bytes[j] = (byte)chars[j];
}
byteWriter.out.write(bytes, 0, size);
}
public static void write(CharWriter charWriter, int i) throws IOException {
if (i == Integer.MIN_VALUE) {
charWriter.out.write(minValueChars, 0, minValueChars.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = charWriter.chars;
getChars(i, size, chars);
charWriter.out.write(chars, 0, size);
}
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* JdkEncoder
*/
public class JdkEncoder extends Encoder {
private CharsetEncoder ce;
public JdkEncoder(Charset charset) {
this.ce = charset.newEncoder();
}
public float maxBytesPerChar() {
return ce.maxBytesPerChar();
}
public int encode(char[] chars, int offset, int len, byte[] bytes) {
ce.reset();
ByteBuffer bb = ByteBuffer.wrap(bytes);
CharBuffer cb = CharBuffer.wrap(chars, offset, len);
try {
CoderResult cr = ce.encode(cb, bb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = ce.flush(bb);
if (!cr.isUnderflow())
cr.throwException();
return bb.position();
} catch (CharacterCodingException x) {
// Substitution is always enabled,
// so this shouldn't happen
throw new RuntimeException("Encode error: " + x.getMessage(), x);
}
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
package com.jfinal.template.io;
import java.io.IOException;
public class LongWriter {
private static final byte[] minValueBytes = "-9223372036854775808".getBytes();
private static final char[] minValueChars = "-9223372036854775808".toCharArray();
public static void write(ByteWriter byteWriter, long value) throws IOException {
if (value == Long.MIN_VALUE) {
byteWriter.out.write(minValueBytes, 0, minValueBytes.length);
return ;
}
int size = (value < 0) ? stringSize(-value) + 1 : stringSize(value);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
getChars(value, size, chars);
// int len = Utf8Encoder.me.encode(chars, 0, size, bytes);
// byteWriter.out.write(bytes, 0, len);
for (int j=0; j<size; j++) {
bytes[j] = (byte)chars[j];
}
byteWriter.out.write(bytes, 0, size);
}
public static void write(CharWriter charWriter, long i) throws IOException {
if (i == Long.MIN_VALUE) {
charWriter.out.write(minValueChars, 0, minValueChars.length);
return ;
}
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] chars = charWriter.chars;
getChars(i, size, chars);
charWriter.out.write(chars, 0, size);
}
static int stringSize(long x) {
long p = 10;
for (int i=1; i<19; i++) {
if (x < p)
return i;
p = 10*p;
}
return 19;
}
static void getChars(long i, int index, char[] buf) {
long q;
int r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Get 2 digits/iteration using longs until quotient fits into an int
while (i > Integer.MAX_VALUE) {
q = i / 100;
// really: r = i - (q * 100);
r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));
i = q;
buf[--charPos] = IntegerWriter.DigitOnes[r];
buf[--charPos] = IntegerWriter.DigitTens[r];
}
// Get 2 digits/iteration using ints
int q2;
int i2 = (int)i;
while (i2 >= 65536) {
q2 = i2 / 100;
// really: r = i2 - (q * 100);
r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2));
i2 = q2;
buf[--charPos] = IntegerWriter.DigitOnes[r];
buf[--charPos] = IntegerWriter.DigitTens[r];
}
// Fall thru to fast mode for smaller numbers
// assert(i2 <= 65536, i2);
for (;;) {
q2 = (i2 * 52429) >>> (16+3);
r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ...
buf[--charPos] = IntegerWriter.digits[r];
i2 = q2;
if (i2 == 0) break;
}
if (sign != 0) {
buf[--charPos] = sign;
}
}
}

View File

@ -0,0 +1,98 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.nio.charset.MalformedInputException;
/**
* Utf8Encoder
*
* http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/nio/cs/UTF_8.java?av=f
* http://grepcode.com/search?query=ArrayEncoder&start=0&entity=type&n=
*/
public class Utf8Encoder extends Encoder {
public static final Utf8Encoder me = new Utf8Encoder();
public float maxBytesPerChar() {
return 3.0F;
}
public int encode(char[] chars, int offset, int len, byte[] bytes) {
int sl = offset + len;
int dp = 0;
int dlASCII = dp + Math.min(len, bytes.length);
// ASCII only optimized loop
while (dp < dlASCII && chars[offset] < '\u0080') {
bytes[dp++] = (byte) chars[offset++];
}
while (offset < sl) {
char c = chars[offset++];
if (c < 0x80) {
// Have at most seven bits
bytes[dp++] = (byte) c;
} else if (c < 0x800) {
// 2 bytes, 11 bits
bytes[dp++] = (byte) (0xc0 | (c >> 6));
bytes[dp++] = (byte) (0x80 | (c & 0x3f));
} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7
final int uc;
int ip = offset - 1;
if (Character.isHighSurrogate(c)) {
if (sl - ip < 2) {
uc = -1;
} else {
char d = chars[ip + 1];
if (Character.isLowSurrogate(d)) {
uc = Character.toCodePoint(c, d);
} else {
throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
}
}
} else {
if (Character.isLowSurrogate(c)) {
throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
} else {
uc = c;
}
}
if (uc < 0) {
bytes[dp++] = (byte) '?';
} else {
bytes[dp++] = (byte) (0xf0 | ((uc >> 18)));
bytes[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));
bytes[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));
bytes[dp++] = (byte) (0x80 | (uc & 0x3f));
offset++; // 2 chars
}
} else {
// 3 bytes, 16 bits
bytes[dp++] = (byte) (0xe0 | ((c >> 12)));
bytes[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));
bytes[dp++] = (byte) (0x80 | (c & 0x3f));
}
}
return dp;
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
import java.io.IOException;
import java.util.Date;
/**
* Writer
*/
public abstract class Writer {
protected DateFormats formats = new DateFormats();
public abstract void flush() throws IOException;
public abstract void close();
public abstract void write(IWritable writable) throws IOException;
public abstract void write(String string, int offset, int length) throws IOException;
public abstract void write(String string) throws IOException;
public abstract void write(StringBuilder stringBuilder, int offset, int length) throws IOException;
public abstract void write(StringBuilder stringBuilder) throws IOException;
public abstract void write(boolean booleanValue) throws IOException;
public abstract void write(int intValue) throws IOException;
public abstract void write(long longValue) throws IOException;
public abstract void write(double doubleValue) throws IOException;
public abstract void write(float floatValue) throws IOException;
public void write(short shortValue) throws IOException {
write((int)shortValue);
}
public void write(Date date, String datePattern) throws IOException {
String str = formats.getDateFormat(datePattern).format(date);
write(str, 0, str.length());
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
/**
* WriterBuffer
*/
public class WriterBuffer {
private static final int MIN_BUFFER_SIZE = 64; // 缓冲区最小 64 字节
private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节
private int bufferSize = 2048; // 缓冲区大小
private EncoderFactory encoderFactory = new EncoderFactory();
private final ThreadLocal<ByteWriter> byteWriters = new ThreadLocal<ByteWriter>() {
protected ByteWriter initialValue() {
return new ByteWriter(encoderFactory.getEncoder(), bufferSize);
}
};
private final ThreadLocal<CharWriter> charWriters = new ThreadLocal<CharWriter>() {
protected CharWriter initialValue() {
return new CharWriter(bufferSize);
}
};
private final ThreadLocal<FastStringWriter> fastStringWriters = new ThreadLocal<FastStringWriter>() {
protected FastStringWriter initialValue() {
return new FastStringWriter();
}
};
public ByteWriter getByteWriter(java.io.OutputStream outputStream) {
return byteWriters.get().init(outputStream);
}
public CharWriter getCharWriter(java.io.Writer writer) {
return charWriters.get().init(writer);
}
public FastStringWriter getFastStringWriter() {
return fastStringWriters.get();
}
public void setBufferSize(int bufferSize) {
if (bufferSize < MIN_BUFFER_SIZE || bufferSize > MAX_BUFFER_SIZE) {
throw new IllegalArgumentException("bufferSize must between " + (MIN_BUFFER_SIZE-1) + " and " + (MAX_BUFFER_SIZE+1));
}
this.bufferSize = bufferSize;
}
public void setEncoderFactory(EncoderFactory encoderFactory) {
if (encoderFactory == null) {
throw new IllegalArgumentException("encoderFactory can not be null");
}
this.encoderFactory = encoderFactory;
}
public void setEncoding(String encoding) {
encoderFactory.setEncoding(encoding);
}
}

View File

@ -141,6 +141,14 @@ class Lexer {
continue ;
}
// 在支持 #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值
*/

View File

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

View File

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

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

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

View File

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

View File

@ -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 #else if statement can not be blank", location);
}
this.cond = cond;
this.stat = stat;
this.cond = cond.getActualExpr();
this.stat = statList.getActualStat();
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* NullStat
*/
public class NullStat extends Stat {
public static final NullStat me = new NullStat();
private NullStat() {}
public void exec(Env env, Scope scope, Writer writer) {
}
}

View File

@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast;
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;

View File

@ -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;
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
// contentbyteschars 三者必有一者不为 null
// OutputStreamWriter 混合模式下 byteschars 同时不为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;
}
public String getContent() {
return text != null ? new String(text) : null;
if (content != null) {
bytes = content.toString().getBytes(charset);
content = null;
return bytes;
}
bytes = new String(chars).getBytes(charset);
return bytes;
}
public char[] getChars() {
if (chars != null) {
return chars;
}
if (content != null) {
char[] charsTemp = new char[content.length()];
content.getChars(0, content.length(), charsTemp, 0);
chars = charsTemp;
content = null;
return chars;
}
String strTemp = new String(bytes, charset);
char[] charsTemp = new char[strTemp.length()];
strTemp.getChars(0, strTemp.length(), charsTemp, 0);
chars = charsTemp;
return chars;
}
public boolean isEmpty() {
if (content != null) {
return content.length() == 0;
} else if (bytes != null) {
return bytes.length == 0;
} else {
return chars.length == 0;
}
}
// public String getContent() {
// return text != null ? new String(text) : null;
// }
public String toString() {
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();
}
}
}