Enjoy 3.2 release ^_^
This commit is contained in:
63
src/main/java/com/jfinal/template/Directive.java
Normal file
63
src/main/java/com/jfinal/template/Directive.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
|
||||
/**
|
||||
* Directive 供用户继承并扩展自定义指令,具体用法可以参考
|
||||
* com.jfinal.template.ext.directive 包下面的例子
|
||||
*/
|
||||
public abstract class Directive extends Stat {
|
||||
|
||||
/**
|
||||
* 传递给指令的表达式列表
|
||||
* 1:表达式列表可通过 exprList.eval(scope) 以及 exprList.evalExprList(scope) 进行求值
|
||||
* 2:使用赋值表达式可实现参数传递功能
|
||||
*
|
||||
* <pre>
|
||||
* 例如:#render("_hot.html", title="热门新闻", list=newsList)
|
||||
* </pre>
|
||||
*/
|
||||
protected ExprList exprList;
|
||||
|
||||
/**
|
||||
* 具有 #end 结束符的指令内部嵌套的所有内容,调用 stat.exec(env, scope, writer)
|
||||
* 即可执行指令内部嵌入所有指令与表达式,如果指令没有 #end 结束符,该属性无效
|
||||
*/
|
||||
protected Stat stat;
|
||||
|
||||
/**
|
||||
* 指令被解析时注入指令参数表达式列表,继承类可以通过覆盖此方法对参数长度和参数类型进行校验
|
||||
*/
|
||||
public void setExprList(ExprList exprList) {
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令被解析时注入指令 body 内容,仅对于具有 #end 结束符的指令有效
|
||||
*/
|
||||
public void setStat(Stat stat) {
|
||||
this.stat = stat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
480
src/main/java/com/jfinal/template/Engine.java
Normal file
480
src/main/java/com/jfinal/template/Engine.java
Normal file
@@ -0,0 +1,480 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.jfinal.kit.HashKit;
|
||||
import com.jfinal.kit.StrKit;
|
||||
import com.jfinal.template.expr.ast.MethodKit;
|
||||
import com.jfinal.template.source.ISource;
|
||||
import com.jfinal.template.source.ISourceFactory;
|
||||
import com.jfinal.template.source.StringSource;
|
||||
import com.jfinal.template.stat.Parser;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
|
||||
/**
|
||||
* Engine
|
||||
*
|
||||
* Example:
|
||||
* Engine.use().getTemplate(fileName).render(...);
|
||||
* Engine.use().getTemplate(fileName).renderToString(...);
|
||||
*/
|
||||
public class Engine {
|
||||
|
||||
public static final String MAIN_ENGINE_NAME = "main";
|
||||
|
||||
private static Engine MAIN_ENGINE;
|
||||
private static Map<String, Engine> engineMap = new HashMap<String, Engine>();
|
||||
|
||||
// Create main engine
|
||||
static {
|
||||
MAIN_ENGINE = new Engine(MAIN_ENGINE_NAME);
|
||||
engineMap.put(MAIN_ENGINE_NAME, MAIN_ENGINE);
|
||||
}
|
||||
|
||||
private String name;
|
||||
private boolean devMode = false;
|
||||
private EngineConfig config = new EngineConfig();
|
||||
private ISourceFactory sourceFactory = config.getSourceFactory();
|
||||
|
||||
private Map<String, Template> templateCache = new HashMap<String, Template>();
|
||||
|
||||
/**
|
||||
* Create engine without management of JFinal
|
||||
*/
|
||||
public Engine() {
|
||||
this.name = "NO_NAME";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create engine by engineName without management of JFinal
|
||||
*/
|
||||
public Engine(String engineName) {
|
||||
this.name = engineName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the main Engine
|
||||
*/
|
||||
public static Engine use() {
|
||||
return MAIN_ENGINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the engine with engine name
|
||||
*/
|
||||
public static Engine use(String engineName) {
|
||||
return engineMap.get(engineName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create engine with engine name managed by JFinal
|
||||
*/
|
||||
public synchronized static Engine create(String engineName) {
|
||||
if (StrKit.isBlank(engineName)) {
|
||||
throw new IllegalArgumentException("Engine name can not be blank");
|
||||
}
|
||||
engineName = engineName.trim();
|
||||
if (engineMap.containsKey(engineName)) {
|
||||
throw new IllegalArgumentException("Engine already exists : " + engineName);
|
||||
}
|
||||
Engine newEngine = new Engine(engineName);
|
||||
engineMap.put(engineName, newEngine);
|
||||
return newEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove engine with engine name managed by JFinal
|
||||
*/
|
||||
public synchronized static Engine remove(String engineName) {
|
||||
Engine removed = engineMap.remove(engineName);
|
||||
if (removed != null && MAIN_ENGINE_NAME.equals(removed.name)) {
|
||||
Engine.MAIN_ENGINE = null;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set main engine
|
||||
*/
|
||||
public synchronized static void setMainEngine(Engine engine) {
|
||||
if (engine == null) {
|
||||
throw new IllegalArgumentException("Engine can not be null");
|
||||
}
|
||||
engine.name = Engine.MAIN_ENGINE_NAME;
|
||||
engineMap.put(Engine.MAIN_ENGINE_NAME, engine);
|
||||
Engine.MAIN_ENGINE = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template with file name
|
||||
*/
|
||||
public Template getTemplate(String fileName) {
|
||||
if (fileName.charAt(0) != '/') {
|
||||
char[] arr = new char[fileName.length() + 1];
|
||||
fileName.getChars(0, fileName.length(), arr, 1);
|
||||
arr[0] = '/';
|
||||
fileName = new String(arr);
|
||||
}
|
||||
|
||||
Template template = templateCache.get(fileName);
|
||||
if (template == null) {
|
||||
template = buildTemplateBySourceFactory(fileName);
|
||||
templateCache.put(fileName, template);
|
||||
} else if (devMode) {
|
||||
if (template.isModified()) {
|
||||
template = buildTemplateBySourceFactory(fileName);
|
||||
templateCache.put(fileName, template);
|
||||
}
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
private Template buildTemplateBySourceFactory(String fileName) {
|
||||
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), fileName, config.getEncoding());
|
||||
ISource source = sourceFactory.getSource(config.getBaseTemplatePath(), fileName, config.getEncoding());
|
||||
Env env = new Env(config);
|
||||
Parser parser = new Parser(env, source.getContent(), fileName);
|
||||
if (devMode) {
|
||||
env.addSource(source);
|
||||
}
|
||||
Stat stat = parser.parse();
|
||||
Template template = new Template(env, stat);
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template by string content and do not cache the template
|
||||
*
|
||||
* 重要:StringSource 中的 key = HashKit.md5(content),也即 key
|
||||
* 与 content 有紧密的对应关系,当 content 发生变化时 key 值也相应变化
|
||||
* 因此,原先 key 所对应的 Template 缓存对象已无法被获取,当 getTemplateByString(String)
|
||||
* 的 String 参数的数量不确定时会引发内存泄漏
|
||||
*
|
||||
* 当 getTemplateByString(String, boolean) 中的 String 参数的
|
||||
* 数量可控并且确定时,才可对其使用缓存
|
||||
*/
|
||||
public Template getTemplateByString(String content) {
|
||||
return getTemplateByString(content, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template by string content
|
||||
* @param content 模板内容
|
||||
* @param cache true 则缓存 Template,否则不缓存
|
||||
*/
|
||||
public Template getTemplateByString(String content, boolean cache) {
|
||||
if (!cache) {
|
||||
return buildTemplateBySource(new StringSource(content, cache));
|
||||
}
|
||||
|
||||
String key = HashKit.md5(content);
|
||||
Template template = templateCache.get(key);
|
||||
if (template == null) {
|
||||
template = buildTemplateBySource(new StringSource(content, cache));
|
||||
templateCache.put(key, template);
|
||||
} else if (devMode) {
|
||||
if (template.isModified()) {
|
||||
template = buildTemplateBySource(new StringSource(content, cache));
|
||||
templateCache.put(key, template);
|
||||
}
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template with implementation of ISource
|
||||
*/
|
||||
public Template getTemplate(ISource source) {
|
||||
String key = source.getKey();
|
||||
if (key == null) { // key 为 null 则不缓存,详见 ISource.getKey() 注释
|
||||
return buildTemplateBySource(source);
|
||||
}
|
||||
|
||||
Template template = templateCache.get(key);
|
||||
if (template == null) {
|
||||
template = buildTemplateBySource(source);
|
||||
templateCache.put(key, template);
|
||||
} else if (devMode) {
|
||||
if (template.isModified()) {
|
||||
template = buildTemplateBySource(source);
|
||||
templateCache.put(key, template);
|
||||
}
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
private Template buildTemplateBySource(ISource source) {
|
||||
Env env = new Env(config);
|
||||
Parser parser = new Parser(env, source.getContent(), null);
|
||||
if (devMode) {
|
||||
env.addSource(source);
|
||||
}
|
||||
Stat stat = parser.parse();
|
||||
Template template = new Template(env, stat);
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function with file
|
||||
*/
|
||||
public Engine addSharedFunction(String fileName) {
|
||||
config.addSharedFunction(fileName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function by ISource
|
||||
*/
|
||||
public Engine addSharedFunction(ISource source) {
|
||||
config.addSharedFunction(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function with files
|
||||
*/
|
||||
public Engine addSharedFunction(String... fileNames) {
|
||||
config.addSharedFunction(fileNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function by string content
|
||||
*/
|
||||
public Engine addSharedFunctionByString(String content) {
|
||||
config.addSharedFunctionByString(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared object
|
||||
*/
|
||||
public Engine addSharedObject(String name, Object object) {
|
||||
config.addSharedObject(name, object);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set output directive factory
|
||||
*/
|
||||
public Engine setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) {
|
||||
config.setOutputDirectiveFactory(outputDirectiveFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add directive
|
||||
*/
|
||||
public Engine addDirective(String directiveName, Directive directive) {
|
||||
config.addDirective(directiveName, directive);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove directive
|
||||
*/
|
||||
public Engine removeDirective(String directiveName) {
|
||||
config.removeDirective(directiveName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared method from object
|
||||
*/
|
||||
public Engine addSharedMethod(Object sharedMethodFromObject) {
|
||||
config.addSharedMethod(sharedMethodFromObject);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared method from class
|
||||
*/
|
||||
public Engine addSharedMethod(Class<?> sharedMethodFromClass) {
|
||||
config.addSharedMethod(sharedMethodFromClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared static method of Class
|
||||
*/
|
||||
public Engine addSharedStaticMethod(Class<?> sharedStaticMethodFromClass) {
|
||||
config.addSharedStaticMethod(sharedStaticMethodFromClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method with method name
|
||||
*/
|
||||
public Engine removeSharedMethod(String methodName) {
|
||||
config.removeSharedMethod(methodName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method of the Class
|
||||
*/
|
||||
public Engine removeSharedMethod(Class<?> clazz) {
|
||||
config.removeSharedMethod(clazz);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method
|
||||
*/
|
||||
public Engine removeSharedMethod(Method method) {
|
||||
config.removeSharedMethod(method);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove template cache with template key
|
||||
*/
|
||||
public void removeTemplateCache(String templateKey) {
|
||||
templateCache.remove(templateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all template cache
|
||||
*/
|
||||
public void removeAllTemplateCache() {
|
||||
templateCache.clear();
|
||||
}
|
||||
|
||||
public int getTemplateCacheSize() {
|
||||
return templateCache.size();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Template Engine: " + name;
|
||||
}
|
||||
|
||||
// Engine config below ---------
|
||||
|
||||
public EngineConfig getEngineConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 true 为开发模式,支持模板文件热加载
|
||||
* 设置 false 为生产模式,不支持模板文件热加载,以达到更高的性能
|
||||
*/
|
||||
public Engine setDevMode(boolean devMode) {
|
||||
this.devMode = devMode;
|
||||
this.config.setDevMode(devMode);
|
||||
if (this.devMode) {
|
||||
removeAllTemplateCache();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getDevMode() {
|
||||
return devMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ISourceFactory 用于为 engine 切换不同的 ISource 实现类
|
||||
* ISource 用于从不同的来源加载模板内容
|
||||
*
|
||||
* <pre>
|
||||
* 配置为 ClassPathSourceFactory 时特别注意:
|
||||
* 由于 JFinal 会在 configEngine(Engine me) 方法调用 “之前”,会默认调用一次如下方法:
|
||||
* me.setBaseTemplatePath(PathKit.getWebRootPath())
|
||||
*
|
||||
* 而 ClassPathSourceFactory 在以上默认值下不能工作,所以需要通过如下方式清掉该值:
|
||||
* me.setBaseTemplatePath(null)
|
||||
*
|
||||
* 或者配置具体要用的 baseTemplatePath 值,例如:
|
||||
* me.setBaseTemplatePath("view");
|
||||
* </pre>
|
||||
*/
|
||||
public Engine setSourceFactory(ISourceFactory sourceFactory) {
|
||||
this.config.setSourceFactory(sourceFactory); // 放第一行先进行参数验证
|
||||
this.sourceFactory = sourceFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISourceFactory getSourceFactory() {
|
||||
return sourceFactory;
|
||||
}
|
||||
|
||||
public Engine setBaseTemplatePath(String baseTemplatePath) {
|
||||
config.setBaseTemplatePath(baseTemplatePath);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getBaseTemplatePath() {
|
||||
return config.getBaseTemplatePath();
|
||||
}
|
||||
|
||||
public Engine setDatePattern(String datePattern) {
|
||||
config.setDatePattern(datePattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDatePattern() {
|
||||
return config.getDatePattern();
|
||||
}
|
||||
|
||||
public Engine setEncoding(String encoding) {
|
||||
config.setEncoding(encoding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return config.getEncoding();
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效,
|
||||
* 但如果在 devMode 之下并不希望对 addSharedFunction(...),
|
||||
* 添加的模板进行是否被修改的检测可以通过此方法设置 false 参进去
|
||||
*
|
||||
* 注意:Engine 在生产环境下(devMode 为 false),该参数无效
|
||||
*/
|
||||
public Engine setReloadModifiedSharedFunctionInDevMode(boolean reloadModifiedSharedFunctionInDevMode) {
|
||||
config.setReloadModifiedSharedFunctionInDevMode(reloadModifiedSharedFunctionInDevMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static void addExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
MethodKit.addExtensionMethod(targetClass, objectOfExtensionClass);
|
||||
}
|
||||
|
||||
public static void addExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
MethodKit.addExtensionMethod(targetClass, extensionClass);
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);;
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
MethodKit.removeExtensionMethod(targetClass, extensionClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
363
src/main/java/com/jfinal/template/EngineConfig.java
Normal file
363
src/main/java/com/jfinal/template/EngineConfig.java
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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.source.FileSource;
|
||||
import com.jfinal.template.source.FileSourceFactory;
|
||||
import com.jfinal.template.source.ISource;
|
||||
import com.jfinal.template.source.ISourceFactory;
|
||||
import com.jfinal.template.source.StringSource;
|
||||
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
|
||||
*/
|
||||
public class EngineConfig {
|
||||
|
||||
public static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
private Map<String, Define> sharedFunctionMap = new HashMap<String, Define>();
|
||||
private List<ISource> sharedFunctionSourceList = new ArrayList<ISource>(); // for devMode only
|
||||
|
||||
Map<String, Object> sharedObjectMap = null;
|
||||
|
||||
private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me;
|
||||
private ISourceFactory sourceFactory = new FileSourceFactory();
|
||||
private Map<String, Stat> directiveMap = new HashMap<String, Stat>();
|
||||
private SharedMethodKit sharedMethodKit = new SharedMethodKit();
|
||||
|
||||
private boolean devMode = false;
|
||||
private boolean reloadModifiedSharedFunctionInDevMode = true;
|
||||
private String baseTemplatePath = null;
|
||||
private String encoding = DEFAULT_ENCODING;
|
||||
private String datePattern = "yyyy-MM-dd HH:mm";
|
||||
|
||||
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());
|
||||
|
||||
// Add official shared method of Template Engine
|
||||
// addSharedMethod(new Json());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function with file
|
||||
*/
|
||||
public void addSharedFunction(String fileName) {
|
||||
// FileSource fileSource = new FileSource(baseTemplatePath, fileName, encoding);
|
||||
ISource source = sourceFactory.getSource(baseTemplatePath, fileName, encoding);
|
||||
doAddSharedFunction(source, fileName);
|
||||
}
|
||||
|
||||
private synchronized void doAddSharedFunction(ISource source, String fileName) {
|
||||
Env env = new Env(this);
|
||||
new Parser(env, source.getContent(), fileName).parse();
|
||||
addToSharedFunctionMap(sharedFunctionMap, env);
|
||||
if (devMode) {
|
||||
sharedFunctionSourceList.add(source);
|
||||
env.addSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function with files
|
||||
*/
|
||||
public void addSharedFunction(String... fileNames) {
|
||||
for (String fileName : fileNames) {
|
||||
addSharedFunction(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function by string content
|
||||
*/
|
||||
public void addSharedFunctionByString(String content) {
|
||||
// content 中的内容被解析后会存放在 Env 之中,而 StringSource 所对应的
|
||||
// Template 对象 isModified() 始终返回 false,所以没有必要对其缓存
|
||||
StringSource stringSource = new StringSource(content, false);
|
||||
doAddSharedFunction(stringSource, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared function by ISource
|
||||
*/
|
||||
public void addSharedFunction(ISource source) {
|
||||
String fileName = source instanceof FileSource ? ((FileSource)source).getFileName() : null;
|
||||
doAddSharedFunction(source, fileName);
|
||||
}
|
||||
|
||||
private void addToSharedFunctionMap(Map<String, Define> sharedFunctionMap, Env env) {
|
||||
Map<String, Define> funcMap = env.getFunctionMap();
|
||||
for (Entry<String, Define> e : funcMap.entrySet()) {
|
||||
if (sharedFunctionMap.containsKey(e.getKey())) {
|
||||
throw new IllegalArgumentException("Template function already exists : " + e.getKey());
|
||||
}
|
||||
Define func = e.getValue();
|
||||
if (devMode) {
|
||||
func.setEnvForDevMode(env);
|
||||
}
|
||||
sharedFunctionMap.put(e.getKey(), func);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shared function by Env
|
||||
*/
|
||||
Define getSharedFunction(String functionName) {
|
||||
Define func = sharedFunctionMap.get(functionName);
|
||||
if (func == null) {
|
||||
/**
|
||||
* 如果 func 最初未定义,但后续在共享模板文件中又被添加进来
|
||||
* 此时在本 if 分支中无法被感知,仍然返回了 null
|
||||
*
|
||||
* 但共享模板文件会在后续其它的 func 调用时被感知修改并 reload
|
||||
* 所以本 if 分支不考虑处理模板文件中追加 #define 的情况
|
||||
*
|
||||
* 如果要处理,只能是每次在 func 为 null 时,判断 sharedFunctionSourceList
|
||||
* 中的模板是否被修改过,再重新加载,不优雅
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
if (devMode && reloadModifiedSharedFunctionInDevMode) {
|
||||
if (func.isSourceModifiedForDevMode()) {
|
||||
synchronized (this) {
|
||||
func = sharedFunctionMap.get(functionName);
|
||||
if (func.isSourceModifiedForDevMode()) {
|
||||
reloadSharedFunctionSourceList();
|
||||
func = sharedFunctionMap.get(functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload shared function source list
|
||||
*
|
||||
* devMode 要照顾到 sharedFunctionFiles,所以暂不提供
|
||||
* removeSharedFunction(String functionName) 功能
|
||||
* 开发者可直接使用模板注释功能将不需要的 function 直接注释掉
|
||||
*/
|
||||
private synchronized void reloadSharedFunctionSourceList() {
|
||||
Map<String, Define> newMap = new HashMap<String, Define>();
|
||||
for (int i = 0, size = sharedFunctionSourceList.size(); i < size; i++) {
|
||||
ISource source = sharedFunctionSourceList.get(i);
|
||||
String fileName = source instanceof FileSource ? ((FileSource)source).getFileName() : null;
|
||||
|
||||
Env env = new Env(this);
|
||||
new Parser(env, source.getContent(), fileName).parse();
|
||||
addToSharedFunctionMap(newMap, env);
|
||||
if (devMode) {
|
||||
env.addSource(source);
|
||||
}
|
||||
}
|
||||
this.sharedFunctionMap = newMap;
|
||||
}
|
||||
|
||||
public synchronized void addSharedObject(String name, Object object) {
|
||||
if (sharedObjectMap == null) {
|
||||
sharedObjectMap = new HashMap<String, Object>();
|
||||
} else if (sharedObjectMap.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Shared object already exists: " + name);
|
||||
}
|
||||
sharedObjectMap.put(name, object);
|
||||
}
|
||||
|
||||
Map<String, Object> getSharedObjectMap() {
|
||||
return sharedObjectMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set output directive factory
|
||||
*/
|
||||
public void setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) {
|
||||
if (outputDirectiveFactory == null) {
|
||||
throw new IllegalArgumentException("outputDirectiveFactory can not be null");
|
||||
}
|
||||
this.outputDirectiveFactory = outputDirectiveFactory;
|
||||
}
|
||||
|
||||
public Output getOutputDirective(ExprList exprList, Location location) {
|
||||
return outputDirectiveFactory.getOutputDirective(exprList, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by Engine only
|
||||
*/
|
||||
void setDevMode(boolean devMode) {
|
||||
this.devMode = devMode;
|
||||
}
|
||||
|
||||
public boolean isDevMode() {
|
||||
return devMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by Engine only
|
||||
*/
|
||||
void setSourceFactory(ISourceFactory sourceFactory) {
|
||||
if (sourceFactory == null) {
|
||||
throw new IllegalArgumentException("sourceFactory can not be null");
|
||||
}
|
||||
this.sourceFactory = sourceFactory;
|
||||
}
|
||||
|
||||
public ISourceFactory getSourceFactory() {
|
||||
return sourceFactory;
|
||||
}
|
||||
|
||||
public void setBaseTemplatePath(String baseTemplatePath) {
|
||||
// 使用 ClassPathSourceFactory 时,允许 baseTemplatePath 为 null 值
|
||||
if (baseTemplatePath == null) {
|
||||
this.baseTemplatePath = null;
|
||||
return ;
|
||||
}
|
||||
if (StrKit.isBlank(baseTemplatePath)) {
|
||||
throw new IllegalArgumentException("baseTemplatePath can not be blank");
|
||||
}
|
||||
baseTemplatePath = baseTemplatePath.trim();
|
||||
if (baseTemplatePath.length() > 1) {
|
||||
if (baseTemplatePath.endsWith("/") || baseTemplatePath.endsWith("\\")) {
|
||||
baseTemplatePath = baseTemplatePath.substring(0, baseTemplatePath.length() - 1);
|
||||
}
|
||||
}
|
||||
this.baseTemplatePath = baseTemplatePath;
|
||||
}
|
||||
|
||||
public String getBaseTemplatePath() {
|
||||
return baseTemplatePath;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding) {
|
||||
if (StrKit.isBlank(encoding)) {
|
||||
throw new IllegalArgumentException("encoding can not be blank");
|
||||
}
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public void setDatePattern(String datePattern) {
|
||||
if (StrKit.isBlank(datePattern)) {
|
||||
throw new IllegalArgumentException("datePattern can not be blank");
|
||||
}
|
||||
this.datePattern = datePattern;
|
||||
}
|
||||
|
||||
public String getDatePattern() {
|
||||
return datePattern;
|
||||
}
|
||||
|
||||
public void setReloadModifiedSharedFunctionInDevMode(boolean reloadModifiedSharedFunctionInDevMode) {
|
||||
this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode;
|
||||
}
|
||||
|
||||
public synchronized void addDirective(String directiveName, Directive directive) {
|
||||
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 (directiveMap.containsKey(directiveName)) {
|
||||
throw new IllegalArgumentException("directive already exists : " + directiveName);
|
||||
}
|
||||
directiveMap.put(directiveName, directive);
|
||||
}
|
||||
|
||||
public Stat getDirective(String directiveName) {
|
||||
return directiveMap.get(directiveName);
|
||||
}
|
||||
|
||||
public void removeDirective(String directiveName) {
|
||||
directiveMap.remove(directiveName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared method from object
|
||||
*/
|
||||
public void addSharedMethod(Object sharedMethodFromObject) {
|
||||
sharedMethodKit.addSharedMethod(sharedMethodFromObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared method from class
|
||||
*/
|
||||
public void addSharedMethod(Class<?> sharedMethodFromClass) {
|
||||
sharedMethodKit.addSharedMethod(sharedMethodFromClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shared static method of Class
|
||||
*/
|
||||
public void addSharedStaticMethod(Class<?> sharedStaticMethodFromClass) {
|
||||
sharedMethodKit.addSharedStaticMethod(sharedStaticMethodFromClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method with method name
|
||||
*/
|
||||
public void removeSharedMethod(String methodName) {
|
||||
sharedMethodKit.removeSharedMethod(methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method of the Class
|
||||
*/
|
||||
public void removeSharedMethod(Class<?> sharedClass) {
|
||||
sharedMethodKit.removeSharedMethod(sharedClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove shared Method
|
||||
*/
|
||||
public void removeSharedMethod(Method method) {
|
||||
sharedMethodKit.removeSharedMethod(method);
|
||||
}
|
||||
|
||||
public SharedMethodKit getSharedMethodKit() {
|
||||
return sharedMethodKit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
123
src/main/java/com/jfinal/template/Env.java
Normal file
123
src/main/java/com/jfinal/template/Env.java
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.source.ISource;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.ast.Define;
|
||||
|
||||
/**
|
||||
* Env
|
||||
*
|
||||
* 1:解析时存放 #define 定义的模板函数
|
||||
* 2:运行时提供 #define 定义的模板函数
|
||||
* 3:每个 Template 对象持有一个 Env 对象
|
||||
*/
|
||||
public class Env {
|
||||
|
||||
protected EngineConfig engineConfig;
|
||||
protected Map<String, Define> functionMap = new HashMap<String, Define>();
|
||||
|
||||
// 代替 Template 持有该属性,便于在 #include 指令中调用 Env.addSource()
|
||||
protected List<ISource> sourceList = null;
|
||||
|
||||
public Env(EngineConfig engineConfig) {
|
||||
this.engineConfig = engineConfig;
|
||||
}
|
||||
|
||||
public EngineConfig getEngineConfig() {
|
||||
return engineConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template function
|
||||
*/
|
||||
public void addFunction(Define function) {
|
||||
String fn = function.getFunctionName();
|
||||
if (functionMap.containsKey(fn)) {
|
||||
Define previous = functionMap.get(fn);
|
||||
throw new ParseException(
|
||||
"Template function \"" + fn + "\" already defined in " +
|
||||
getAlreadyDefinedLocation(previous.getLocation()),
|
||||
function.getLocation()
|
||||
);
|
||||
}
|
||||
functionMap.put(fn, function);
|
||||
}
|
||||
|
||||
private String getAlreadyDefinedLocation(Location loc) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (loc.getTemplateFile() != null) {
|
||||
buf.append(loc.getTemplateFile()).append(", line ").append(loc.getRow());
|
||||
} else {
|
||||
buf.append("string template line ").append(loc.getRow());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get function of current template first, getting shared function if null before
|
||||
*/
|
||||
public Define getFunction(String functionName) {
|
||||
Define func = functionMap.get(functionName);
|
||||
return func != null ? func : engineConfig.getSharedFunction(functionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* For EngineConfig.addSharedFunction(...) only
|
||||
*/
|
||||
Map<String, Define> getFunctionMap() {
|
||||
return functionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本方法用于在 devMode 之下,判断当前 Template 以及其下 #include 指令
|
||||
* 所涉及的所有 ISource 对象是否被修改,以便于在 devMode 下重新加载
|
||||
*
|
||||
* sourceList 属性用于存放主模板以及 #include 进来的模板所对应的
|
||||
* ISource 对象
|
||||
*/
|
||||
public boolean isSourceListModified() {
|
||||
if (sourceList != null) {
|
||||
for (int i = 0, size = sourceList.size(); i < size; i++) {
|
||||
if (sourceList.get(i).isModified()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加本 Template 的 ISource,以及该 Template 使用 include 包含进来的所有 ISource
|
||||
* 以便于在 devMode 之下判断该 Template 是否被 modified,进而 reload 该 Template
|
||||
*/
|
||||
public void addSource(ISource source) {
|
||||
if (sourceList == null) {
|
||||
sourceList = new ArrayList<ISource>();
|
||||
}
|
||||
sourceList.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
105
src/main/java/com/jfinal/template/FastStringWriter.java
Normal file
105
src/main/java/com/jfinal/template/FastStringWriter.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* FastStringWriter
|
||||
*
|
||||
* 由 JDK 中 StringWriter 改造而成,将 StringBuffer 属性替换为
|
||||
* StringBuilder,避免 StringBuffer 的 synchronized 操作
|
||||
*/
|
||||
public class FastStringWriter extends Writer {
|
||||
|
||||
private StringBuilder buf;
|
||||
|
||||
public FastStringWriter() {
|
||||
buf = new StringBuilder();
|
||||
}
|
||||
|
||||
public FastStringWriter(int initialSize) {
|
||||
if (initialSize < 0) {
|
||||
throw new IllegalArgumentException("Negative buffer size");
|
||||
}
|
||||
buf = new StringBuilder(initialSize);
|
||||
}
|
||||
|
||||
public void write(int c) {
|
||||
buf.append((char) c);
|
||||
}
|
||||
|
||||
public void write(char cbuf[], int off, int len) {
|
||||
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
|
||||
((off + len) > cbuf.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
buf.append(cbuf, off, len);
|
||||
}
|
||||
|
||||
public void write(String str) {
|
||||
buf.append(str);
|
||||
}
|
||||
|
||||
public void write(String str, int off, int len) {
|
||||
buf.append(str.substring(off, off + len));
|
||||
}
|
||||
|
||||
public FastStringWriter append(CharSequence csq) {
|
||||
if (csq == null) {
|
||||
write("null");
|
||||
} else {
|
||||
write(csq.toString());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public FastStringWriter append(CharSequence csq, int start, int end) {
|
||||
CharSequence cs = (csq == null ? "null" : csq);
|
||||
write(cs.subSequence(start, end).toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public FastStringWriter append(char c) {
|
||||
write(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public StringBuilder getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ast.Output;
|
||||
|
||||
/**
|
||||
* IOutputDirectiveFactory
|
||||
* 用于定制自定义输出指令,替换系统默认输出指令,满足个性化需求
|
||||
*
|
||||
* 用法:
|
||||
* 1:定义 MyOutput
|
||||
* public class MyOutput extends Output {
|
||||
* public MyOutput(ExprList exprList) {
|
||||
* super(exprList);
|
||||
* }
|
||||
*
|
||||
* public void exec(Env env, Scope scope, Writer writer) {
|
||||
* write(writer, exprList.eval(scope));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 2:定义 MyOutputDirectiveFactory
|
||||
* public class MyOutputDirectiveFactory implements IOutputDirectiveFactory {
|
||||
* public Output getOutputDirective(ExprList exprList) {
|
||||
* return new MyOutput(exprList);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 3:配置
|
||||
* engine.setOutputDirectiveFactory(new MyOutputDirectiveFactory())
|
||||
*/
|
||||
public interface IOutputDirectiveFactory {
|
||||
|
||||
public Output getOutputDirective(ExprList exprList, Location location);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ast.Output;
|
||||
|
||||
/**
|
||||
* OutputDirectiveFactory
|
||||
*/
|
||||
public class OutputDirectiveFactory implements IOutputDirectiveFactory {
|
||||
|
||||
public static final OutputDirectiveFactory me = new OutputDirectiveFactory();
|
||||
|
||||
public Output getOutputDirective(ExprList exprList, Location location) {
|
||||
return new Output(exprList, location);
|
||||
}
|
||||
}
|
||||
|
92
src/main/java/com/jfinal/template/Template.java
Normal file
92
src/main/java/com/jfinal/template/Template.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
|
||||
/**
|
||||
* Template
|
||||
*
|
||||
* 用法:
|
||||
* Template template = Engine.use().getTemplate(...);
|
||||
* template.render(data, writer);
|
||||
* template.renderToString(data);
|
||||
*/
|
||||
public class Template {
|
||||
|
||||
private Env env;
|
||||
private Stat ast;
|
||||
|
||||
public Template(Env env, Stat ast) {
|
||||
if (env == null || ast == null) {
|
||||
throw new IllegalArgumentException("env and ast can not be null");
|
||||
}
|
||||
this.env = env;
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 Writer 中去
|
||||
*/
|
||||
public void render(Map<?, ?> data, Writer writer) {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持无 data 参数,渲染到 Writer 中去 <br>
|
||||
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景<br>
|
||||
* 此外,其它所有 render 方法也支持传入 null 值 data 参数
|
||||
*/
|
||||
public void render(Writer writer) {
|
||||
ast.exec(env, new Scope(null, env.engineConfig.sharedObjectMap), writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 FastStringWriter 中去
|
||||
*/
|
||||
public void render(Map<?, ?> data, FastStringWriter fastStringWriter) {
|
||||
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), fastStringWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 StringBuilder 中去
|
||||
*/
|
||||
public StringBuilder renderToStringBuilder(Map<?, ?> data) {
|
||||
FastStringWriter fsw = new FastStringWriter();
|
||||
render(data, fsw);
|
||||
return fsw.getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染到 String 中去
|
||||
*/
|
||||
public String renderToString(Map<?, ?> data) {
|
||||
return renderToStringBuilder(data).toString();
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return env.isSourceListModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
36
src/main/java/com/jfinal/template/TemplateException.java
Normal file
36
src/main/java/com/jfinal/template/TemplateException.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template;
|
||||
|
||||
import com.jfinal.template.stat.Location;
|
||||
|
||||
/**
|
||||
* Template runtime exception
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TemplateException extends RuntimeException {
|
||||
|
||||
public TemplateException(String msg, Location loc) {
|
||||
super(loc != null ? msg + loc : msg);
|
||||
}
|
||||
|
||||
public TemplateException(String msg, Location loc, Throwable t) {
|
||||
super(loc != null ? msg + loc : msg, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
520
src/main/java/com/jfinal/template/expr/ExprLexer.java
Normal file
520
src/main/java/com/jfinal/template/expr/ExprLexer.java
Normal file
@@ -0,0 +1,520 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import com.jfinal.kit.JavaKeyword;
|
||||
import com.jfinal.template.stat.CharTable;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParaToken;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
|
||||
/**
|
||||
* ExprLexer
|
||||
*/
|
||||
class ExprLexer {
|
||||
|
||||
static final char EOF = (char)-1;
|
||||
static final JavaKeyword javaKeyword = new JavaKeyword();
|
||||
static final Pattern DOUBLE_QUOTES_PATTERN = Pattern.compile("\\\\\"");
|
||||
static final Pattern SINGLE_QUOTES_PATTERN = Pattern.compile("\\\\'");
|
||||
|
||||
char[] buf;
|
||||
int state = 0;
|
||||
int lexemeBegin = 0;
|
||||
int forward = 0;
|
||||
int beginRow = 1;
|
||||
int forwardRow = 1;
|
||||
List<Tok> tokens = new ArrayList<Tok>();
|
||||
Location location;
|
||||
|
||||
public ExprLexer(ParaToken paraToken, Location location) {
|
||||
this.location = location;
|
||||
StringBuilder content = paraToken.getContent();
|
||||
beginRow = paraToken.getRow();
|
||||
forwardRow = beginRow;
|
||||
if (content == null) {
|
||||
buf = new char[]{EOF};
|
||||
return ;
|
||||
}
|
||||
int len = content.length();
|
||||
buf = new char[len + 1];
|
||||
content.getChars(0, content.length(), buf, 0);
|
||||
buf[len] = EOF;
|
||||
}
|
||||
|
||||
public List<Tok> scan() {
|
||||
while (peek() != EOF) {
|
||||
skipBlanks();
|
||||
lexemeBegin = forward;
|
||||
beginRow = forwardRow;
|
||||
if (scanId()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanOperator()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanString()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanNumber()) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
if (peek() != EOF) {
|
||||
throw new ParseException("Expression not support the char: '" + peek() + "'", location);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描 ID true false null
|
||||
*/
|
||||
boolean scanId() {
|
||||
if (state != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CharTable.isLetter(peek())) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
while (CharTable.isLetterOrDigit(next())) {
|
||||
;
|
||||
}
|
||||
String id = subBuf(lexemeBegin, forward - 1).toString();
|
||||
if ("true".equals(id)) {
|
||||
addToken(new Tok(Sym.TRUE, id, beginRow));
|
||||
} else if ("false".equals(id)) {
|
||||
addToken(new Tok(Sym.FALSE, id, beginRow));
|
||||
} else if ("null".equals(id)) {
|
||||
addToken(new Tok(Sym.NULL, id, beginRow));
|
||||
} else if (CharTable.isBlankOrLineFeed(peek()) && javaKeyword.contains(id)) {
|
||||
throw new ParseException("Identifier can not be java keyword : " + id, location);
|
||||
} else {
|
||||
addToken(new Tok(Sym.ID, id, beginRow));
|
||||
}
|
||||
return prepareNextScan();
|
||||
}
|
||||
|
||||
/**
|
||||
* + - * / % ++ --
|
||||
* = == != < <= > >=
|
||||
* ! && ||
|
||||
* ? ?: ?!
|
||||
* . .. : :: , ;
|
||||
* ( ) [ ] { }
|
||||
*/
|
||||
boolean scanOperator() {
|
||||
if (state != 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Tok tok;
|
||||
switch (peek()) {
|
||||
case '+': // + - * / % ++ --
|
||||
if (next() == '+') {
|
||||
tok = new Tok(Sym.INC, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.ADD, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '-':
|
||||
if (next() == '-') {
|
||||
tok = new Tok(Sym.DEC, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.SUB, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '*':
|
||||
tok = new Tok(Sym.MUL, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '/':
|
||||
tok = new Tok(Sym.DIV, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '%':
|
||||
tok = new Tok(Sym.MOD, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '=': // = == != < <= > >=
|
||||
if (next() == '=') {
|
||||
tok = new Tok(Sym.EQUAL, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.ASSIGN, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '!':
|
||||
if (next() == '=') {
|
||||
tok = new Tok(Sym.NOTEQUAL, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.NOT, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '<':
|
||||
if (next() == '=') {
|
||||
tok = new Tok(Sym.LE, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.LT, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '>':
|
||||
if (next() == '=') {
|
||||
tok = new Tok(Sym.GE, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.GT, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '&': // ! && ||
|
||||
if (next() == '&') {
|
||||
tok = new Tok(Sym.AND, beginRow);
|
||||
next();
|
||||
} else {
|
||||
throw new ParseException("Unsupported operator: '&'", location);
|
||||
}
|
||||
return ok(tok);
|
||||
case '|':
|
||||
if (next() == '|') {
|
||||
tok = new Tok(Sym.OR, beginRow);
|
||||
next();
|
||||
} else {
|
||||
throw new ParseException("Unsupported operator: '|'", location);
|
||||
}
|
||||
return ok(tok);
|
||||
case '?': // ? ??
|
||||
if (next() == '?') {
|
||||
tok = new Tok(Sym.NULL_SAFE, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.QUESTION, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case '.': // . .. : :: , ;
|
||||
if (next() == '.') {
|
||||
tok = new Tok(Sym.RANGE, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.DOT, ".", beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case ':':
|
||||
if (next() == ':') {
|
||||
tok = new Tok(Sym.STATIC, beginRow);
|
||||
next();
|
||||
} else {
|
||||
tok = new Tok(Sym.COLON, beginRow);
|
||||
}
|
||||
return ok(tok);
|
||||
case ',':
|
||||
tok = new Tok(Sym.COMMA, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case ';':
|
||||
tok = new Tok(Sym.SEMICOLON, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '(': // ( ) [ ] { }
|
||||
tok = new Tok(Sym.LPAREN, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case ')':
|
||||
tok = new Tok(Sym.RPAREN, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '[':
|
||||
tok = new Tok(Sym.LBRACK, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case ']':
|
||||
tok = new Tok(Sym.RBRACK, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '{':
|
||||
tok = new Tok(Sym.LBRACE, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
case '}':
|
||||
tok = new Tok(Sym.RBRACE, beginRow);
|
||||
next();
|
||||
return ok(tok);
|
||||
default :
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
|
||||
boolean ok(Tok tok) {
|
||||
tokens.add(tok);
|
||||
return prepareNextScan();
|
||||
}
|
||||
|
||||
boolean scanString() {
|
||||
if (state != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char quotes = peek();
|
||||
if (quotes != '"' && quotes != '\'') {
|
||||
return fail();
|
||||
}
|
||||
|
||||
for (char c=next(); true; c=next()) {
|
||||
if (c == quotes) {
|
||||
if (buf[forward - 1] != '\\') { // 前一个字符不是转义字符
|
||||
StringBuilder sb = subBuf(lexemeBegin + 1, forward -1);
|
||||
String str;
|
||||
if (sb != null) {
|
||||
if (quotes == '"') {
|
||||
str = DOUBLE_QUOTES_PATTERN.matcher(sb).replaceAll("\"");
|
||||
} else {
|
||||
str = SINGLE_QUOTES_PATTERN.matcher(sb).replaceAll("'");
|
||||
}
|
||||
} else {
|
||||
str = "";
|
||||
}
|
||||
|
||||
Tok tok = new Tok(Sym.STR, str, beginRow);
|
||||
addToken(tok);
|
||||
next();
|
||||
return prepareNextScan();
|
||||
} else {
|
||||
continue ;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == EOF) {
|
||||
throw new ParseException("Expression error, the string not ending", location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean scanNumber() {
|
||||
if (state != 300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c = peek();
|
||||
if (!CharTable.isDigit(c)) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
int numStart = lexemeBegin; // forward;
|
||||
int radix = 10; // 10 进制
|
||||
if (c == '0') {
|
||||
c = next();
|
||||
if (c == 'X' || c == 'x') {
|
||||
radix = 16; // 16 进制
|
||||
c = next();
|
||||
numStart = numStart + 2;
|
||||
} else {
|
||||
radix = 8; // 8 进制
|
||||
// numStart = numStart + 1; // 8 进制不用去掉前缀 0,可被正确转换,去除此行便于正确处理数字 0
|
||||
}
|
||||
}
|
||||
|
||||
c = skipDigit(radix);
|
||||
Sym sym = null;
|
||||
if (c == '.') { // 以 '.' 字符结尾是合法的浮点数
|
||||
next();
|
||||
if (peek() == '.' || // 处理 [0..9] 这样的表达式
|
||||
CharTable.isLetter(peek())) { // 处理 123.toInt() 这样的表达式,1.2.toInt() 及 1D.toInt() 可正常处理
|
||||
StringBuilder n = subBuf(numStart, forward - 2);
|
||||
if (n == null /* && radix == 16 */) {
|
||||
// 16 进制数格式错误,前缀 0x 后缺少 16 进制数字(16 进制时 numStart 已增加了 2, n 为 null 必是 16 进制解析出错)
|
||||
throw new ParseException("Error hex format", location);
|
||||
}
|
||||
NumTok tok = new NumTok(Sym.INT, n.toString(), radix, false, location);
|
||||
addToken(tok);
|
||||
retract(1);
|
||||
return prepareNextScan();
|
||||
}
|
||||
|
||||
sym = Sym.DOUBLE; // 浮点型默认为 double
|
||||
c = skipDigit(radix);
|
||||
}
|
||||
|
||||
boolean isScientificNotation = false;
|
||||
if (c == 'E' || c == 'e') { // scientific notation 科学计数法
|
||||
c = next();
|
||||
if (c == '+' || c == '-') {
|
||||
c = next();
|
||||
}
|
||||
if (!CharTable.isDigit(c)) {
|
||||
// 科学计数法后面缺少数字
|
||||
throw new ParseException("Error scientific notation format", location);
|
||||
}
|
||||
isScientificNotation = true;
|
||||
sym = Sym.DOUBLE; // 科学计数法默认类型为 double
|
||||
|
||||
c = skipDecimalDigit(); // 科学计数法的指数部分是十进制
|
||||
}
|
||||
|
||||
StringBuilder num;
|
||||
if (c == 'L' || c == 'l') {
|
||||
if (sym == Sym.DOUBLE) {
|
||||
// 浮点类型不能使用 'L' 或 'l' 后缀
|
||||
throw new ParseException("Error float format", location);
|
||||
}
|
||||
sym = Sym.LONG;
|
||||
next();
|
||||
num = subBuf(numStart, forward - 2);
|
||||
} else if (c == 'F' || c == 'f') {
|
||||
sym = Sym.FLOAT;
|
||||
next();
|
||||
num = subBuf(numStart, forward - 2);
|
||||
} else if (c == 'D' || c == 'd') {
|
||||
sym = Sym.DOUBLE;
|
||||
next();
|
||||
num = subBuf(numStart, forward - 2);
|
||||
} else {
|
||||
if (sym == null) {
|
||||
sym = Sym.INT;
|
||||
}
|
||||
num = subBuf(numStart, forward - 1);
|
||||
}
|
||||
if (errorFollow()) {
|
||||
// "错误的表达式元素 : " + num + peek()
|
||||
throw new ParseException("Error expression: " + num + peek(), location);
|
||||
}
|
||||
if (num == null /* && radix == 16 */) {
|
||||
// 16 进制数格式错误,前缀 0x 后缺少 16 进制数字
|
||||
throw new ParseException("Error hex format", location);
|
||||
}
|
||||
|
||||
NumTok tok = new NumTok(sym, num.toString(), radix, isScientificNotation, location);
|
||||
addToken(tok);
|
||||
return prepareNextScan();
|
||||
}
|
||||
|
||||
boolean errorFollow() {
|
||||
char c = peek();
|
||||
return CharTable.isLetterOrDigit(c) || c == '"' || c == '\'';
|
||||
}
|
||||
|
||||
char skipDigit(int radix) {
|
||||
if (radix == 10) {
|
||||
return skipDecimalDigit();
|
||||
} else if (radix == 16) {
|
||||
return skipHexadecimalDigit();
|
||||
} else {
|
||||
return skipOctalDigit();
|
||||
}
|
||||
}
|
||||
|
||||
char skipDecimalDigit() {
|
||||
char c = peek();
|
||||
for (; CharTable.isDigit(c);) {
|
||||
c = next();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
char skipHexadecimalDigit() {
|
||||
char c = peek();
|
||||
for (; CharTable.isHexadecimalDigit(c);) {
|
||||
c = next();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
char skipOctalDigit() {
|
||||
char c = peek();
|
||||
for (; CharTable.isOctalDigit(c);) {
|
||||
c = next();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
boolean fail() {
|
||||
forward = lexemeBegin;
|
||||
forwardRow = beginRow;
|
||||
|
||||
if (state < 100) {
|
||||
state = 100;
|
||||
} else if (state < 200) {
|
||||
state = 200;
|
||||
} else if (state < 300) {
|
||||
state = 300;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char next() {
|
||||
if (buf[forward] == '\n') {
|
||||
forwardRow++;
|
||||
}
|
||||
return buf[++forward];
|
||||
}
|
||||
|
||||
char peek() {
|
||||
return buf[forward];
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式词法分析需要跳过换行与回车
|
||||
*/
|
||||
void skipBlanks() {
|
||||
while(CharTable.isBlankOrLineFeed(buf[forward])) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder subBuf(int start, int end) {
|
||||
if (start > end) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder ret = new StringBuilder(end - start + 1);
|
||||
for (int i=start; i<=end; i++) {
|
||||
ret.append(buf[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
boolean prepareNextScan() {
|
||||
state = 0;
|
||||
lexemeBegin = forward;
|
||||
beginRow = forwardRow;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addToken(Tok tok) {
|
||||
tokens.add(tok);
|
||||
}
|
||||
|
||||
void retract(int n) {
|
||||
for (int i=0; i<n; i++) {
|
||||
forward--;
|
||||
if (buf[forward] == '\n') {
|
||||
forwardRow--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
588
src/main/java/com/jfinal/template/expr/ExprParser.java
Normal file
588
src/main/java/com/jfinal/template/expr/ExprParser.java
Normal file
@@ -0,0 +1,588 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.expr.ast.Arith;
|
||||
import com.jfinal.template.expr.ast.Array;
|
||||
import com.jfinal.template.expr.ast.Assign;
|
||||
import com.jfinal.template.expr.ast.Compare;
|
||||
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.expr.ast.Field;
|
||||
import com.jfinal.template.expr.ast.ForCtrl;
|
||||
import com.jfinal.template.expr.ast.Id;
|
||||
import com.jfinal.template.expr.ast.IncDec;
|
||||
import com.jfinal.template.expr.ast.Index;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.expr.ast.Map;
|
||||
import com.jfinal.template.expr.ast.Method;
|
||||
import com.jfinal.template.expr.ast.NullSafe;
|
||||
import com.jfinal.template.expr.ast.RangeArray;
|
||||
import com.jfinal.template.expr.ast.SharedMethod;
|
||||
import com.jfinal.template.expr.ast.StaticField;
|
||||
import com.jfinal.template.expr.ast.StaticMethod;
|
||||
import com.jfinal.template.expr.ast.Ternary;
|
||||
import com.jfinal.template.expr.ast.Unary;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParaToken;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
|
||||
/**
|
||||
* ExprParser
|
||||
*/
|
||||
public class ExprParser {
|
||||
|
||||
static final Tok EOF = new Tok(Sym.EOF, -1);
|
||||
|
||||
Tok peek = null;
|
||||
int forward = 0;
|
||||
List<Tok> tokenList;
|
||||
Location location;
|
||||
|
||||
ParaToken paraToken;
|
||||
EngineConfig engineConfig;
|
||||
|
||||
public ExprParser(ParaToken paraToken, EngineConfig engineConfig, String fileName) {
|
||||
this.paraToken = paraToken;
|
||||
this.engineConfig = engineConfig;
|
||||
this.location = new Location(fileName, paraToken.getRow());
|
||||
}
|
||||
|
||||
void initPeek() {
|
||||
peek = tokenList.get(forward);
|
||||
}
|
||||
|
||||
Tok peek() {
|
||||
return peek;
|
||||
}
|
||||
|
||||
Tok move() {
|
||||
peek = tokenList.get(++forward);
|
||||
return peek;
|
||||
}
|
||||
void resetForward(int position) {
|
||||
forward = position;
|
||||
peek = tokenList.get(forward);
|
||||
}
|
||||
|
||||
Tok match(Sym sym) {
|
||||
Tok current = peek();
|
||||
if (current.sym == sym) {
|
||||
move();
|
||||
return current;
|
||||
}
|
||||
throw new ParseException("Expression error: can not match the symbol \"" + sym.value() + "\"", location);
|
||||
}
|
||||
|
||||
public ExprList parseExprList() {
|
||||
return (ExprList)parse(true);
|
||||
}
|
||||
|
||||
public ForCtrl parseForCtrl() {
|
||||
Expr forCtrl = parse(false);
|
||||
if (forCtrl instanceof ForCtrl) {
|
||||
return (ForCtrl)forCtrl;
|
||||
} else {
|
||||
throw new ParseException("The expression of #for directive is error", location);
|
||||
}
|
||||
}
|
||||
|
||||
Expr parse(boolean isExprList) {
|
||||
tokenList = new ExprLexer(paraToken, location).scan();
|
||||
if (tokenList.size() == 0) {
|
||||
return ExprList.NULL_EXPR_LIST;
|
||||
}
|
||||
tokenList.add(EOF);
|
||||
initPeek();
|
||||
Expr expr = isExprList ? exprList() : forCtrl();
|
||||
if (peek() != EOF) {
|
||||
throw new ParseException("Expression error: can not match \"" + peek().value() + "\"", location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* exprList : expr (',' expr)*
|
||||
*/
|
||||
Expr exprList() {
|
||||
List<Expr> exprList = new ArrayList<Expr>();
|
||||
while (true) {
|
||||
Expr stat = expr();
|
||||
if (stat != null) {
|
||||
exprList.add(stat);
|
||||
if (peek().sym == Sym.COMMA) {
|
||||
move();
|
||||
if (peek() == EOF) {
|
||||
throw new ParseException("Expression error: can not match the char of comma ','", location);
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
}
|
||||
break ;
|
||||
}
|
||||
return new ExprList(exprList);
|
||||
}
|
||||
|
||||
Expr expr() {
|
||||
return assign();
|
||||
}
|
||||
|
||||
/**
|
||||
* assign : <assoc=right> ID ( '[' expr ']' )? '=' expr
|
||||
*/
|
||||
Expr assign() {
|
||||
Tok idTok = peek();
|
||||
if (idTok.sym != Sym.ID) {
|
||||
return ternary();
|
||||
}
|
||||
|
||||
int begin = forward;
|
||||
// ID = expr
|
||||
if (move().sym == Sym.ASSIGN) {
|
||||
move();
|
||||
return new Assign(idTok.value(), expr(), location);
|
||||
}
|
||||
|
||||
// array、map 赋值:ID [ expr ] = expr
|
||||
if (peek().sym == Sym.LBRACK) {
|
||||
move();
|
||||
Expr index = expr();
|
||||
match(Sym.RBRACK);
|
||||
if (peek().sym == Sym.ASSIGN) {
|
||||
move();
|
||||
return new Assign(idTok.value(), index, expr(), location); // 右结合无限连
|
||||
}
|
||||
}
|
||||
|
||||
resetForward(begin);
|
||||
return ternary();
|
||||
}
|
||||
|
||||
/**
|
||||
* ternary : expr '?' expr ':' expr
|
||||
*/
|
||||
Expr ternary() {
|
||||
Expr cond = or();
|
||||
if (peek().sym == Sym.QUESTION) {
|
||||
move();
|
||||
Expr exprOne = expr();
|
||||
match(Sym.COLON);
|
||||
return new Ternary(cond, exprOne, expr(), location);
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
|
||||
/**
|
||||
* or : expr '||' expr
|
||||
*/
|
||||
Expr or() {
|
||||
Expr expr = and();
|
||||
for (Tok tok=peek(); tok.sym==Sym.OR; tok=peek()) {
|
||||
move();
|
||||
expr = new Logic(Sym.OR, expr, and(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* and : expr '&&' expr
|
||||
*/
|
||||
Expr and() {
|
||||
Expr expr = equalNotEqual();
|
||||
for (Tok tok=peek(); tok.sym==Sym.AND; tok=peek()) {
|
||||
move();
|
||||
expr = new Logic(Sym.AND, expr, equalNotEqual(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* equalNotEqual : expr ('==' | '!=') expr
|
||||
*/
|
||||
Expr equalNotEqual() {
|
||||
Expr expr = greaterLess();
|
||||
for (Tok tok=peek(); tok.sym==Sym.EQUAL || tok.sym==Sym.NOTEQUAL; tok=peek()) {
|
||||
move();
|
||||
expr = new Compare(tok.sym, expr, greaterLess(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* compare expr ('<=' | '>=' | '>' | '<') expr
|
||||
* 不支持无限连: > >= < <=
|
||||
*/
|
||||
Expr greaterLess() {
|
||||
Expr expr = addSub();
|
||||
Tok tok = peek();
|
||||
if (tok.sym == Sym.LT || tok.sym == Sym.LE || tok.sym == Sym.GT || tok.sym == Sym.GE) {
|
||||
move();
|
||||
return new Compare(tok.sym, expr, addSub(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* addSub : expr ('+'|'-') expr
|
||||
*/
|
||||
Expr addSub() {
|
||||
Expr expr = mulDivMod();
|
||||
for (Tok tok=peek(); tok.sym==Sym.ADD || tok.sym==Sym.SUB; tok=peek()) {
|
||||
move();
|
||||
expr = new Arith(tok.sym, expr, mulDivMod(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* mulDivMod : expr ('*'|'/'|'%') expr
|
||||
*/
|
||||
Expr mulDivMod() {
|
||||
Expr expr = nullSafe();
|
||||
for (Tok tok=peek(); tok.sym==Sym.MUL || tok.sym==Sym.DIV || tok.sym==Sym.MOD; tok=peek()) {
|
||||
move();
|
||||
expr = new Arith(tok.sym, expr, nullSafe(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* nullSafe : expr '??' expr
|
||||
*/
|
||||
Expr nullSafe() {
|
||||
Expr expr = unary();
|
||||
for (Tok tok=peek(); tok.sym==Sym.NULL_SAFE; tok=peek()) {
|
||||
move();
|
||||
expr = new NullSafe(expr, unary(), location);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* unary : ('!' | '+' | '-'| '++' | '--') expr
|
||||
*/
|
||||
Expr unary() {
|
||||
Tok tok = peek();
|
||||
switch (tok.sym) {
|
||||
case NOT:
|
||||
move();
|
||||
return new Logic(tok.sym, unary(), location);
|
||||
case ADD:
|
||||
case SUB:
|
||||
move();
|
||||
return new Unary(tok.sym, unary(), location);
|
||||
case INC:
|
||||
case DEC:
|
||||
move();
|
||||
return new IncDec(tok.sym, false, incDec(), location);
|
||||
default:
|
||||
return incDec();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* incDec : expr ('++' | '--')
|
||||
*/
|
||||
Expr incDec() {
|
||||
Expr expr = staticMember();
|
||||
Tok tok = peek();
|
||||
if (tok.sym == Sym.INC || tok.sym == Sym.DEC) {
|
||||
move();
|
||||
return new IncDec(tok.sym, true, expr, location);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* staticMember
|
||||
* : ID_list '::' ID
|
||||
* | ID_list '::' ID '(' exprList? ')'
|
||||
*/
|
||||
Expr staticMember() {
|
||||
if (peek().sym != Sym.ID) {
|
||||
return sharedMethod();
|
||||
}
|
||||
|
||||
int begin = forward;
|
||||
while (move().sym == Sym.DOT && move().sym == Sym.ID) {
|
||||
;
|
||||
}
|
||||
// ID.ID.ID::
|
||||
if (peek().sym != Sym.STATIC || tokenList.get(forward - 1).sym != Sym.ID) {
|
||||
resetForward(begin);
|
||||
return sharedMethod();
|
||||
}
|
||||
|
||||
String clazz = getClazz(begin);
|
||||
match(Sym.STATIC);
|
||||
String memberName = match(Sym.ID).value();
|
||||
|
||||
// com.jfinal.kit.Str::isBlank(str)
|
||||
if (peek().sym == Sym.LPAREN) {
|
||||
move();
|
||||
if (peek().sym == Sym.RPAREN) {
|
||||
move();
|
||||
return new StaticMethod(clazz, memberName, location);
|
||||
}
|
||||
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
match(Sym.RPAREN);
|
||||
return new StaticMethod(clazz, memberName, exprList, location);
|
||||
}
|
||||
|
||||
// com.jfinal.core.Const::JFINAL_VERSION
|
||||
return new StaticField(clazz, memberName, location);
|
||||
}
|
||||
|
||||
String getClazz(int begin) {
|
||||
StringBuilder clazz = new StringBuilder();
|
||||
for (int i=begin; i<forward; i++) {
|
||||
clazz.append(tokenList.get(i).value());
|
||||
}
|
||||
return clazz.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* sharedMethod : ID '(' exprList? ')'
|
||||
*/
|
||||
Expr sharedMethod() {
|
||||
Tok tok = peek();
|
||||
if (tok.sym != Sym.ID) {
|
||||
return indexMethodField(null);
|
||||
}
|
||||
if (move().sym != Sym.LPAREN) {
|
||||
resetForward(forward - 1);
|
||||
return indexMethodField(null);
|
||||
}
|
||||
|
||||
move();
|
||||
if (peek().sym == Sym.RPAREN) {
|
||||
SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), ExprList.NULL_EXPR_LIST, location);
|
||||
move();
|
||||
return indexMethodField(sharedMethod);
|
||||
}
|
||||
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location);
|
||||
match(Sym.RPAREN);
|
||||
return indexMethodField(sharedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* index : expr '[' expr ']'
|
||||
* method : expr '.' ID '(' exprList? ')'
|
||||
* field : expr '.' ID
|
||||
*/
|
||||
Expr indexMethodField(Expr expr) {
|
||||
if (expr == null) {
|
||||
expr = map();
|
||||
}
|
||||
|
||||
// Expr expr = map();
|
||||
while (true) {
|
||||
Tok tok = peek();
|
||||
// expr [ expr ]
|
||||
if (tok.sym == Sym.LBRACK) {
|
||||
move();
|
||||
Expr index = expr();
|
||||
match(Sym.RBRACK);
|
||||
expr = new Index(expr, index, location);
|
||||
continue;
|
||||
}
|
||||
if (tok.sym != Sym.DOT) {
|
||||
return expr;
|
||||
}
|
||||
if ((tok = move()).sym != Sym.ID) {
|
||||
resetForward(forward - 1);
|
||||
return expr;
|
||||
}
|
||||
|
||||
move();
|
||||
if (peek().sym != Sym.LPAREN) {
|
||||
expr = new Field(expr, tok.value(), location);
|
||||
continue;
|
||||
}
|
||||
|
||||
move();
|
||||
// expr '.' ID '(' ')'
|
||||
if (peek().sym == Sym.RPAREN) {
|
||||
move();
|
||||
expr = new Method(expr, tok.value(), location);
|
||||
continue;
|
||||
}
|
||||
|
||||
// expr '.' ID '(' exprList ')'
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
match(Sym.RPAREN);
|
||||
expr = new Method(expr, tok.value(), exprList, location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* map : '{' (mapEntry ( , mapEntry ) * ) ? '}'
|
||||
* mapEntry : (ID | STR) ':' expr
|
||||
*/
|
||||
Expr map() {
|
||||
if (peek().sym != Sym.LBRACE) {
|
||||
return array();
|
||||
}
|
||||
|
||||
LinkedHashMap<Object, Expr> mapEntry = new LinkedHashMap<Object, Expr>();
|
||||
Map map = new Map(mapEntry);
|
||||
move();
|
||||
if (peek().sym == Sym.RBRACE) {
|
||||
move();
|
||||
return map;
|
||||
}
|
||||
|
||||
buildMapEntry(mapEntry);
|
||||
while (peek().sym == Sym.COMMA) {
|
||||
move();
|
||||
buildMapEntry(mapEntry);
|
||||
}
|
||||
match(Sym.RBRACE);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* mapEntry : (ID | STR) ':' expr
|
||||
*/
|
||||
void buildMapEntry(LinkedHashMap<Object, Expr> map) {
|
||||
Tok tok = peek();
|
||||
if (tok.sym == Sym.ID || tok.sym == Sym.STR) {
|
||||
move();
|
||||
match(Sym.COLON);
|
||||
Expr value = expr();
|
||||
if (value == null) {
|
||||
throw new ParseException("Expression error: the value on the right side of map entry can not be blank", location);
|
||||
}
|
||||
map.put(tok.value(), value);
|
||||
return ;
|
||||
}
|
||||
throw new ParseException("Expression error: the value of map key must be identifier or String", location);
|
||||
}
|
||||
|
||||
/**
|
||||
* array : '[' exprList ? | range ? ']'
|
||||
* exprList : expr (',' expr)*
|
||||
* range : expr .. expr
|
||||
*/
|
||||
Expr array() {
|
||||
if (peek().sym != Sym.LBRACK) {
|
||||
return atom();
|
||||
}
|
||||
|
||||
move();
|
||||
if (peek().sym == Sym.RBRACK) {
|
||||
move();
|
||||
return new Array(ExprList.NULL_EXPR_ARRAY, location);
|
||||
}
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
if (exprList.length() == 1 && peek().sym == Sym.RANGE) {
|
||||
move();
|
||||
Expr end = expr();
|
||||
match(Sym.RBRACK);
|
||||
return new RangeArray(exprList.getExprArray()[0], end, location);
|
||||
}
|
||||
|
||||
match(Sym.RBRACK);
|
||||
return new Array(exprList.getExprArray(), location);
|
||||
}
|
||||
|
||||
/**
|
||||
* atom : '(' expr ')' | ID | STR | 'true' | 'false' | 'null'
|
||||
* | INT | LONG | FLOAT | DOUBLE
|
||||
*/
|
||||
Expr atom() {
|
||||
Tok tok = peek();
|
||||
switch (tok.sym) {
|
||||
case LPAREN:
|
||||
move();
|
||||
Expr expr = expr();
|
||||
match(Sym.RPAREN);
|
||||
return expr;
|
||||
case ID:
|
||||
move();
|
||||
return new Id(tok.value());
|
||||
case STR:
|
||||
case INT:
|
||||
case LONG:
|
||||
case FLOAT:
|
||||
case DOUBLE:
|
||||
move();
|
||||
return new Const(tok.sym, tok.value());
|
||||
case TRUE:
|
||||
move();
|
||||
return Const.TRUE;
|
||||
case FALSE:
|
||||
move();
|
||||
return Const.FALSE;
|
||||
case NULL:
|
||||
move();
|
||||
return Const.NULL;
|
||||
case COMMA:
|
||||
case SEMICOLON:
|
||||
case QUESTION: // support "c ?? ? a : b"
|
||||
case AND: case OR: case EQUAL: case NOTEQUAL: // support "a.b ?? && expr"
|
||||
case RPAREN: // support "(a.b ??)"
|
||||
case RBRACK: // support "[start .. end ??]"
|
||||
case RBRACE: // support "{key : value ??}"
|
||||
case RANGE: // support "[start ?? .. end]"
|
||||
case COLON: // support "c ? a ?? : b"
|
||||
case EOF:
|
||||
return null;
|
||||
default :
|
||||
throw new ParseException("Expression error: can not match the symbol \"" + tok.value() + "\"", location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* forControl : ID : expr | exprList? ';' expr? ';' exprList?
|
||||
*/
|
||||
Expr forCtrl() {
|
||||
ExprList exprList = (ExprList)exprList();
|
||||
if (peek().sym == Sym.SEMICOLON) {
|
||||
move();
|
||||
Expr cond = expr();
|
||||
match(Sym.SEMICOLON);
|
||||
Expr update = exprList();
|
||||
return new ForCtrl(exprList, cond, update, location);
|
||||
}
|
||||
|
||||
if (exprList.length() == 1) {
|
||||
Expr expr = exprList.getExprArray()[0];
|
||||
if (expr instanceof Id) {
|
||||
match(Sym.COLON);
|
||||
return new ForCtrl(((Id)expr), expr(), location);
|
||||
}
|
||||
}
|
||||
throw new ParseException("The expression of #for directive is error", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
103
src/main/java/com/jfinal/template/expr/NumTok.java
Normal file
103
src/main/java/com/jfinal/template/expr/NumTok.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
|
||||
/**
|
||||
* NumToken 封装所有数值类型,并进行类型转换,以便尽早抛出异常
|
||||
*
|
||||
* java 数值类型规则:
|
||||
* 1:科学计数法默认为 double 类型,通过 Object v = 123E1; 测试可知
|
||||
* 2:出现小数点的浮点数默认为 double 类型,无需指定 D/d 后缀。 而 float 类型必须指令 F/f 后缀
|
||||
* 3:double、float (出现小数点即为浮点数) 只支持 10 进制:16 进制形式去书写直接报错,8 进制形式去书写被当成 10 进制
|
||||
* 4:16 进制不支持科学计数法,因为 E/e 后缀会被当成是普通的 16 进制数字,而 +/- 号则被当成了加/减法运算
|
||||
* 5: 8 进制在本质上不支持科学计数法,010E1 这样的科学计数写法会被当成 10 进制,去掉后面的 E1 变为 010 时才被当成 8 进制
|
||||
* 6:所以 16 8 进制都不支持科学计数法,结论是对科学计数法的类型转换无需指定 radix 参数,而 BigDecimal 正好也不支持这个参数
|
||||
*
|
||||
* 概要:
|
||||
* 1:16 8 进制不支持浮点数
|
||||
* 前者直接报错,后者直接忽略前缀 0 并当作 10 进制处理
|
||||
*
|
||||
* 2:16 8 进制不支持科学计数法
|
||||
* 虽然二者在书写方式上被允许写成 16 8 进制,但只将其当成 10 进制处理,前者将 E/e 当成16进制数字
|
||||
* 后者忽略前缀 0 当成 10 进制处理,即看似 8 进制的科学计数法,实质是 10 进制科学计数法
|
||||
*
|
||||
* 3: 科学计数法在本质上是 double,所以总结为一点 ---> 16 8 进制只支持整型数据
|
||||
*/
|
||||
public class NumTok extends Tok {
|
||||
|
||||
private Object value;
|
||||
|
||||
NumTok(Sym sym, String s, int radix, boolean isScientificNotation, Location location) {
|
||||
super(sym, location.getRow());
|
||||
try {
|
||||
typeConvert(sym, s, radix, isScientificNotation, location);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void typeConvert(Sym sym, String s, int radix, boolean isScientificNotation, Location location) {
|
||||
switch (sym) {
|
||||
case INT:
|
||||
if (isScientificNotation) {
|
||||
value = new BigDecimal(s).intValue();
|
||||
} else {
|
||||
value = Integer.valueOf(s, radix); // 整型数据才支持 16 8 进制
|
||||
}
|
||||
break ;
|
||||
case LONG:
|
||||
if (isScientificNotation) {
|
||||
value = new BigDecimal(s).longValue();
|
||||
} else {
|
||||
value = Long.valueOf(s, radix); // 整型数据才支持 16 8 进制
|
||||
}
|
||||
break ;
|
||||
case FLOAT:
|
||||
if (isScientificNotation) {
|
||||
value = new BigDecimal(s).floatValue();
|
||||
} else {
|
||||
value = Float.valueOf(s); // 浮点数只支持 10 进制
|
||||
}
|
||||
break ;
|
||||
case DOUBLE:
|
||||
if (isScientificNotation) {
|
||||
value = new BigDecimal(s).doubleValue();
|
||||
} else {
|
||||
value = Double.valueOf(s); // 浮点数只支持 10 进制
|
||||
}
|
||||
break ;
|
||||
default :
|
||||
throw new ParseException("Unsupported type: " + sym.value(), location);
|
||||
}
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
public Object getNumberValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return sym.value() + " : " + value;
|
||||
}
|
||||
}
|
64
src/main/java/com/jfinal/template/expr/Sym.java
Normal file
64
src/main/java/com/jfinal/template/expr/Sym.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Sym
|
||||
*/
|
||||
public enum Sym {
|
||||
|
||||
ASSIGN("="),
|
||||
|
||||
DOT("."), RANGE(".."), COLON(":"), STATIC("::"), COMMA(","), SEMICOLON(";"),
|
||||
LPAREN("("), RPAREN(")"), LBRACK("["), RBRACK("]"), LBRACE("{"), RBRACE("}"),
|
||||
|
||||
ADD("+"), SUB("-"), INC("++"), DEC("--"),
|
||||
MUL("*"), DIV("/"), MOD("%"),
|
||||
|
||||
EQUAL("=="), NOTEQUAL("!="), LT("<"), LE("<="), GT(">"), GE(">="),
|
||||
|
||||
NOT("!"), AND("&&"), OR("||"),
|
||||
|
||||
QUESTION("?"),
|
||||
NULL_SAFE("??"),
|
||||
|
||||
ID("ID"),
|
||||
|
||||
STR("STR"), TRUE("TRUE"), FALSE("FALSE"), NULL("NULL"),
|
||||
INT("INT"), LONG("LONG"), FLOAT("FLOAT"), DOUBLE("DOUBLE"),
|
||||
|
||||
EOF("EOF");
|
||||
|
||||
private final String value;
|
||||
|
||||
private Sym(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
63
src/main/java/com/jfinal/template/expr/Tok.java
Normal file
63
src/main/java/com/jfinal/template/expr/Tok.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Tok
|
||||
*/
|
||||
class Tok {
|
||||
|
||||
final Sym sym;
|
||||
private final String value;
|
||||
final int row;
|
||||
|
||||
Tok(Sym sym, int row) {
|
||||
this(sym, sym.value(), row);
|
||||
}
|
||||
|
||||
Tok(Sym exprSym, String value, int row) {
|
||||
if (exprSym == null || value == null) {
|
||||
throw new IllegalArgumentException("exprSym and value can not be null");
|
||||
}
|
||||
this.sym = exprSym;
|
||||
this.value = value;
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
void print() {
|
||||
System.out.print("[");
|
||||
System.out.print(row);
|
||||
System.out.print(", ");
|
||||
System.out.print(sym.value());
|
||||
System.out.print(", ");
|
||||
System.out.print(value());
|
||||
System.out.println("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
229
src/main/java/com/jfinal/template/expr/ast/Arith.java
Normal file
229
src/main/java/com/jfinal/template/expr/ast/Arith.java
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Arithmetic
|
||||
* 1:支持 byte short int long float double BigDecimal 的 + - * / % 运算
|
||||
* 2:支持字符串加法运算
|
||||
*/
|
||||
public class Arith extends Expr {
|
||||
|
||||
public static final int INT = 0; // byte、short 用 int 类型支持,java 表达式亦如此
|
||||
public static final int LONG = 1;
|
||||
public static final int FLOAT = 2;
|
||||
public static final int DOUBLE = 3;
|
||||
public static final int BIGDECIMAL = 4;
|
||||
|
||||
private Sym op;
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public Arith(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null || right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
try {
|
||||
return doEval(scope);
|
||||
} catch (TemplateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object doEval(Scope scope) {
|
||||
Object leftValue = left.eval(scope);
|
||||
Object rightValue = right.eval(scope);
|
||||
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
|
||||
switch (op) {
|
||||
case ADD:
|
||||
return add(maxType, l, r);
|
||||
case SUB:
|
||||
return sub(maxType, l, r);
|
||||
case MUL:
|
||||
return mul(maxType, l, r);
|
||||
case DIV:
|
||||
return div(maxType, l, r);
|
||||
case MOD:
|
||||
return mod(maxType, l, r);
|
||||
default :
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
|
||||
// 字符串加法运算
|
||||
if (leftValue instanceof String || rightValue instanceof String) {
|
||||
return String.valueOf(leftValue).concat(String.valueOf(rightValue));
|
||||
}
|
||||
|
||||
String leftObj = leftValue != null ? leftValue.getClass().getName() : "null";
|
||||
String rightObj = rightValue != null ? rightValue.getClass().getName() : "null";
|
||||
throw new TemplateException("Unsupported operation type: " + leftObj + " " + op.value() + " " + rightObj, location);
|
||||
}
|
||||
|
||||
private int getMaxType(Number obj1, Number obj2) {
|
||||
int t1 = getType(obj1);
|
||||
if (t1 == BIGDECIMAL) {
|
||||
return BIGDECIMAL;
|
||||
}
|
||||
int t2 = getType(obj2);
|
||||
return t1 > t2 ? t1 : t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注意:调用此方法的前提是,其中有一个对象的类型已经确定是 BigDecimal
|
||||
*/
|
||||
private BigDecimal[] toBigDecimals(Number left, Number right) {
|
||||
BigDecimal[] ret = new BigDecimal[2];
|
||||
if (left instanceof BigDecimal) {
|
||||
ret[0] = (BigDecimal)left;
|
||||
ret[1] = new BigDecimal(right.toString());
|
||||
} else {
|
||||
ret[0] = new BigDecimal(left.toString());
|
||||
ret[1] = (BigDecimal)right;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private int getType(Number obj) {
|
||||
if (obj instanceof Integer) {
|
||||
return INT;
|
||||
} else if (obj instanceof Long) {
|
||||
return LONG;
|
||||
} else if (obj instanceof Float) {
|
||||
return FLOAT;
|
||||
} else if (obj instanceof Double) {
|
||||
return DOUBLE;
|
||||
} else if (obj instanceof BigDecimal) {
|
||||
return BIGDECIMAL;
|
||||
} else if (obj instanceof Short || obj instanceof Byte) {
|
||||
return INT; // short byte 用 int 支持,java 表达式亦如此
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + obj.getClass().getName(), location);
|
||||
}
|
||||
|
||||
private Number add(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() + right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() + right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() + right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() + right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).add(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number sub(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() - right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() - right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() - right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() - right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).subtract(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number mul(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() * right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() * right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() * right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() * right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).multiply(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number div(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() / right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() / right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() / right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() / right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
// return (bd[0]).divide(bd[1]);
|
||||
return (bd[0]).divide(bd[1], RoundingMode.HALF_EVEN); // 银行家舍入法
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number mod(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() % right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() % right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() % right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() % right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).divideAndRemainder(bd[1])[1];
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
68
src/main/java/com/jfinal/template/expr/ast/Array.java
Normal file
68
src/main/java/com/jfinal/template/expr/ast/Array.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Array
|
||||
*
|
||||
* 用法:
|
||||
* 1:[1, 2, 3]
|
||||
* 2:["a", 1, "b", 2, false, 3.14]
|
||||
*/
|
||||
public class Array extends Expr {
|
||||
|
||||
private Expr[] exprList;
|
||||
|
||||
public Array(Expr[] exprList, Location location) {
|
||||
if (exprList == null) {
|
||||
throw new ParseException("exprList can not be null", location);
|
||||
}
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
List<Object> array = new ArrayListExt(exprList.length);
|
||||
for (Expr expr : exprList) {
|
||||
array.add(expr.eval(scope));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 array.length 表达式
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class ArrayListExt extends ArrayList<Object> {
|
||||
|
||||
public ArrayListExt(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
public Integer getLength() {
|
||||
return size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
142
src/main/java/com/jfinal/template/expr/ast/Assign.java
Normal file
142
src/main/java/com/jfinal/template/expr/ast/Assign.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Assign
|
||||
*
|
||||
* 支持三种赋值,其中第二种如果括号中是 ID 或 STR 则演变为第三种是对 map 赋值:
|
||||
* 1:ID = expr
|
||||
* 2:ID [ expr ] = expr
|
||||
* 如果 expr 为 int 或 long 型,则是对 array 赋值
|
||||
* 如果 expr 为 ID、STR 型,则是对 map 进行赋值
|
||||
* 否则抛异常出来
|
||||
* 3:ID [ ID ] = expr 或者 ID [ STR ] = expr
|
||||
* 4:支持无限连:id = array[ i = 0 ] = array[1] = 123
|
||||
*/
|
||||
public class Assign extends Expr {
|
||||
|
||||
private String id;
|
||||
private Expr index; // index 用于支持 ID [ expr ] = expr 这种形式
|
||||
private Expr right;
|
||||
|
||||
/**
|
||||
* 数组赋值表达式
|
||||
*/
|
||||
public Assign(String id, Expr index, Expr right, Location location) {
|
||||
if (index == null) {
|
||||
throw new ParseException("The index expression of array assignment can not be null", location);
|
||||
}
|
||||
if (right == null) {
|
||||
throw new ParseException("The expression on the right side of an assignment expression can not be null", location);
|
||||
}
|
||||
this.id = id;
|
||||
this.index = index;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通赋值表达式
|
||||
*/
|
||||
public Assign(String id, Expr right, Location location) {
|
||||
if (right == null) {
|
||||
throw new ParseException("The expression on the right side of an assignment expression can not be null", location);
|
||||
}
|
||||
this.id = id;
|
||||
this.index = null;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赋值语句有返回值,可以用于表达式计算
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
if (index == null) {
|
||||
return assignVariable(scope);
|
||||
} else {
|
||||
return assignElement(scope);
|
||||
}
|
||||
}
|
||||
|
||||
Object assignVariable(Scope scope) {
|
||||
Object rightValue = right.eval(scope);
|
||||
if (scope.getCtrl().isWisdomAssignment()) {
|
||||
scope.set(id, rightValue);
|
||||
} else if (scope.getCtrl().isLocalAssignment()) {
|
||||
scope.setLocal(id, rightValue);
|
||||
} else {
|
||||
scope.setGlobal(id, rightValue);
|
||||
}
|
||||
|
||||
return rightValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组或 Map 赋值
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Object assignElement(Scope scope) {
|
||||
Object target = scope.get(id);
|
||||
if (target == null) {
|
||||
throw new TemplateException("The assigned targets \"" + id + "\" can not be null", location);
|
||||
}
|
||||
Object idx = index.eval(scope);
|
||||
if (idx == null) {
|
||||
throw new TemplateException("The index of list/array and the key of map can not be null", location);
|
||||
}
|
||||
|
||||
Object value;
|
||||
if (target instanceof Map) {
|
||||
value = right.eval(scope);
|
||||
((Map)target).put(idx, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if ( !(idx instanceof Integer) ) {
|
||||
throw new TemplateException("The index of list/array can only be integer", location);
|
||||
}
|
||||
|
||||
if (target instanceof List) {
|
||||
value = right.eval(scope);
|
||||
((List)target).set((Integer)idx, value);
|
||||
return value;
|
||||
}
|
||||
if (target.getClass().isArray()) {
|
||||
value = right.eval(scope);
|
||||
java.lang.reflect.Array.set(target, (Integer)idx, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new TemplateException("Only the list array and map is supported by index assignment", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
288
src/main/java/com/jfinal/template/expr/ast/Compare.java
Normal file
288
src/main/java/com/jfinal/template/expr/ast/Compare.java
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Compare
|
||||
*
|
||||
* 1:支持 byte short int long float double BigDecimal 的 == != > >= < <= 操作
|
||||
* 2:== != 作用于 string,调用其 equals 方法进行比较
|
||||
* 3:> >= < <= 可以比较实现了 Comparable 接口的对象
|
||||
*
|
||||
* 注意:float double 浮点型数据在比较操作时,具有精度上的局限性,不建议对浮点数进行比较
|
||||
*/
|
||||
public class Compare extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public Compare(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null || right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object leftValue = left.eval(scope);
|
||||
Object rightValue = right.eval(scope);
|
||||
|
||||
switch(op) {
|
||||
case EQUAL:
|
||||
return equal(leftValue, rightValue);
|
||||
case NOTEQUAL:
|
||||
return ! equal(leftValue, rightValue);
|
||||
case GT:
|
||||
return gt(leftValue, rightValue);
|
||||
case GE:
|
||||
return ge(leftValue, rightValue);
|
||||
case LT:
|
||||
return lt(leftValue, rightValue);
|
||||
case LE:
|
||||
return le(leftValue, rightValue);
|
||||
default:
|
||||
String l = leftValue != null ? leftValue.getClass().getSimpleName() : "null";
|
||||
String r = rightValue != null ? rightValue.getClass().getSimpleName() : "null";
|
||||
throw new TemplateException("Unsupported operation: " + l + " \"" + op.value() + "\" " + r, location);
|
||||
}
|
||||
}
|
||||
|
||||
Boolean equal(Object leftValue, Object rightValue) {
|
||||
if (leftValue == rightValue) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (leftValue == null || rightValue == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (leftValue.equals(rightValue)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() == r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() == r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) == 0;
|
||||
}
|
||||
throw new TemplateException("Equal comparison support types of int long float double and BigDeciaml", location);
|
||||
}
|
||||
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean gt(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() > r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() > r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) > 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \">\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) > 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean ge(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() >= r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() >= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) >= 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \">=\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) >= 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean lt(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() < r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() < r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) < 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \"<\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) < 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean le(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() <= r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() <= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) <= 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \"<=\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) <= 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
private int getMaxType(Number obj1, Number obj2) {
|
||||
int t1 = getType(obj1);
|
||||
if (t1 == Arith.BIGDECIMAL) {
|
||||
return Arith.BIGDECIMAL;
|
||||
}
|
||||
int t2 = getType(obj2);
|
||||
return t1 > t2 ? t1 : t2;
|
||||
}
|
||||
|
||||
private int getType(Number obj) {
|
||||
if (obj instanceof Integer) {
|
||||
return Arith.INT;
|
||||
} else if (obj instanceof Long) {
|
||||
return Arith.LONG;
|
||||
} else if (obj instanceof Float) {
|
||||
return Arith.FLOAT;
|
||||
} else if (obj instanceof Double) {
|
||||
return Arith.DOUBLE;
|
||||
} else if (obj instanceof BigDecimal) {
|
||||
return Arith.BIGDECIMAL;
|
||||
} else if (obj instanceof Short || obj instanceof Byte) {
|
||||
return Arith.INT; // short byte 用 int 支持,java 表达式亦如此
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + obj.getClass().getName(), location);
|
||||
}
|
||||
|
||||
BigDecimal[] toBigDecimals(Number left, Number right) {
|
||||
BigDecimal[] ret = new BigDecimal[2];
|
||||
ret[0] = (left instanceof BigDecimal ? (BigDecimal)left : new BigDecimal(left.toString()));
|
||||
ret[1] = (right instanceof BigDecimal ? (BigDecimal)right : new BigDecimal(right.toString()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Boolean checkType(Object leftValue, Object rightValue) {
|
||||
if (leftValue == null) {
|
||||
throw new TemplateException("The operation target on the left side of \"" + op.value() + "\" can not be null", location);
|
||||
}
|
||||
if (rightValue == null) {
|
||||
throw new TemplateException("The operation target on the right side of \"" + op.value() + "\" can not be null", location);
|
||||
}
|
||||
|
||||
throw new TemplateException(
|
||||
"Unsupported operation: " +
|
||||
leftValue.getClass().getSimpleName() +
|
||||
" \"" + op.value() + "\" " +
|
||||
rightValue.getClass().getSimpleName(),
|
||||
location
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
150
src/main/java/com/jfinal/template/expr/ast/Const.java
Normal file
150
src/main/java/com/jfinal/template/expr/ast/Const.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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.expr.Sym;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* STR INT LONG FLOAT DOUBLE true false null
|
||||
*/
|
||||
public class Const extends Expr {
|
||||
|
||||
public static final Const TRUE = new Const(Boolean.TRUE, Sym.TRUE);
|
||||
public static final Const FALSE = new Const(Boolean.FALSE, Sym.FALSE);
|
||||
public static final Const NULL = new Const(null, Sym.NULL);
|
||||
|
||||
private Sym type;
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 构造 TRUE FALSE NULL 常量,无需对 value 进行转换
|
||||
*/
|
||||
private Const(Object value, Sym type) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Const(Sym type, String value) {
|
||||
this.type = type;
|
||||
this.value = typeConvert(type, value);
|
||||
}
|
||||
|
||||
private Object typeConvert(Sym type, String value) {
|
||||
switch (type) {
|
||||
case STR:
|
||||
return value;
|
||||
case INT:
|
||||
return Integer.parseInt(value);
|
||||
case LONG:
|
||||
return Long.parseLong(value);
|
||||
case FLOAT:
|
||||
return Float.parseFloat(value);
|
||||
case DOUBLE:
|
||||
return Double.parseDouble(value);
|
||||
/*
|
||||
case TRUE:
|
||||
case FALSE:
|
||||
return Boolean.parseBoolean(value);
|
||||
case NULL:
|
||||
return null;
|
||||
*/
|
||||
default:
|
||||
throw new RuntimeException("never happend");
|
||||
}
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
public boolean isStr() {
|
||||
return type == Sym.STR;
|
||||
}
|
||||
|
||||
public boolean isTrue() {
|
||||
return type == Sym.TRUE;
|
||||
}
|
||||
|
||||
public boolean isFalse() {
|
||||
return type == Sym.FALSE;
|
||||
}
|
||||
|
||||
public boolean isBoolean() {
|
||||
return type == Sym.TRUE || type == Sym.FALSE;
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return type == Sym.NULL;
|
||||
}
|
||||
|
||||
public boolean isInt() {
|
||||
return type == Sym.INT;
|
||||
}
|
||||
|
||||
public boolean isLong() {
|
||||
return type == Sym.LONG;
|
||||
}
|
||||
|
||||
public boolean isFloat() {
|
||||
return type == Sym.FLOAT;
|
||||
}
|
||||
|
||||
public boolean isDouble() {
|
||||
return type == Sym.DOUBLE;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return (String)value;
|
||||
}
|
||||
|
||||
public Boolean getBoolean() {
|
||||
return (Boolean)value;
|
||||
}
|
||||
|
||||
public Integer getInt() {
|
||||
return (Integer)value;
|
||||
}
|
||||
|
||||
public Long getLong() {
|
||||
return (Long)value;
|
||||
}
|
||||
|
||||
public Float getFloat() {
|
||||
return (Float)value;
|
||||
}
|
||||
|
||||
public Double getDouble() {
|
||||
return (Double)value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
34
src/main/java/com/jfinal/template/expr/ast/Expr.java
Normal file
34
src/main/java/com/jfinal/template/expr/ast/Expr.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.Location;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Expr
|
||||
*/
|
||||
public abstract class Expr {
|
||||
|
||||
protected Location location;
|
||||
|
||||
public abstract Object eval(Scope scope);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
89
src/main/java/com/jfinal/template/expr/ast/ExprList.java
Normal file
89
src/main/java/com/jfinal/template/expr/ast/ExprList.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* ExprList
|
||||
*/
|
||||
public class ExprList extends Expr {
|
||||
|
||||
public static final Expr[] NULL_EXPR_ARRAY = new 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) {
|
||||
exprArray = exprList.toArray(new Expr[exprList.size()]);
|
||||
} else {
|
||||
exprArray = NULL_EXPR_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
public Expr[] getExprArray() {
|
||||
return exprArray;
|
||||
}
|
||||
|
||||
public Expr getExpr(int index) {
|
||||
if (index < 0 || index >= exprArray.length) {
|
||||
throw new TemplateException("Index out of bounds: index = " + index + ", length = " + exprArray.length, location);
|
||||
}
|
||||
return exprArray[index];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return exprArray.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有表达式求值,只返回最后一个表达式的值
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
Object ret = null;
|
||||
for (Expr expr : exprArray) {
|
||||
ret = expr.eval(scope);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有表达式求值,并返回所有表达式的值
|
||||
*/
|
||||
public Object[] evalExprList(Scope scope) {
|
||||
if (exprArray.length == 0) {
|
||||
return NULL_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
Object[] ret = new Object[exprArray.length];
|
||||
for (int i=0; i<exprArray.length; i++) {
|
||||
ret[i] = exprArray[i].eval(scope);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
121
src/main/java/com/jfinal/template/expr/ast/Field.java
Normal file
121
src/main/java/com/jfinal/template/expr/ast/Field.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.Array;
|
||||
import com.jfinal.kit.StrKit;
|
||||
// import com.jfinal.plugin.activerecord.Model;
|
||||
// import com.jfinal.plugin.activerecord.Record;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Field
|
||||
*
|
||||
* field 表达式取值优先次序,以 user.name 为例
|
||||
* 1:假如 user.getName() 存在,则优先调用
|
||||
* 2:假如 user 为 Model 子类,则调用 user.get("name")
|
||||
* 3:假如 user 为 Record,则调用 user.get("name")
|
||||
* 4:假如 user 为 Map,则调用 user.get("name")
|
||||
* 5:假如 user 具有 public name 属性,则取 user.name 属性值
|
||||
*/
|
||||
public class Field extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private String fieldName;
|
||||
private String getterName;
|
||||
|
||||
public Field(Expr expr, String fieldName, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The object for field access can not be null", location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.fieldName = fieldName;
|
||||
this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object target = expr.eval(scope);
|
||||
if (target == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
if (expr instanceof Id) {
|
||||
String id = ((Id)expr).getId();
|
||||
throw new TemplateException("\"" + id + "\" can not be null for accessed by \"" + id + "." + fieldName + "\"", location);
|
||||
}
|
||||
throw new TemplateException("Can not accessed by \"" + fieldName + "\" field from null target", location);
|
||||
}
|
||||
|
||||
Class<?> targetClass = target.getClass();
|
||||
String key = FieldKit.getFieldKey(targetClass, getterName);
|
||||
MethodInfo getter;
|
||||
try {
|
||||
getter = MethodKit.getGetterMethod(key, targetClass, getterName);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (getter != null) {
|
||||
return getter.invoke(target, ExprList.NULL_OBJECT_ARRAY);
|
||||
}
|
||||
// if (target instanceof Model) {
|
||||
// return ((Model<?>)target).get(fieldName);
|
||||
// }
|
||||
// if (target instanceof Record) {
|
||||
// return ((Record)target).get(fieldName);
|
||||
// }
|
||||
if (target instanceof java.util.Map) {
|
||||
return ((java.util.Map<?, ?>)target).get(fieldName);
|
||||
}
|
||||
// if (target instanceof com.jfinal.kit.Ret) {
|
||||
// return ((com.jfinal.kit.Ret)target).get(fieldName);
|
||||
// }
|
||||
java.lang.reflect.Field field = FieldKit.getField(key, targetClass, fieldName);
|
||||
if (field != null) {
|
||||
return field.get(target);
|
||||
}
|
||||
|
||||
// 支持获取数组长度: array.length
|
||||
if ("length".equals(fieldName) && target.getClass().isArray()) {
|
||||
return Array.getLength(target);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (expr instanceof Id) {
|
||||
String id = ((Id)expr).getId();
|
||||
throw new TemplateException("Field not found: \"" + id + "." + fieldName + "\" and getter method not found: \"" + id + "." + getterName + "()\"", location);
|
||||
}
|
||||
throw new TemplateException("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
65
src/main/java/com/jfinal/template/expr/ast/FieldKit.java
Normal file
65
src/main/java/com/jfinal/template/expr/ast/FieldKit.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* FieldKit
|
||||
*/
|
||||
public class FieldKit {
|
||||
|
||||
private static final ConcurrentHashMap<String, Object> fieldCache = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
public static Field getField(String key, Class<?> targetClass, String fieldName) {
|
||||
Object field = fieldCache.get(key);
|
||||
if (field == null) {
|
||||
field = doGetField(targetClass, fieldName);
|
||||
if (field != null) {
|
||||
fieldCache.putIfAbsent(key, field);
|
||||
} else {
|
||||
// 对于不存在的 Field,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
|
||||
fieldCache.put(key, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
return field instanceof Field ? (Field)field : null;
|
||||
}
|
||||
|
||||
private static Field doGetField(Class<?> targetClass, String fieldName) {
|
||||
Field[] fs = targetClass.getFields();
|
||||
for (Field f : fs) {
|
||||
if (f.getName().equals(fieldName)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Field 用于缓存的 key
|
||||
*/
|
||||
public static String getFieldKey(Class<?> targetClass, String getterName) {
|
||||
return new StringBuilder(64).append(targetClass.getName())
|
||||
.append('.').append(getterName).toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
99
src/main/java/com/jfinal/template/expr/ast/ForCtrl.java
Normal file
99
src/main/java/com/jfinal/template/expr/ast/ForCtrl.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* forCtrl : ID : expression
|
||||
* | exprList? ';' expr? ';' exprList?
|
||||
*
|
||||
* 两种用法
|
||||
* 1:#for(id : list) #end
|
||||
* #for(entry : map) #end
|
||||
*
|
||||
* 2:#for(init; cond; update) #end
|
||||
*/
|
||||
public class ForCtrl extends Expr {
|
||||
|
||||
private String id;
|
||||
private Expr expr;
|
||||
|
||||
private Expr init;
|
||||
private Expr cond;
|
||||
private Expr update;
|
||||
|
||||
/**
|
||||
* ID : expr
|
||||
*/
|
||||
public ForCtrl(Id id, Expr expr, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The iterator target of #for statement can not be null", location);
|
||||
}
|
||||
this.id = id.getId();
|
||||
this.expr = expr;
|
||||
this.init = null;
|
||||
this.cond = null;
|
||||
this.update = null;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* exprList? ';' expr? ';' exprList?
|
||||
*/
|
||||
public ForCtrl(Expr init, Expr cond, Expr update, Location location) {
|
||||
this.init = init;
|
||||
this.cond = cond;
|
||||
this.update = update;
|
||||
this.id = null;
|
||||
this.expr = null;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public boolean isIterator() {
|
||||
return id != null;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Expr getExpr() {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expr getInit() {
|
||||
return init;
|
||||
}
|
||||
|
||||
public Expr getCond() {
|
||||
return cond;
|
||||
}
|
||||
|
||||
public Expr getUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
throw new TemplateException("The eval(Scope scope) method can not be invoked", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
48
src/main/java/com/jfinal/template/expr/ast/Id.java
Normal file
48
src/main/java/com/jfinal/template/expr/ast/Id.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
public class Id extends Expr {
|
||||
|
||||
private String id;
|
||||
|
||||
public Id(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
return scope.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Id.toString() 后续版本不能变动,已有部分第三方依赖此方法
|
||||
*/
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
|
138
src/main/java/com/jfinal/template/expr/ast/IncDec.java
Normal file
138
src/main/java/com/jfinal/template/expr/ast/IncDec.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 自增与自减
|
||||
*/
|
||||
public class IncDec extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private String id;
|
||||
private boolean isPost; // 是否是后缀形式: i++ i--
|
||||
|
||||
public IncDec(Sym op, boolean isPost, Expr id, Location location) {
|
||||
if (id == null) {
|
||||
throw new ParseException(op.value() + " operator requires target to be operational", location);
|
||||
}
|
||||
if ( !(id instanceof Id) ) {
|
||||
throw new ParseException(op.value() + " operator only supports identifiers", location);
|
||||
}
|
||||
|
||||
this.op = op;
|
||||
this.id = ((Id)id).getId();
|
||||
this.isPost = isPost;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Object eval(Scope scope) {
|
||||
Map map = scope.getMapOfValue(id);
|
||||
if (map == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The target of " + op.value() + " operator can not be null", location);
|
||||
}
|
||||
Object value = map.get(id);
|
||||
if ( !(value instanceof Number) ) {
|
||||
throw new TemplateException(op.value() + " operator only support int long float double and BigDecimal type", location);
|
||||
}
|
||||
|
||||
Number newValue;
|
||||
switch (op) {
|
||||
case INC:
|
||||
newValue = inc((Number)value);
|
||||
break ;
|
||||
case DEC:
|
||||
newValue = dec((Number)value);
|
||||
break ;
|
||||
default:
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
map.put(id, newValue);
|
||||
return isPost ? value : newValue;
|
||||
}
|
||||
|
||||
private Number inc(Number num) {
|
||||
if (num instanceof Integer) {
|
||||
return Integer.valueOf(num.intValue() + 1);
|
||||
}
|
||||
if (num instanceof Long) {
|
||||
return Long.valueOf(num.longValue() + 1L);
|
||||
}
|
||||
if (num instanceof Float) {
|
||||
return Float.valueOf(num.floatValue() + 1F);
|
||||
}
|
||||
if (num instanceof Double) {
|
||||
return Double.valueOf(num.doubleValue() + 1D);
|
||||
}
|
||||
if (num instanceof BigDecimal) {
|
||||
return ((BigDecimal)num).add(BigDecimal.ONE);
|
||||
}
|
||||
if (num instanceof BigInteger) {
|
||||
return ((BigInteger)num).add(BigInteger.ONE);
|
||||
}
|
||||
if (num instanceof Short) {
|
||||
return (short)(((Short)num).shortValue() + 1);
|
||||
}
|
||||
if (num instanceof Byte) {
|
||||
return (byte)(((Byte)num).byteValue() + 1);
|
||||
}
|
||||
return num.intValue() + 1;
|
||||
}
|
||||
|
||||
private Number dec(Number num) {
|
||||
if (num instanceof Integer) {
|
||||
return Integer.valueOf(num.intValue() - 1);
|
||||
}
|
||||
if (num instanceof Long) {
|
||||
return Long.valueOf(num.longValue() - 1L);
|
||||
}
|
||||
if (num instanceof Float) {
|
||||
return Float.valueOf(num.floatValue() - 1F);
|
||||
}
|
||||
if (num instanceof Double) {
|
||||
return Double.valueOf(num.doubleValue() - 1D);
|
||||
}
|
||||
if (num instanceof BigDecimal) {
|
||||
return ((BigDecimal)num).subtract(BigDecimal.ONE);
|
||||
}
|
||||
if (num instanceof BigInteger) {
|
||||
return ((BigInteger)num).subtract(BigInteger.ONE);
|
||||
}
|
||||
if (num instanceof Short) {
|
||||
return (short)(((Short)num).shortValue() - 1);
|
||||
}
|
||||
if (num instanceof Byte) {
|
||||
return (byte)(((Byte)num).byteValue() - 1);
|
||||
}
|
||||
return num.intValue() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
86
src/main/java/com/jfinal/template/expr/ast/Index.java
Normal file
86
src/main/java/com/jfinal/template/expr/ast/Index.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.util.List;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* index : expr '[' expr ']'
|
||||
*
|
||||
* 支持 a[i]、 a[b[i]]、a[i][j]、a[i][j]...[n]
|
||||
*/
|
||||
public class Index extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private Expr index;
|
||||
|
||||
public Index(Expr expr, Expr index, Location location) {
|
||||
if (expr == null || index == null) {
|
||||
throw new ParseException("array/list/map and their index can not be null", location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.index = index;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Object eval(Scope scope) {
|
||||
Object array = expr.eval(scope);
|
||||
if (array == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The index access operation target can not be null", location);
|
||||
}
|
||||
|
||||
Object idx = index.eval(scope);
|
||||
if (idx == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The index of list/array and the key of map can not be null", location);
|
||||
}
|
||||
|
||||
if (array instanceof List) {
|
||||
if (idx instanceof Integer) {
|
||||
return ((List<?>)array).get((Integer)idx);
|
||||
}
|
||||
throw new TemplateException("The index of list can only be integer", location);
|
||||
}
|
||||
|
||||
if (array instanceof java.util.Map) {
|
||||
return ((java.util.Map)array).get(idx);
|
||||
}
|
||||
|
||||
if (array.getClass().isArray()) {
|
||||
if (idx instanceof Integer) {
|
||||
return java.lang.reflect.Array.get(array, (Integer)idx);
|
||||
}
|
||||
throw new TemplateException("The index of array can only be integer", location);
|
||||
}
|
||||
|
||||
throw new TemplateException("Only the list array and map is supported by index access", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
145
src/main/java/com/jfinal/template/expr/ast/Logic.java
Normal file
145
src/main/java/com/jfinal/template/expr/ast/Logic.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Logic
|
||||
*
|
||||
* 支持逻辑运算: ! && ||
|
||||
*/
|
||||
public class Logic extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr left; // ! 运算没有 left 参数
|
||||
private Expr right;
|
||||
|
||||
/**
|
||||
* 构造 || && 结点
|
||||
*/
|
||||
public Logic(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the left side can not be blank", location);
|
||||
}
|
||||
if (right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the right side can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 ! 结点,left 为 null
|
||||
*/
|
||||
public Logic(Sym op, Expr right, Location location) {
|
||||
if (right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the right side can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = null;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
switch (op) {
|
||||
case NOT:
|
||||
return evalNot(scope);
|
||||
case AND:
|
||||
return evalAnd(scope);
|
||||
case OR:
|
||||
return evalOr(scope);
|
||||
default:
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
|
||||
Object evalNot(Scope scope) {
|
||||
return ! isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
Object evalAnd(Scope scope) {
|
||||
return isTrue(left.eval(scope)) && isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
Object evalOr(Scope scope) {
|
||||
return isTrue(left.eval(scope)) || isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则:
|
||||
* 1:null 返回 false
|
||||
* 2:boolean 类型,原值返回
|
||||
* 3:Map、Connection(List被包括在内) 返回 size() > 0
|
||||
* 4:数组,返回 length > 0
|
||||
* 5:String、StringBuilder、StringBuffer 等继承自 CharSequence 类的对象,返回 length > 0
|
||||
* 6:Number 类型,返回 value != 0
|
||||
* 7:Iterator 返回 hasNext() 值
|
||||
* 8:其它返回 true
|
||||
*/
|
||||
public static boolean isTrue(Object v) {
|
||||
if (v == null) {
|
||||
return false;
|
||||
}
|
||||
if (v instanceof Boolean) {
|
||||
return (Boolean)v;
|
||||
}
|
||||
if (v instanceof Collection) {
|
||||
return ((Collection<?>)v).size() > 0;
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
return ((Map<?, ?>)v).size() > 0;
|
||||
}
|
||||
if (v.getClass().isArray()) {
|
||||
return Array.getLength(v) > 0;
|
||||
}
|
||||
if (v instanceof CharSequence) {
|
||||
return ((CharSequence)v).length() > 0;
|
||||
}
|
||||
if (v instanceof Number) {
|
||||
if (v instanceof Double) {
|
||||
return ((Number)v).doubleValue() != 0;
|
||||
}
|
||||
if (v instanceof Float) {
|
||||
return ((Number)v).floatValue() != 0;
|
||||
}
|
||||
return ((Number)v).intValue() != 0;
|
||||
}
|
||||
if (v instanceof Iterator) {
|
||||
return ((Iterator<?>)v).hasNext();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isFalse(Object v) {
|
||||
return !isTrue(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
67
src/main/java/com/jfinal/template/expr/ast/Map.java
Normal file
67
src/main/java/com/jfinal/template/expr/ast/Map.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Map
|
||||
*
|
||||
* 1:定义 map 常量
|
||||
* {k1:123, k2:"abc", 'k3':true, "k4":[1,2,3], k5:1+2}
|
||||
* 如上所示,map定义的 key 可以为 String 或者 id 标识符,而右侧的 value 可以是任意的常量与表达式
|
||||
*
|
||||
* 2:取值
|
||||
* 先将 Map 常量赋值给某个变量: #set(map = {...})
|
||||
* map['k1']
|
||||
* map["k1"]
|
||||
* map[expr]
|
||||
* map.get("k1")
|
||||
* map.k1
|
||||
*
|
||||
* 如上所示,当以下标方式取值时,下标参数可以是 string 与 expr,而 expr 求值以后的值必须也为 string类型
|
||||
* 当用 map.k1 这类 field 字段取值形式时,则是使用 id 标识符,而不是 string 形参数
|
||||
*
|
||||
* 注意:即便是定义的时候 key 用的是 id 标识符,但在取值时也要用 string 类型下标参数或 expr 求值后为 string
|
||||
* 定义时 key 可以使用 id 标识符是为了书写方便,本质上仍然是 string
|
||||
*
|
||||
* 3:可创建空 map,如: #(map = {})
|
||||
*/
|
||||
public class Map extends Expr {
|
||||
|
||||
private LinkedHashMap<Object, Expr> map;
|
||||
|
||||
public Map(LinkedHashMap<Object, Expr> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
LinkedHashMap<Object, Object> valueMap = new LinkedHashMap<Object, Object>(map.size());
|
||||
for (Entry<Object, Expr> e : map.entrySet()) {
|
||||
valueMap.put(e.getKey(), e.getValue().eval(scope));
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
129
src/main/java/com/jfinal/template/expr/ast/Method.java
Normal file
129
src/main/java/com/jfinal/template/expr/ast/Method.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.InvocationTargetException;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Method : expr '.' ID '(' exprList? ')'
|
||||
*/
|
||||
public class Method extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public Method(Expr expr, String methodName, ExprList exprList, Location location) {
|
||||
if (exprList == null || exprList.length() == 0) {
|
||||
throw new ParseException("The parameter of method can not be blank", location);
|
||||
}
|
||||
init(expr, methodName, exprList, location);
|
||||
}
|
||||
|
||||
public Method(Expr expr, String methodName, Location location) {
|
||||
init(expr, methodName, ExprList.NULL_EXPR_LIST, location);
|
||||
}
|
||||
|
||||
private void init(Expr expr, String methodName, ExprList exprList, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The target for method invoking can not be blank", location);
|
||||
}
|
||||
if (MethodKit.isForbiddenMethod(methodName)) {
|
||||
throw new ParseException("Forbidden method: " + methodName, location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object target = expr.eval(scope);
|
||||
if (target == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The target for method invoking can not be null, method name: " + methodName, location);
|
||||
}
|
||||
|
||||
Object[] argValues = exprList.evalExprList(scope);
|
||||
MethodInfo methodInfo;
|
||||
try {
|
||||
methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
if (methodInfo == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException(buildMethodNotFoundSignature("Method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
|
||||
}
|
||||
|
||||
try {
|
||||
return methodInfo.invoke(target, argValues);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable t = e.getTargetException();
|
||||
if (t != null) {
|
||||
throw new TemplateException(t.getMessage(), location, t);
|
||||
} else {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
static String buildMethodNotFoundSignature(String preMsg, String methodName, Object[] argValues) {
|
||||
StringBuilder ret = new StringBuilder().append(preMsg).append(methodName).append("(");
|
||||
if (argValues != null) {
|
||||
for (int i = 0; i < argValues.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(argValues[i] != null ? argValues[i].getClass().getName() : "null");
|
||||
}
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
|
||||
/*
|
||||
public static Object invokeVarArgsMethod(java.lang.reflect.Method method, Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Class<?>[] paraTypes = method.getParameterTypes();
|
||||
Object[] finalArgValues = new Object[paraTypes.length];
|
||||
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
System.arraycopy(argValues, 0, finalArgValues, 0, fixedParaLength);
|
||||
Class<?> varParaComponentType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
Object varParaValues = Array.newInstance(varParaComponentType, argValues.length - fixedParaLength);
|
||||
int p = 0;
|
||||
for (int i=fixedParaLength; i<argValues.length; i++) {
|
||||
Array.set(varParaValues, p++, argValues[i]);
|
||||
}
|
||||
finalArgValues[paraTypes.length - 1] = varParaValues;
|
||||
return method.invoke(target, finalArgValues);
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
99
src/main/java/com/jfinal/template/expr/ast/MethodInfo.java
Normal file
99
src/main/java/com/jfinal/template/expr/ast/MethodInfo.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* MethodInfo
|
||||
*/
|
||||
public class MethodInfo {
|
||||
|
||||
protected final String 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) {
|
||||
this.key = key;
|
||||
this.clazz = clazz;
|
||||
this.method = method;
|
||||
this.isVarArgs = method.isVarArgs();
|
||||
this.paraTypes = method.getParameterTypes();
|
||||
}
|
||||
|
||||
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
if (isVarArgs) {
|
||||
return invokeVarArgsMethod(target, args);
|
||||
} else {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Object[] finalArgValues = new Object[paraTypes.length];
|
||||
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
System.arraycopy(argValues, 0, finalArgValues, 0, fixedParaLength);
|
||||
Class<?> varParaComponentType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
Object varParaValues = Array.newInstance(varParaComponentType, argValues.length - fixedParaLength);
|
||||
int p = 0;
|
||||
for (int i=fixedParaLength; i<argValues.length; i++) {
|
||||
Array.set(varParaValues, p++, argValues[i]);
|
||||
}
|
||||
finalArgValues[paraTypes.length - 1] = varParaValues;
|
||||
return method.invoke(target, finalArgValues);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(method.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return isVarArgs;
|
||||
}
|
||||
|
||||
protected Class<?>[] getParameterTypes() {
|
||||
return paraTypes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder ret = new StringBuilder(clazz.getName()).append(".").append(method.getName()).append("(");
|
||||
for (int i=0; i<paraTypes.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(paraTypes[i].getName());
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* MethodInfoExt 辅助实现 extension method 功能
|
||||
*/
|
||||
public class MethodInfoExt extends MethodInfo {
|
||||
|
||||
protected Object objectOfExtensionClass;
|
||||
|
||||
public MethodInfoExt(Object objectOfExtensionClass, String key, Class<?> clazz, Method method) {
|
||||
super(key, clazz, method);
|
||||
this.objectOfExtensionClass = objectOfExtensionClass;
|
||||
|
||||
// 将被 mixed 的类自身添加入参数类型数组的第一个位置
|
||||
// Class<?>[] newParaTypes = new Class<?>[paraTypes.length + 1];
|
||||
// newParaTypes[0] = clazz; // 第一个参数就是被 mixed 的类它自己
|
||||
// System.arraycopy(paraTypes, 0, newParaTypes, 1, paraTypes.length);
|
||||
// this.paraTypes = newParaTypes;
|
||||
}
|
||||
|
||||
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Object[] finalArgs = new Object[args.length + 1];
|
||||
finalArgs[0] = target;
|
||||
|
||||
if (args.length > 0) {
|
||||
System.arraycopy(args, 0, finalArgs, 1, args.length);
|
||||
}
|
||||
|
||||
if (isVarArgs) {
|
||||
return invokeVarArgsMethod(objectOfExtensionClass, finalArgs);
|
||||
} else {
|
||||
return method.invoke(objectOfExtensionClass, finalArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
393
src/main/java/com/jfinal/template/expr/ast/MethodKit.java
Normal file
393
src/main/java/com/jfinal/template/expr/ast/MethodKit.java
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* 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 java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
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;
|
||||
import com.jfinal.template.ext.extensionmethod.FloatExt;
|
||||
import com.jfinal.template.ext.extensionmethod.IntegerExt;
|
||||
import com.jfinal.template.ext.extensionmethod.LongExt;
|
||||
import com.jfinal.template.ext.extensionmethod.ShortExt;
|
||||
import com.jfinal.template.ext.extensionmethod.StringExt;
|
||||
|
||||
/**
|
||||
* MethodKit
|
||||
*/
|
||||
public class MethodKit {
|
||||
|
||||
private static final Class<?>[] NULL_ARG_TYPES = new Class<?>[0];
|
||||
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>();
|
||||
|
||||
// 初始化在模板中调用 method 时所在的被禁止使用类
|
||||
static {
|
||||
Class<?>[] cs = {
|
||||
System.class, Runtime.class, Thread.class, Class.class, ClassLoader.class, File.class,
|
||||
Compiler.class, InheritableThreadLocal.class, Package.class, Process.class,
|
||||
RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class
|
||||
};
|
||||
for (Class<?> c : cs) {
|
||||
forbiddenClasses.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化在模板中被禁止使用的 method name
|
||||
static {
|
||||
String[] ms = {
|
||||
"getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader",
|
||||
"getMethod", "getMethods", "getField", "getFields",
|
||||
"notify", "notifyAll", "wait",
|
||||
"load", "exit", "loadLibrary", "halt",
|
||||
"stop", "suspend", "resume", "setDaemon", "setPriority",
|
||||
};
|
||||
for (String m : ms) {
|
||||
forbiddenMethods.add(m);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 primitive type 与 boxed type 双向映射关系
|
||||
static {
|
||||
primitiveMap.put(byte.class, Byte.class);
|
||||
primitiveMap.put(short.class, Short.class);
|
||||
primitiveMap.put(int.class, Integer.class);
|
||||
primitiveMap.put(long.class, Long.class);
|
||||
primitiveMap.put(float.class, Float.class);
|
||||
primitiveMap.put(double.class, Double.class);
|
||||
primitiveMap.put(char.class, Character.class);
|
||||
primitiveMap.put(boolean.class, Boolean.class);
|
||||
|
||||
primitiveMap.put(Byte.class, byte.class);
|
||||
primitiveMap.put(Short.class, short.class);
|
||||
primitiveMap.put(Integer.class, int.class);
|
||||
primitiveMap.put(Long.class, long.class);
|
||||
primitiveMap.put(Float.class, float.class);
|
||||
primitiveMap.put(Double.class, double.class);
|
||||
primitiveMap.put(Character.class, char.class);
|
||||
primitiveMap.put(Boolean.class, boolean.class);
|
||||
}
|
||||
|
||||
public static boolean isForbiddenClass(Class<?> clazz) {
|
||||
return forbiddenClasses.contains(clazz);
|
||||
}
|
||||
|
||||
public static boolean isForbiddenMethod(String methodName) {
|
||||
return forbiddenMethods.contains(methodName);
|
||||
}
|
||||
|
||||
public static void addForbiddenMethod(String methodName) {
|
||||
forbiddenMethods.add(methodName);
|
||||
}
|
||||
|
||||
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
|
||||
Class<?>[] argTypes = getArgTypes(argValues);
|
||||
String 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);
|
||||
} else {
|
||||
// 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
|
||||
methodCache.put(key, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
return method instanceof MethodInfo ? (MethodInfo)method : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 getter 方法
|
||||
* 使用与 Field 相同的 key,避免生成两次 key值
|
||||
*/
|
||||
public static MethodInfo getGetterMethod(String 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);
|
||||
} else {
|
||||
methodCache.put(key, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null;
|
||||
}
|
||||
|
||||
static Class<?>[] getArgTypes(Object[] argValues) {
|
||||
if (argValues == null || argValues.length == 0) {
|
||||
return NULL_ARG_TYPES;
|
||||
}
|
||||
Class<?>[] argTypes = new Class<?>[argValues.length];
|
||||
for (int i=0; i<argValues.length; i++) {
|
||||
argTypes[i] = argValues[i] != null ? argValues[i].getClass() : null;
|
||||
}
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
private static MethodInfo doGetMethod(String key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
if (forbiddenClasses.contains(targetClass)) {
|
||||
throw new RuntimeException("Forbidden class: " + targetClass.getName());
|
||||
}
|
||||
// 仅开启 forbiddenClasses 检测
|
||||
// if (forbiddenMethods.contains(methodName)) {
|
||||
// throw new RuntimeException("Forbidden method: " + methodName);
|
||||
// }
|
||||
|
||||
Method[] methodArray = targetClass.getMethods();
|
||||
for (Method method : methodArray) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
Class<?>[] paraTypes = method.getParameterTypes();
|
||||
if (matchFixedArgTypes(paraTypes, argTypes)) { // 无条件优先匹配固定参数方法
|
||||
return new MethodInfo(key, targetClass, method);
|
||||
}
|
||||
if (method.isVarArgs() && matchVarArgTypes(paraTypes, argTypes)) {
|
||||
return new MethodInfo(key, targetClass, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean matchFixedArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {
|
||||
if (paraTypes.length != argTypes.length) {
|
||||
return false;
|
||||
}
|
||||
return matchRangeTypes(paraTypes, argTypes, paraTypes.length);
|
||||
}
|
||||
|
||||
private static boolean matchRangeTypes(Class<?>[] paraTypes, Class<?>[] argTypes, int matchLength) {
|
||||
for (int i=0; i<matchLength; i++) {
|
||||
if (argTypes[i] == null) {
|
||||
if (paraTypes[i].isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
if (paraTypes[i].isAssignableFrom(argTypes[i])) {
|
||||
continue ;
|
||||
}
|
||||
// object instanceof Xxx、Class.isAssignableFrom(Class)、Class.isInstance(Object) not works for primitive type
|
||||
if (paraTypes[i] == argTypes[i] || primitiveMap.get(paraTypes[i]) == argTypes[i]) {
|
||||
continue ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean matchVarArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
if (argTypes.length < fixedParaLength) {
|
||||
return false;
|
||||
}
|
||||
if (!matchRangeTypes(paraTypes, argTypes, fixedParaLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> varArgType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
for (int i=fixedParaLength; i<argTypes.length; i++) {
|
||||
if (argTypes[i] == null) {
|
||||
if (varArgType.isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
if (varArgType.isAssignableFrom(argTypes[i])) {
|
||||
continue ;
|
||||
}
|
||||
if (varArgType == argTypes[i] || primitiveMap.get(varArgType) == argTypes[i]) {
|
||||
continue ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法用于缓存的 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()));
|
||||
}
|
||||
|
||||
// 以下代码实现 extension method 功能 --------------------
|
||||
|
||||
// 添加 jfinal 官方扩展方法 extension method
|
||||
static {
|
||||
addExtensionMethod(String.class, new StringExt());
|
||||
addExtensionMethod(Integer.class, new IntegerExt());
|
||||
addExtensionMethod(Long.class, new LongExt());
|
||||
addExtensionMethod(Float.class, new FloatExt());
|
||||
addExtensionMethod(Double.class, new DoubleExt());
|
||||
addExtensionMethod(Short.class, new ShortExt());
|
||||
addExtensionMethod(Byte.class, new ByteExt());
|
||||
}
|
||||
|
||||
public synchronized static void addExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
Class<?> extensionClass = objectOfExtensionClass.getClass();
|
||||
java.lang.reflect.Method[] methodArray = extensionClass.getMethods();
|
||||
for (java.lang.reflect.Method method : methodArray) {
|
||||
Class<?> decClass = method.getDeclaringClass();
|
||||
if (decClass == Object.class) { // 考虑用于优化路由生成那段代码
|
||||
continue ;
|
||||
}
|
||||
|
||||
Class<?>[] extensionMethodParaTypes = method.getParameterTypes();
|
||||
String methodName = method.getName();
|
||||
if (extensionMethodParaTypes.length == 0) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("Extension method requires at least one argument: " + extensionClass.getName() + ".", methodName, extensionMethodParaTypes));
|
||||
}
|
||||
|
||||
// Extension method 第一个参数必须与当前对象的类型一致,在调用时会将当前对象自身传给扩展方法的第一个参数
|
||||
if (targetClass != extensionMethodParaTypes[0]) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("The first argument type of : " + extensionClass.getName() + ".", methodName, extensionMethodParaTypes) + " must be: " + targetClass.getName());
|
||||
}
|
||||
|
||||
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
|
||||
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
|
||||
|
||||
try {
|
||||
Method error = targetClass.getMethod(methodName, targetParaTypes);
|
||||
if (error != null) {
|
||||
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));
|
||||
if (methodCache.containsKey(key)) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("The extension method is already exists: " + extensionClass.getName() + ".", methodName, targetParaTypes));
|
||||
}
|
||||
|
||||
MethodInfoExt mie = new MethodInfoExt(objectOfExtensionClass, key, extensionClass/* targetClass */, method);
|
||||
methodCache.put(key, mie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
addExtensionMethod(targetClass, ReflectKit.newInstance(extensionClass));
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
Class<?> extensionClass = objectOfExtensionClass.getClass();
|
||||
java.lang.reflect.Method[] methodArray = extensionClass.getMethods();
|
||||
for (java.lang.reflect.Method method : methodArray) {
|
||||
Class<?> decClass = method.getDeclaringClass();
|
||||
if (decClass == Object.class) { // 考虑用于优化路由生成那段代码
|
||||
continue ;
|
||||
}
|
||||
|
||||
Class<?>[] extensionMethodParaTypes = method.getParameterTypes();
|
||||
String methodName = method.getName();
|
||||
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
|
||||
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
|
||||
|
||||
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
methodCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Class<?>, Class<?>> primitiveToBoxedMap = new HashMap<Class<?>, Class<?>>();
|
||||
|
||||
// 初始化 primitive type 到 boxed type 的映射
|
||||
static {
|
||||
primitiveToBoxedMap.put(byte.class, Byte.class);
|
||||
primitiveToBoxedMap.put(short.class, Short.class);
|
||||
primitiveToBoxedMap.put(int.class, Integer.class);
|
||||
primitiveToBoxedMap.put(long.class, Long.class);
|
||||
primitiveToBoxedMap.put(float.class, Float.class);
|
||||
primitiveToBoxedMap.put(double.class, Double.class);
|
||||
primitiveToBoxedMap.put(char.class, Character.class);
|
||||
primitiveToBoxedMap.put(boolean.class, Boolean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 由于从在模板中传递的基本数据类型参数只可能是 boxed 类型,当 extension method 中的方法参数是
|
||||
* primitive 类型时,在 getMethod(key) 时无法获取 addExtensionMethod(...) 注册的扩展方法
|
||||
* 所以为扩展方法调用 getMethodKey(...) 生成 key 时一律转成 boxed 类型去生成方法的 key 值
|
||||
*
|
||||
* 注意:该值仅用于在获取方法是通过 key 能获取到 MethindInfoExt,而 MethindInfoExt.paraType 仍然
|
||||
* 是原来的参数值
|
||||
*/
|
||||
private static Class<?>[] toBoxedType(Class<?>[] targetParaTypes) {
|
||||
int len = targetParaTypes.length;
|
||||
if (len == 0) {
|
||||
return targetParaTypes;
|
||||
}
|
||||
|
||||
Class<?>[] ret = new Class<?>[len];
|
||||
for (int i=0; i<len; i++) {
|
||||
Class<?> temp = primitiveToBoxedMap.get(targetParaTypes[i]);
|
||||
if (temp != null) {
|
||||
ret[i] = temp;
|
||||
} else {
|
||||
ret[i] = targetParaTypes[i];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
removeExtensionMethod(targetClass, ReflectKit.newInstance(extensionClass));
|
||||
}
|
||||
|
||||
private static String buildMethodSignatureForException(String preMsg, String methodName, Class<?>[] argTypes) {
|
||||
StringBuilder ret = new StringBuilder().append(preMsg).append(methodName).append("(");
|
||||
if (argTypes != null) {
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(argTypes[i] != null ? argTypes[i].getName() : "null");
|
||||
}
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
68
src/main/java/com/jfinal/template/expr/ast/NullSafe.java
Normal file
68
src/main/java/com/jfinal/template/expr/ast/NullSafe.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* NullSafe
|
||||
* 在原则上只支持具有动态特征的用法,例如:方法调用、字段取值、Map 与 List 取值
|
||||
* 而不支持具有静态特征的用法,例如:static method 调用、shared method 调用
|
||||
*
|
||||
* 用法:
|
||||
* #( seoTitle ?? "JFinal 极速开发社区" )
|
||||
* 支持级联: #( a.b.c ?? "JFinal 极速开发社区" )
|
||||
* 支持嵌套: #( a ?? b ?? c ?? d)
|
||||
*/
|
||||
public class NullSafe extends Expr {
|
||||
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public NullSafe(Expr left, Expr right, Location location) {
|
||||
if (left == null) {
|
||||
throw new ParseException("The expression on the left side of null coalescing and safe access operator \"??\" can not be blank", location);
|
||||
}
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
boolean oldNullSafeValue = ctrl.isNullSafe();
|
||||
|
||||
Object ret;
|
||||
try {
|
||||
ctrl.setNullSafe(true);
|
||||
ret = left.eval(scope);
|
||||
} finally {
|
||||
ctrl.setNullSafe(oldNullSafeValue);
|
||||
}
|
||||
|
||||
return ret == null && right != null ? right.eval(scope) : ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
96
src/main/java/com/jfinal/template/expr/ast/RangeArray.java
Normal file
96
src/main/java/com/jfinal/template/expr/ast/RangeArray.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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 java.util.AbstractList;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* RangeArray : [expr .. expr]
|
||||
*
|
||||
* 用法:
|
||||
* 1:[1..3]
|
||||
* 2:[3..1]
|
||||
*/
|
||||
public class RangeArray extends Expr {
|
||||
|
||||
private Expr start;
|
||||
private Expr end;
|
||||
|
||||
/**
|
||||
* array : '[' exprList ? | range ? ']'
|
||||
* exprList : expr (',' expr)*
|
||||
* range : expr .. expr
|
||||
*/
|
||||
public RangeArray(Expr start, Expr end, Location location) {
|
||||
if (start == null) {
|
||||
throw new ParseException("The start value of range array can not be blank", location);
|
||||
}
|
||||
if (end == null) {
|
||||
throw new ParseException("The end value of range array can not be blank", location);
|
||||
}
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object startValue = start.eval(scope);
|
||||
if ( !(startValue instanceof Integer) ) {
|
||||
throw new TemplateException("The start value of range array must be Integer", location);
|
||||
}
|
||||
Object endValue = end.eval(scope);
|
||||
if ( !(endValue instanceof Integer) ) {
|
||||
throw new TemplateException("The end value of range array must be Integer", location);
|
||||
}
|
||||
|
||||
return new RangeList((Integer)startValue, (Integer)endValue, location);
|
||||
}
|
||||
|
||||
public static class RangeList extends AbstractList<Integer> {
|
||||
|
||||
final int start;
|
||||
final int size;
|
||||
final int increment;
|
||||
final Location location;
|
||||
|
||||
public RangeList(int start, int end, Location location) {
|
||||
this.start = start;
|
||||
this.increment = start <= end ? 1 : -1;
|
||||
this.size = Math.abs(end - start) + 1;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Integer get(int index) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new TemplateException("Index out of bounds. Index: " + index + ", Size: " + size, location);
|
||||
}
|
||||
return start + index * increment;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
67
src/main/java/com/jfinal/template/expr/ast/SharedMethod.java
Normal file
67
src/main/java/com/jfinal/template/expr/ast/SharedMethod.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.SharedMethodKit.SharedMethodInfo;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* SharedMethod
|
||||
*
|
||||
* 用法:
|
||||
* engine.addSharedMethod(object);
|
||||
* engine.addSharedStaticMethod(Xxx.class);
|
||||
* #(method(para))
|
||||
*/
|
||||
public class SharedMethod extends Expr {
|
||||
|
||||
private SharedMethodKit sharedMethodKit;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public SharedMethod(SharedMethodKit sharedMethodKit, String methodName, ExprList exprList, Location location) {
|
||||
if (MethodKit.isForbiddenMethod(methodName)) {
|
||||
throw new ParseException("Forbidden method: " + methodName, location);
|
||||
}
|
||||
this.sharedMethodKit = sharedMethodKit;
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object[] argValues = exprList.evalExprList(scope);
|
||||
SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
|
||||
|
||||
// ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法
|
||||
if (sharedMethodInfo == null) {
|
||||
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
|
||||
}
|
||||
try {
|
||||
return sharedMethodInfo.invoke(argValues);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
173
src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java
Normal file
173
src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import com.jfinal.kit.ReflectKit;
|
||||
|
||||
/**
|
||||
* SharedMethodKit
|
||||
*/
|
||||
public class SharedMethodKit {
|
||||
|
||||
private static final Set<String> excludedMethodKey = new HashSet<String>();
|
||||
|
||||
static {
|
||||
Method[] methods = Object.class.getMethods();
|
||||
for (Method method : methods) {
|
||||
String 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>();
|
||||
|
||||
public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) {
|
||||
Class<?>[] argTypes = MethodKit.getArgTypes(argValues);
|
||||
String key = getSharedMethodKey(methodName, argTypes);
|
||||
SharedMethodInfo method = methodCache.get(key);
|
||||
if (method == null) {
|
||||
method = doGetSharedMethodInfo(methodName, argTypes);
|
||||
if (method != null) {
|
||||
methodCache.putIfAbsent(key, method);
|
||||
}
|
||||
// shared method 不支持 null safe,不缓存: methodCache.put(key, Boolean.FALSE)
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private SharedMethodInfo doGetSharedMethodInfo(String methodName, Class<?>[] argTypes) {
|
||||
for (SharedMethodInfo smi : sharedMethodList) {
|
||||
if (smi.getName().equals(methodName)) {
|
||||
Class<?>[] paraTypes = smi.getParameterTypes();
|
||||
if (MethodKit.matchFixedArgTypes(paraTypes, argTypes)) { // 无条件优先匹配固定参数方法
|
||||
return smi;
|
||||
}
|
||||
if (smi.isVarArgs() && MethodKit.matchVarArgTypes(paraTypes, argTypes)) {
|
||||
return smi;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addSharedMethod(Object sharedMethodFromObject) {
|
||||
addSharedMethod(sharedMethodFromObject.getClass(), sharedMethodFromObject);
|
||||
}
|
||||
|
||||
public void addSharedMethod(Class<?> sharedMethodFromClass) {
|
||||
addSharedMethod(sharedMethodFromClass, ReflectKit.newInstance(sharedMethodFromClass));
|
||||
}
|
||||
|
||||
public void addSharedStaticMethod(Class<?> sharedStaticMethodFromClass) {
|
||||
addSharedMethod(sharedStaticMethodFromClass, null);
|
||||
}
|
||||
|
||||
public void removeSharedMethod(String methodName) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
if (it.next().getName().equals(methodName)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSharedMethod(Class<?> sharedClass) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
if (it.next().getClazz() == sharedClass) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSharedMethod(Method method) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
SharedMethodInfo current = it.next();
|
||||
String methodName = method.getName();
|
||||
if (current.getName().equals(methodName)) {
|
||||
String key = getSharedMethodKey(methodName, method.getParameterTypes());
|
||||
if (current.getKey().equals(key)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void addSharedMethod(Class<?> sharedClass, Object target) {
|
||||
if (MethodKit.isForbiddenClass(sharedClass)) {
|
||||
throw new IllegalArgumentException("Forbidden class: " + sharedClass.getName());
|
||||
}
|
||||
|
||||
Method[] methods = sharedClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
if (excludedMethodKey.contains(key)) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
for (SharedMethodInfo smi : sharedMethodList) {
|
||||
if (smi.getKey().equals(key)) {
|
||||
throw new RuntimeException("The shared method is already exists : " + smi.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
sharedMethodList.add(new SharedMethodInfo(key, sharedClass, method, target));
|
||||
} else if (Modifier.isStatic(method.getModifiers())) { // target 为 null 时添加 static method
|
||||
sharedMethodList.add(new SharedMethodInfo(key, sharedClass, method, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
static class SharedMethodInfo extends MethodInfo {
|
||||
final Object target;
|
||||
|
||||
private SharedMethodInfo(String key, Class<?> clazz, Method method, Object target) {
|
||||
super(key, clazz, method);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
return super.invoke(target, args);
|
||||
}
|
||||
|
||||
Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
65
src/main/java/com/jfinal/template/expr/ast/StaticField.java
Normal file
65
src/main/java/com/jfinal/template/expr/ast/StaticField.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* StaticField : ID_list '::' ID
|
||||
* 动态获取静态变量值,变量值改变时仍可正确获取
|
||||
* 用法:com.jfinal.core.Const::JFINAL_VERSION
|
||||
*/
|
||||
public class StaticField extends Expr {
|
||||
|
||||
private Class<?> clazz;
|
||||
private String fieldName;
|
||||
private Field field;
|
||||
|
||||
public StaticField(String className, String fieldName, Location location) {
|
||||
try {
|
||||
this.clazz = Class.forName(className);
|
||||
this.fieldName = fieldName;
|
||||
this.field = clazz.getField(fieldName);
|
||||
this.location = location;
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
try {
|
||||
return field.get(null);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return clazz.getName() + "::" + fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
85
src/main/java/com/jfinal/template/expr/ast/StaticMethod.java
Normal file
85
src/main/java/com/jfinal/template/expr/ast/StaticMethod.java
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* StaticMethod : ID_list : '::' ID '(' exprList? ')'
|
||||
* 用法: com.jfinal.kit.Str::isBlank("abc")
|
||||
*/
|
||||
public class StaticMethod extends Expr {
|
||||
|
||||
private Class<?> clazz;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public StaticMethod(String className, String methodName, Location location) {
|
||||
init(className, methodName, ExprList.NULL_EXPR_LIST, location);
|
||||
}
|
||||
|
||||
public StaticMethod(String className, String methodName, ExprList exprList, Location location) {
|
||||
if (exprList == null || exprList.length() == 0) {
|
||||
throw new ParseException("exprList can not be blank", location);
|
||||
}
|
||||
init(className, methodName, exprList, location);
|
||||
}
|
||||
|
||||
private void init(String className, String methodName, ExprList exprList, Location location) {
|
||||
try {
|
||||
this.clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ParseException("Class not found: " + className, location, e);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object[] argValues = exprList.evalExprList(scope);
|
||||
MethodInfo methodInfo;
|
||||
try {
|
||||
methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
|
||||
// StaticMethod 是固定的存在,不支持 null safe,null safe 只支持具有动态特征的用法
|
||||
if (methodInfo == null) {
|
||||
throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
|
||||
}
|
||||
if (!methodInfo.isStatic()) {
|
||||
throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location);
|
||||
}
|
||||
|
||||
try {
|
||||
return methodInfo.invoke(null, argValues);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
56
src/main/java/com/jfinal/template/expr/ast/Ternary.java
Normal file
56
src/main/java/com/jfinal/template/expr/ast/Ternary.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Ternary
|
||||
*/
|
||||
public class Ternary extends Expr {
|
||||
|
||||
private Expr cond;
|
||||
private Expr exprOne;
|
||||
private Expr exprTwo;
|
||||
|
||||
/**
|
||||
* cond ? exprOne : exprTwo
|
||||
*/
|
||||
public Ternary(Expr cond, Expr exprOne, Expr exprTwo, Location location) {
|
||||
if (cond == null || exprOne == null || exprTwo == null) {
|
||||
throw new ParseException("The parameter of ternary expression can not be blank", location);
|
||||
}
|
||||
this.cond = cond;
|
||||
this.exprOne = exprOne;
|
||||
this.exprTwo = exprTwo;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
return Logic.isTrue(cond.eval(scope)) ? exprOne.eval(scope) : exprTwo.eval(scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
91
src/main/java/com/jfinal/template/expr/ast/Unary.java
Normal file
91
src/main/java/com/jfinal/template/expr/ast/Unary.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* unary : ('!' | '+' | '-'| '++' | '--') expr
|
||||
*
|
||||
* 只支持 +expr 与 -expr
|
||||
* !expr、 ++expr、 --expr 分别由 Logic、IncDec 支持
|
||||
*/
|
||||
public class Unary extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr expr;
|
||||
|
||||
public Unary(Sym op, Expr expr, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The parameter of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.expr = expr;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* unary : ('!' | '+' | '-'| '++' | '--') expr
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
Object value = expr.eval(scope);
|
||||
if (value == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The parameter of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
if (! (value instanceof Number) ) {
|
||||
throw new TemplateException(op.value() + " operator only support int long float double BigDecimal type", location);
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case ADD:
|
||||
return value;
|
||||
case SUB:
|
||||
Number n = (Number)value;
|
||||
if (n instanceof Integer) {
|
||||
return Integer.valueOf(-n.intValue());
|
||||
}
|
||||
if (n instanceof Long) {
|
||||
return Long.valueOf(-n.longValue());
|
||||
}
|
||||
if (n instanceof Float) {
|
||||
return Float.valueOf(-n.floatValue());
|
||||
}
|
||||
if (n instanceof Double) {
|
||||
return Double.valueOf(-n.doubleValue());
|
||||
}
|
||||
if (n instanceof BigDecimal) {
|
||||
return ((BigDecimal)n).negate();
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + n.getClass().getName(), location);
|
||||
default :
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
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.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 不带参时,按默认 pattern 输出当前日期
|
||||
*
|
||||
* #date() 指令支持无参时获取当前指令,第一个参数 string 当成是 pattern
|
||||
*
|
||||
* 日期输出指令,第一个参数是被输出的 java.util.Date 对象或其子类对象
|
||||
* 无第二个参数时按默认 patter 输出,第二个参数为 expr 表达式,表示 pattern
|
||||
* 第二个为 date 时,表示当第一个为 null 时的默认值
|
||||
*/
|
||||
public class DateDirective extends Directive {
|
||||
|
||||
private Expr valueExpr;
|
||||
private Expr datePatternExpr;
|
||||
private int paraNum;
|
||||
|
||||
public void setExprList(ExprList exprList) {
|
||||
this.paraNum = exprList.length();
|
||||
if (paraNum > 2) {
|
||||
throw new ParseException("Wrong number parameter of #date directive, two parameters allowed at most", location);
|
||||
}
|
||||
|
||||
if (paraNum == 0) {
|
||||
this.valueExpr = null;
|
||||
this.datePatternExpr = null;
|
||||
} else if (paraNum == 1) {
|
||||
this.valueExpr = exprList.getExprArray()[0];
|
||||
this.datePatternExpr = null;
|
||||
} else if (paraNum == 2) {
|
||||
this.valueExpr = exprList.getExprArray()[0];
|
||||
this.datePatternExpr = exprList.getExprArray()[1];
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
if (paraNum == 0) {
|
||||
outputToday(env, writer);
|
||||
} else if (paraNum == 1) {
|
||||
outputWithoutDatePattern(env, scope, writer);
|
||||
} else if (paraNum == 2) {
|
||||
outputWithDatePattern(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void outputToday(Env env, Writer writer) {
|
||||
Object value = format(new java.util.Date(), env.getEngineConfig().getDatePattern());
|
||||
write(writer, value.toString());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
private void outputWithDatePattern(Env env, Scope scope, Writer writer) {
|
||||
Object value = valueExpr.eval(scope);
|
||||
if (value == null) {
|
||||
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());
|
||||
}
|
||||
|
||||
private String format(Object value, String datePattern) {
|
||||
try {
|
||||
return new SimpleDateFormat(datePattern).format(value);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Escape 对字符串进行转义
|
||||
* 用法:
|
||||
* #escape(value)
|
||||
*/
|
||||
public class EscapeDirective extends Directive {
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Object value = exprList.eval(scope);
|
||||
if (value != null) {
|
||||
write(writer, escape(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 挪到 StrKit 中
|
||||
private String escape(String str) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
int len = str.length();
|
||||
StringBuilder ret = new StringBuilder(len * 2);
|
||||
for (int i = 0; i < len; i++) {
|
||||
char cur = str.charAt(i);
|
||||
switch (cur) {
|
||||
case '<':
|
||||
ret.append("<");
|
||||
break;
|
||||
case '>':
|
||||
ret.append(">");
|
||||
break;
|
||||
case '\"':
|
||||
ret.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
ret.append("'"); // IE 不支持 ' 考虑 '
|
||||
break;
|
||||
case '&':
|
||||
ret.append("&");
|
||||
break;
|
||||
default:
|
||||
ret.append(cur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
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.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 输出当前时间,默认考虑是输出时间,给 pattern 输出可能是 Date、DateTime、Timestamp
|
||||
* 带 String 参数,表示 pattern
|
||||
*/
|
||||
public class NowDirective extends Directive {
|
||||
|
||||
public void setExrpList(ExprList exprList) {
|
||||
if (exprList.length() > 1) {
|
||||
throw new ParseException("#now directive support one parameter only", location);
|
||||
}
|
||||
super.setExprList(exprList);
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
String dataPattern;
|
||||
if (exprList.length() == 0) {
|
||||
dataPattern = env.getEngineConfig().getDatePattern();
|
||||
} else {
|
||||
Object dp = exprList.eval(scope);
|
||||
if (dp instanceof String) {
|
||||
dataPattern = (String)dp;
|
||||
} else {
|
||||
throw new TemplateException("The parameter of #new directive must be String", location);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String value = new SimpleDateFormat(dataPattern).format(new java.util.Date());
|
||||
write(writer, value);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 输出随机数
|
||||
*/
|
||||
public class RandomDirective extends Directive {
|
||||
|
||||
private java.util.Random random = new java.util.Random();
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
write(writer, String.valueOf(random.nextInt()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
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.source.ISource;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Parser;
|
||||
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;
|
||||
|
||||
/**
|
||||
* #render 指令用于动态渲染子模板,作为 include 指令的补充
|
||||
*
|
||||
* <pre>
|
||||
* 两种用法:
|
||||
* 1:只传入一个参数,参数可以是 String 常量,也可以是任意表达式
|
||||
* #render("_hot.html")
|
||||
* #render(subFile)
|
||||
*
|
||||
* 2:传入任意多个参数,除第一个参数以外的所有参数必须是赋值表达式,用于实现参数传递功能
|
||||
* #render("_hot.html", title = "热门新闻", list = newsList)
|
||||
*
|
||||
* 上例中传递了 title、list 两个参数,可以代替父模板中的 #set 指令传参方式
|
||||
* 并且此方式传入的参数只在子模板作用域有效,不会污染父模板作用域
|
||||
*
|
||||
* 这种传参方式有利于将子模板模块化,例如上例的调用改成如下的参数:
|
||||
* #render("_hot.html", title = "热门项目", list = projectList)
|
||||
* 通过这种传参方式在子模板 _hot.html 之中,完全不需要修改对于 title 与 list
|
||||
* 这两个变量的处理代码,就实现了对 “热门项目” 数据的渲染
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class RenderDirective extends Directive {
|
||||
|
||||
private String parentFileName;
|
||||
private Map<String, StatInfo> statInfoCache = new HashMap<String,StatInfo>();
|
||||
|
||||
public void setExprList(ExprList exprList) {
|
||||
int len = exprList.length();
|
||||
if (len == 0) {
|
||||
throw new ParseException("The parameter of #render directive can not be blank", location);
|
||||
}
|
||||
if (len > 1) {
|
||||
for (int i = 1; i < len; i++) {
|
||||
if (!(exprList.getExpr(i) instanceof Assign)) {
|
||||
throw new ParseException("The " + i + "th parameter of #render directive must be an assignment expression", location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 location 中获取父模板的 fileName,用于生成 subFileName
|
||||
* 如果是孙子模板,那么 parentFileName 为最顶层的模板,而非直接上层的模板
|
||||
*/
|
||||
this.parentFileName = location.getTemplateFile();
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 exprList 进行求值,并将第一个表达式的值作为模板名称返回,
|
||||
* 开启 local assignment 保障 #render 指令参数表达式列表
|
||||
* 中的赋值表达式在当前 scope 中进行,有利于模块化
|
||||
*/
|
||||
private Object evalAssignExpressionAndGetFileName(Scope scope) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setLocalAssignment();
|
||||
return exprList.evalExprList(scope)[0];
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
// 在 exprList.eval(scope) 之前创建,使赋值表达式在本作用域内进行
|
||||
scope = new Scope(scope);
|
||||
|
||||
Object value = evalAssignExpressionAndGetFileName(scope);
|
||||
if (!(value instanceof String)) {
|
||||
throw new TemplateException("The parameter value of #render directive must be String", location);
|
||||
}
|
||||
|
||||
String subFileName = Include.getSubFileName((String)value, parentFileName);
|
||||
StatInfo statInfo = statInfoCache.get(subFileName);
|
||||
if (statInfo == null) {
|
||||
statInfo = parseStatInfo(env, subFileName);
|
||||
statInfoCache.put(subFileName, statInfo);
|
||||
} else if (env.getEngineConfig().isDevMode()) {
|
||||
// statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载
|
||||
if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) {
|
||||
statInfo = parseStatInfo(env, subFileName);
|
||||
statInfoCache.put(subFileName, statInfo);
|
||||
}
|
||||
}
|
||||
|
||||
statInfo.stat.exec(statInfo.env, scope, writer);
|
||||
scope.getCtrl().setJumpNone();
|
||||
}
|
||||
|
||||
private StatInfo parseStatInfo(Env env, String subFileName) {
|
||||
EngineConfig config = env.getEngineConfig();
|
||||
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||||
ISource fileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||||
|
||||
try {
|
||||
EnvSub envSub = new EnvSub(env);
|
||||
Stat stat = new Parser(envSub, fileSource.getContent(), subFileName).parse();
|
||||
return new StatInfo(envSub, stat, fileSource);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatInfo {
|
||||
EnvSub env;
|
||||
Stat stat;
|
||||
ISource source;
|
||||
|
||||
StatInfo(EnvSub env, Stat stat, ISource source) {
|
||||
this.env = env;
|
||||
this.stat = stat;
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EnvSub 用于将子模板与父模板中的模板函数隔离开来,
|
||||
* 否则在子模板被修改并被重新解析时会再次添加子模板中的
|
||||
* 模板函数,从而抛出异常
|
||||
*
|
||||
* EnvSub 也可以使子模板中定义的模板函数不与上层产生冲突,
|
||||
* 有利于动态型模板渲染的模块化
|
||||
*
|
||||
* 注意: #render 子模板中定义的模板函数无法被上层调用
|
||||
*/
|
||||
private static class EnvSub extends Env {
|
||||
Env parentEnv;
|
||||
|
||||
public EnvSub(Env parentEnv) {
|
||||
super(parentEnv.getEngineConfig());
|
||||
this.parentEnv = parentEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接管父类 getFunction(),先从子模板中找模板函数,找不到再去父模板中找
|
||||
*/
|
||||
public Define getFunction(String functionName) {
|
||||
Define func = functionMap.get(functionName);
|
||||
return func != null ? func : parentEnv.getFunction(functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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.io.Writer;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.FastStringWriter;
|
||||
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.expr.ast.Id;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* #string 指令方便定义大量的多行文本变量,这个是 java 语言中极为需要的功能
|
||||
*
|
||||
* 定义:
|
||||
* #string(name)
|
||||
* 在此是大量的字符串
|
||||
* #end
|
||||
*
|
||||
* 使用:
|
||||
* #(name)
|
||||
*/
|
||||
public class StringDirective extends Directive {
|
||||
|
||||
private String name;
|
||||
private boolean isLocalAssignment = false;
|
||||
|
||||
public void setExprList(ExprList exprList) {
|
||||
Expr[] exprArray = exprList.getExprArray();
|
||||
if (exprArray.length == 0) {
|
||||
throw new ParseException("#string directive parameter cant not be null", location);
|
||||
}
|
||||
if (exprArray.length > 2) {
|
||||
throw new ParseException("wrong number of #string directive parameter, two parameters allowed at most", location);
|
||||
}
|
||||
|
||||
if (!(exprArray[0] instanceof Id)) {
|
||||
throw new ParseException("#string first parameter must be identifier", location);
|
||||
}
|
||||
this.name = ((Id)exprArray[0]).getId();
|
||||
if (exprArray.length == 2) {
|
||||
if (exprArray[1] instanceof Const) {
|
||||
if (((Const)exprArray[1]).isBoolean()) {
|
||||
this.isLocalAssignment = ((Const)exprArray[1]).getBoolean();
|
||||
} else {
|
||||
throw new ParseException("#string sencond parameter must be boolean", location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
FastStringWriter fsw = new FastStringWriter();
|
||||
stat.exec(env, scope, fsw);
|
||||
|
||||
if (this.isLocalAssignment) {
|
||||
scope.setLocal(name, fsw.toString());
|
||||
} else {
|
||||
scope.set(name, fsw.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hasEnd() 方法返回 true 时,表示该指令拥有指令体以及 #end 结束块
|
||||
* 模板引擎在解析时会将 "指令体" 赋值到 stat 属性中,在 exec(...) 方法中
|
||||
* 可通过 stat.exec(...) 执行 "指令体" 内部的所有指令
|
||||
*/
|
||||
public boolean hasEnd() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Byte 的扩展方法
|
||||
*
|
||||
* 用法:
|
||||
* #if(value.toInt() == 123)
|
||||
*/
|
||||
public class ByteExt {
|
||||
|
||||
public Boolean toBoolean(Byte self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Byte self) {
|
||||
return self.intValue();
|
||||
}
|
||||
|
||||
public Long toLong(Byte self) {
|
||||
return self.longValue();
|
||||
}
|
||||
|
||||
public Float toFloat(Byte self) {
|
||||
return self.floatValue();
|
||||
}
|
||||
|
||||
public Double toDouble(Byte self) {
|
||||
return self.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Double 的扩展方法
|
||||
*
|
||||
* 用法:
|
||||
* #if(value.toInt() == 123)
|
||||
*/
|
||||
public class DoubleExt {
|
||||
|
||||
public Boolean toBoolean(Double self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Double self) {
|
||||
return self.intValue();
|
||||
}
|
||||
|
||||
public Long toLong(Double self) {
|
||||
return self.longValue();
|
||||
}
|
||||
|
||||
public Float toFloat(Double self) {
|
||||
return self.floatValue();
|
||||
}
|
||||
|
||||
public Double toDouble(Double self) {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Float 的扩展方法
|
||||
*
|
||||
* 用法:
|
||||
* #if(value.toInt() == 123)
|
||||
*/
|
||||
public class FloatExt {
|
||||
|
||||
public Boolean toBoolean(Float self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Float self) {
|
||||
return self.intValue();
|
||||
}
|
||||
|
||||
public Long toLong(Float self) {
|
||||
return self.longValue();
|
||||
}
|
||||
|
||||
public Float toFloat(Float self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
public Double toDouble(Float self) {
|
||||
return self.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Integer 的扩展方法
|
||||
*
|
||||
* 重要用途:
|
||||
* Controller.keepPara() 方法会将所有类型的数据当成 String 并传回到
|
||||
* 到模板中,所以模板中的如下代码将无法工作:
|
||||
* #if(age > 18)
|
||||
* ....
|
||||
* #end
|
||||
*
|
||||
* 以上代码,第一次渲染模板时,由于 age 为 int 类型,那么 if 语句中是正确的表达式,
|
||||
* 当提交表单后在后端调用 keepPara() 以后 age 变成了 String 类型,表达式错误,
|
||||
* 在有了扩展方法以后,解决办法如下:
|
||||
* #if(age.toInt() > 18)
|
||||
* ...
|
||||
* #end
|
||||
* 如上所示,无论 age 是 String 还是 int 型,调用其 toInt() 方法将一直确保
|
||||
* age 为 int 类型
|
||||
*
|
||||
* 以上用法,必须针对 String 与 Integer 同时扩展一个 toInt() 方法,模板表达式中的
|
||||
* 变量为 String 或为 Integer 时都存在 toInt() 方法可供调用
|
||||
*
|
||||
*
|
||||
* 用法:
|
||||
* #if(age.toInt() > 18)
|
||||
*/
|
||||
public class IntegerExt {
|
||||
|
||||
public Boolean toBoolean(Integer self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Integer self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
public Long toLong(Integer self) {
|
||||
return self.longValue();
|
||||
}
|
||||
|
||||
public Float toFloat(Integer self) {
|
||||
return self.floatValue();
|
||||
}
|
||||
|
||||
public Double toDouble(Integer self) {
|
||||
return self.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Long 的扩展方法
|
||||
*
|
||||
* 用法:
|
||||
* #if(value.toInt() == 123)
|
||||
*/
|
||||
public class LongExt {
|
||||
|
||||
public Boolean toBoolean(Long self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Long self) {
|
||||
return self.intValue();
|
||||
}
|
||||
|
||||
public Long toLong(Long self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
public Float toFloat(Long self) {
|
||||
return self.floatValue();
|
||||
}
|
||||
|
||||
public Double toDouble(Long self) {
|
||||
return self.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.extensionmethod;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.Short 的扩展方法
|
||||
*
|
||||
* 用法:
|
||||
* #if(value.toInt() == 123)
|
||||
*/
|
||||
public class ShortExt {
|
||||
|
||||
public Boolean toBoolean(Short self) {
|
||||
return self != 0;
|
||||
}
|
||||
|
||||
public Integer toInt(Short self) {
|
||||
return self.intValue();
|
||||
}
|
||||
|
||||
public Long toLong(Short self) {
|
||||
return self.longValue();
|
||||
}
|
||||
|
||||
public Float toFloat(Short self) {
|
||||
return self.floatValue();
|
||||
}
|
||||
|
||||
public Double toDouble(Short self) {
|
||||
return self.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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.ext.extensionmethod;
|
||||
|
||||
import com.jfinal.kit.StrKit;
|
||||
|
||||
/**
|
||||
* 针对 java.lang.String 的扩展方法
|
||||
*
|
||||
* 重要用途:
|
||||
* Controller.keepPara() 方法会将所有类型的数据当成 String 并传回到
|
||||
* 到模板中,所以模板中的如下代码将无法工作:
|
||||
* #if(age > 18)
|
||||
* ....
|
||||
* #end
|
||||
*
|
||||
* 以上代码,第一次渲染模板时,由于 age 为 int 类型,那么 if 语句中是正确的表达式,
|
||||
* 当提交表单后在后端调用 keepPara() 以后 age 变成了 String 类型,表达式错误,
|
||||
* 在有了扩展方法以后,解决办法如下:
|
||||
* #if(age.toInt() > 18)
|
||||
* ...
|
||||
* #end
|
||||
* 如上所示,无论 age 是 String 还是 int 型,调用其 toInt() 方法将一直确保
|
||||
* age 为 int 类型
|
||||
*
|
||||
* 以上用法,必须针对 String 与 Integer 同时扩展一个 toInt() 方法,模板表达式中的
|
||||
* 变量为 String 或为 Integer 时都存在 toInt() 方法可供调用
|
||||
*
|
||||
* 用法:
|
||||
* #if(age.toInt() > 18)
|
||||
*/
|
||||
public class StringExt {
|
||||
|
||||
/**
|
||||
* StringExt.toBoolean() 是数据类型转换,所以与 Logic.isTrue(String)
|
||||
* 中的逻辑不同,后者只要 String 值非 null 并且 length() > 0 即返回 true
|
||||
*/
|
||||
public Boolean toBoolean(String self) {
|
||||
if (StrKit.isBlank(self)) {
|
||||
return null; // return Boolean.FALSE;
|
||||
}
|
||||
|
||||
String value = self.trim().toLowerCase();
|
||||
if ("true".equals(value) || "1".equals(value)) { // 未来考虑 "yes"、"on"
|
||||
return Boolean.TRUE;
|
||||
} else if ("false".equals(value) || "0".equals(value)) {
|
||||
return Boolean.FALSE;
|
||||
} else {
|
||||
throw new RuntimeException("Can not parse to boolean type of value: \"" + self + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
public Integer toInt(String self) {
|
||||
return StrKit.isBlank(self) ? null : Integer.parseInt(self);
|
||||
}
|
||||
|
||||
public Long toLong(String self) {
|
||||
return StrKit.isBlank(self) ? null : Long.parseLong(self);
|
||||
}
|
||||
|
||||
public Float toFloat(String self) {
|
||||
return StrKit.isBlank(self) ? null : Float.parseFloat(self);
|
||||
}
|
||||
|
||||
public Double toDouble(String self) {
|
||||
return StrKit.isBlank(self) ? null : Double.parseDouble(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
166
src/main/java/com/jfinal/template/ext/spring/JFinalView.java
Normal file
166
src/main/java/com/jfinal/template/ext/spring/JFinalView.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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.spring;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import org.springframework.web.servlet.view.AbstractTemplateView;
|
||||
|
||||
/**
|
||||
* JFinalView
|
||||
*
|
||||
* <pre>
|
||||
* 关键设置:
|
||||
* 1:setContentType("text/html;charset=UTF-8") 设置 content type 字符集为 UTF-8
|
||||
*
|
||||
* 2:setExposeRequestAttributes(true) 设置将 request 中的属性值注入到 model 中去
|
||||
* 便于在模板中使用 #(value) 访问 request.setAttribute(...) 进去的值
|
||||
*
|
||||
* 3: setExposeSessionAttributes(true) 设置将 session 中的属性值注入到 model 中去
|
||||
* 使用在模板中使用 #(value) 访问 session.setAttribute(...) 进去的值
|
||||
*
|
||||
* 注意:JFinalViewResolver.setSessionInView(true) 中的配置与
|
||||
* JFinalView.setExposeSessionAttributes(true) 可实现
|
||||
* 相似的功能,区别在于前者访问方式为 #(session.value) 而后者为
|
||||
* #(value),两种配置只选其一
|
||||
* </pre>
|
||||
*/
|
||||
public class JFinalView extends AbstractTemplateView {
|
||||
|
||||
@Override
|
||||
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
if (JFinalViewResolver.sessionInView) {
|
||||
HttpSession hs = request.getSession(JFinalViewResolver.createSession);
|
||||
if (hs != null) {
|
||||
model.put("session", new InnerSession(hs));
|
||||
}
|
||||
}
|
||||
|
||||
Writer writer = response.getWriter();
|
||||
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, writer);
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
|
||||
class InnerSession extends HashMap<Object, Object> implements HttpSession {
|
||||
|
||||
private static final long serialVersionUID = -8679493647540628009L;
|
||||
private HttpSession session;
|
||||
|
||||
public InnerSession(HttpSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
// HashMap 相关方法处理 ----------------------------------------------------
|
||||
/**
|
||||
* 覆盖 HashMap 的 put
|
||||
*/
|
||||
public Object put(Object name, Object value) {
|
||||
session.setAttribute((String)name, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖 HashMap 的 get
|
||||
*/
|
||||
public Object get(Object name) {
|
||||
return session.getAttribute((String)name);
|
||||
}
|
||||
|
||||
// Session 相关方法处理 ----------------------------------------------------
|
||||
public Object getAttribute(String key) {
|
||||
return session.getAttribute(key);
|
||||
}
|
||||
|
||||
public Enumeration getAttributeNames() {
|
||||
return session.getAttributeNames();
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return session.getCreationTime();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return session.getId();
|
||||
}
|
||||
|
||||
public long getLastAccessedTime() {
|
||||
return session.getLastAccessedTime();
|
||||
}
|
||||
|
||||
public int getMaxInactiveInterval() {
|
||||
return session.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
public ServletContext getServletContext() {
|
||||
return session.getServletContext();
|
||||
}
|
||||
|
||||
public javax.servlet.http.HttpSessionContext getSessionContext() {
|
||||
return session.getSessionContext();
|
||||
}
|
||||
|
||||
public Object getValue(String key) {
|
||||
return session.getValue(key);
|
||||
}
|
||||
|
||||
public String[] getValueNames() {
|
||||
return session.getValueNames();
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return session.isNew();
|
||||
}
|
||||
|
||||
public void putValue(String key, Object value) {
|
||||
session.putValue(key, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String key) {
|
||||
session.removeAttribute(key);
|
||||
}
|
||||
|
||||
public void removeValue(String key) {
|
||||
session.removeValue(key);
|
||||
}
|
||||
|
||||
public void setAttribute(String key, Object value) {
|
||||
session.setAttribute(key, value);
|
||||
}
|
||||
|
||||
public void setMaxInactiveInterval(int maxInactiveInterval) {
|
||||
session.setMaxInactiveInterval(maxInactiveInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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.spring;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.servlet.ServletContext;
|
||||
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
|
||||
import com.jfinal.kit.StrKit;
|
||||
import com.jfinal.template.Directive;
|
||||
import com.jfinal.template.Engine;
|
||||
import com.jfinal.template.source.ClassPathSourceFactory;
|
||||
import com.jfinal.template.source.ISourceFactory;
|
||||
|
||||
/**
|
||||
* JFinalViewResolver
|
||||
*
|
||||
* <pre>
|
||||
* 关键配置:
|
||||
* 1:setDevMode(true) 设置支持热加载模板文件
|
||||
*
|
||||
* 2:addSharedFunction(file) 添加共享函数文件
|
||||
*
|
||||
* 3:setSourceFactory(new ClassPathSourceFactory()),从 class path 与 jar 包中加载模板文件
|
||||
* 一般用于 sprint boot
|
||||
*
|
||||
* 4:setSessionInView(true) 设置在模板中可通过 #(session.value) 访问 session 中的数据
|
||||
*
|
||||
* 5:setCreateSession(boolean) 用来设置 request.getSession(boolean) 调时的参数
|
||||
*
|
||||
* 6:setBaseTemplatePath(path) 设置模板文件所在的基础路径,通常用于 spring mvc
|
||||
* 默认值为 web 根路径,一般不需要设置
|
||||
* </pre>
|
||||
*/
|
||||
public class JFinalViewResolver extends AbstractTemplateViewResolver {
|
||||
|
||||
public static final Engine engine = new Engine();
|
||||
|
||||
static List<String> sharedFunctionFiles = new ArrayList<String>();
|
||||
static boolean sessionInView = false;
|
||||
static boolean createSession = true;
|
||||
|
||||
public Engine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置开发模式,值为 true 时支持模板文件热加载
|
||||
*/
|
||||
public void setDevMode(boolean devMode) {
|
||||
engine.setDevMode(devMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 shared function 文件,多个文件用逗号分隔
|
||||
*
|
||||
* 主要用于 Spring MVC 的 xml 配置方式
|
||||
*
|
||||
* Spring Boot 的代码配置方式可使用 addSharedFunction(...) 进行配置
|
||||
*/
|
||||
public void setSharedFunction(String sharedFunctionFiles) {
|
||||
if (StrKit.isBlank(sharedFunctionFiles)) {
|
||||
throw new IllegalArgumentException("sharedFunctionFiles can not be blank");
|
||||
}
|
||||
|
||||
String[] fileArray = sharedFunctionFiles.split(",");
|
||||
for (String fileName : fileArray) {
|
||||
JFinalViewResolver.sharedFunctionFiles.add(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 shared function 文件,可调用多次添加多个文件
|
||||
*/
|
||||
public void addSharedFunction(String fileName) {
|
||||
// 等待 SourceFactory、baseTemplatePath 配置到位,利用 sharedFunctionFiles 实现延迟加载
|
||||
sharedFunctionFiles.add(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义指令
|
||||
*/
|
||||
public void addDirective(String directiveName, Directive directive) {
|
||||
engine.addDirective(directiveName, directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加共享对象
|
||||
*/
|
||||
public void addSharedObject(String name, Object object) {
|
||||
engine.addSharedObject(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加共享方法
|
||||
*/
|
||||
public void addSharedMethod(Object sharedMethodFromObject) {
|
||||
engine.addSharedMethod(sharedMethodFromObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加共享方法
|
||||
*/
|
||||
public void addSharedMethod(Class<?> sharedMethodFromClass) {
|
||||
engine.addSharedMethod(sharedMethodFromClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加扩展方法
|
||||
*/
|
||||
public static void addExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
Engine.addExtensionMethod(targetClass, objectOfExtensionClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加扩展方法
|
||||
*/
|
||||
public static void addExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
Engine.addExtensionMethod(targetClass, extensionClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ISourceFactory 用于为 engine 切换不同的 ISource 实现类
|
||||
*
|
||||
* <pre>
|
||||
* 配置为 ClassPathSourceFactory 时特别注意:
|
||||
* 由于在 initServletContext() 通过如下方法中已设置了 baseTemplatePath 值:
|
||||
* setBaseTemplatePath(servletContext.getRealPath("/"))
|
||||
*
|
||||
* 而 ClassPathSourceFactory 在 initServletContext() 方法中设置的
|
||||
* 值之下不能工作,所以在本方法中通过如下方法清掉了该值:
|
||||
* setBaseTemplatePath(null)
|
||||
*
|
||||
* 这种处理方式适用于绝大部分场景,如果在使用 ClassPathSourceFactory 的同时
|
||||
* 仍然需要设置 baseTemplatePath,则在调用该方法 “之后” 通过如下代码再次配置:
|
||||
* setBaseTemplatePath(value)
|
||||
* </pre>
|
||||
*/
|
||||
public void setSourceFactory(ISourceFactory sourceFactory) {
|
||||
if (sourceFactory instanceof ClassPathSourceFactory) {
|
||||
engine.setBaseTemplatePath(null);
|
||||
}
|
||||
engine.setSourceFactory(sourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模板基础路径
|
||||
*/
|
||||
public void setBaseTemplatePath(String baseTemplatePath) {
|
||||
engine.setBaseTemplatePath(baseTemplatePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为 true 时支持在模板中使用 #(session.value) 形式访问 session 中的数据
|
||||
*/
|
||||
public void setSessionInView(boolean sessionInView) {
|
||||
JFinalViewResolver.sessionInView = sessionInView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在使用 request.getSession(createSession) 时传入
|
||||
* 用来指示 session 不存在时是否立即创建
|
||||
*/
|
||||
public void setCreateSession(boolean createSession) {
|
||||
JFinalViewResolver.createSession = createSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 encoding
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
engine.setEncoding(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 #date(...) 指令,对于 Date、Timestamp、Time 的输出格式
|
||||
*/
|
||||
public void setDatePattern(String datePattern) {
|
||||
engine.setDatePattern(datePattern);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public JFinalViewResolver() {
|
||||
setViewClass(requiredViewClass());
|
||||
setOrder(0);
|
||||
setContentType("text/html;charset=UTF-8");
|
||||
// setPrefix("/view/");
|
||||
// setSuffix(".html");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> requiredViewClass() {
|
||||
return JFinalView.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* spring 回调,利用 ServletContext 做必要的初始化工作
|
||||
*/
|
||||
@Override
|
||||
protected void initServletContext(ServletContext servletContext) {
|
||||
super.initServletContext(servletContext);
|
||||
|
||||
initBaseTemplatePath(servletContext);
|
||||
initSharedFunction();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 baseTemplatePath 值,启用 ClassPathSourceFactory 时
|
||||
* 无需设置 baseTemplatePath 为 web 根路径
|
||||
*/
|
||||
private void initBaseTemplatePath(ServletContext servletContext) {
|
||||
if (engine.getSourceFactory() instanceof ClassPathSourceFactory) {
|
||||
// do nothing
|
||||
} else {
|
||||
if (StrKit.isBlank(engine.getBaseTemplatePath())) {
|
||||
String path = servletContext.getRealPath("/");
|
||||
engine.setBaseTemplatePath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 利用 sharedFunctionFiles 延迟调用 addSharedFunction
|
||||
* 因为需要等待 baseTemplatePath 以及 ISourceFactory 设置完毕以后
|
||||
* 才能正常工作
|
||||
*/
|
||||
private void initSharedFunction() {
|
||||
for (String file : sharedFunctionFiles) {
|
||||
engine.addSharedFunction(file.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
207
src/main/java/com/jfinal/template/source/ClassPathSource.java
Normal file
207
src/main/java/com/jfinal/template/source/ClassPathSource.java
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
|
||||
/**
|
||||
* ClassPathSource 用于从 class path 以及 jar 包之中加载模板内容
|
||||
*
|
||||
* <pre>
|
||||
* 注意:
|
||||
* 1:如果被加载的文件是 class path 中的普通文件,则该文件支持热加载
|
||||
*
|
||||
* 2:如果被加载的文件处于 jar 包之中,则该文件不支持热加载,jar 包之中的文件在运行时通常不会被修改
|
||||
* 在极少数情况下如果需要对 jar 包之中的模板文件进行热加载,可以通过继承 ClassPathSource
|
||||
* 的方式进行扩展
|
||||
*
|
||||
* 3:JFinal Template Engine 开启热加载需要配置 engine.setDevMode(true)
|
||||
* </pre>
|
||||
*/
|
||||
public class ClassPathSource implements ISource {
|
||||
|
||||
protected String finalFileName;
|
||||
protected String fileName;
|
||||
protected String encoding;
|
||||
|
||||
protected boolean isInJar;
|
||||
protected long lastModified;
|
||||
protected ClassLoader classLoader;
|
||||
protected URL url;
|
||||
|
||||
public ClassPathSource(String fileName) {
|
||||
this(null, fileName, EngineConfig.DEFAULT_ENCODING);
|
||||
}
|
||||
|
||||
public ClassPathSource(String baseTemplatePath, String fileName) {
|
||||
this(baseTemplatePath, fileName, EngineConfig.DEFAULT_ENCODING);
|
||||
}
|
||||
|
||||
public ClassPathSource(String baseTemplatePath, String fileName, String encoding) {
|
||||
this.finalFileName = buildFinalFileName(baseTemplatePath, fileName);
|
||||
this.fileName = fileName;
|
||||
this.encoding= encoding;
|
||||
this.classLoader = getClassLoader();
|
||||
this.url = classLoader.getResource(finalFileName);
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("File not found : \"" + finalFileName + "\"");
|
||||
}
|
||||
|
||||
processIsInJarAndlastModified();
|
||||
}
|
||||
|
||||
protected void processIsInJarAndlastModified() {
|
||||
try {
|
||||
URLConnection conn = url.openConnection();
|
||||
if ("jar".equals(url.getProtocol()) || conn instanceof JarURLConnection) {
|
||||
isInJar = true;
|
||||
lastModified = -1;
|
||||
} else {
|
||||
isInJar = false;
|
||||
lastModified = conn.getLastModified();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected ClassLoader getClassLoader() {
|
||||
ClassLoader ret = Thread.currentThread().getContextClassLoader();
|
||||
return ret != null ? ret : getClass().getClassLoader();
|
||||
}
|
||||
|
||||
protected String buildFinalFileName(String baseTemplatePath, String fileName) {
|
||||
String finalFileName;
|
||||
if (baseTemplatePath != null) {
|
||||
char firstChar = fileName.charAt(0);
|
||||
if (firstChar == '/' || firstChar == '\\') {
|
||||
finalFileName = baseTemplatePath + fileName;
|
||||
} else {
|
||||
finalFileName = baseTemplatePath + "/" + fileName;
|
||||
}
|
||||
} else {
|
||||
finalFileName = fileName;
|
||||
}
|
||||
|
||||
if (finalFileName.charAt(0) == '/') {
|
||||
finalFileName = finalFileName.substring(1);
|
||||
}
|
||||
|
||||
return finalFileName;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
protected long getLastModified() {
|
||||
try {
|
||||
URLConnection conn = url.openConnection();
|
||||
return conn.getLastModified();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板文件在 jar 包文件之内则不支持热加载
|
||||
*/
|
||||
public boolean isModified() {
|
||||
return isInJar ? false : lastModified != getLastModified();
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
// 与 FileSorce 不同,ClassPathSource 在构造方法中已经初始化了 lastModified
|
||||
// 下面的代码可以去掉,在此仅为了避免继承类忘了在构造中初始化 lastModified 的防卫式代码
|
||||
if (!isInJar) { // 如果模板文件不在 jar 包文件之中,则需要更新 lastModified 值
|
||||
lastModified = getLastModified();
|
||||
}
|
||||
|
||||
InputStream inputStream = classLoader.getResourceAsStream(finalFileName);
|
||||
if (inputStream == null) {
|
||||
throw new RuntimeException("File not found : \"" + finalFileName + "\"");
|
||||
}
|
||||
|
||||
return loadFile(inputStream, encoding);
|
||||
}
|
||||
|
||||
public static StringBuilder loadFile(InputStream inputStream, String encoding) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(inputStream, encoding));
|
||||
// br = new BufferedReader(new FileReader(fileName));
|
||||
String line = br.readLine();
|
||||
if (line != null) {
|
||||
ret.append(line);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
while ((line=br.readLine()) != null) {
|
||||
ret.append('\n').append(line);
|
||||
}
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
// com.jfinal.kit.LogKit.error(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("In Jar File: ").append(isInJar).append("\n");
|
||||
sb.append("File name: ").append(fileName).append("\n");
|
||||
sb.append("Final file name: ").append(finalFileName).append("\n");
|
||||
sb.append("Last modified: ").append(lastModified).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
protected File getFile(URL url) {
|
||||
try {
|
||||
// return new File(url.toURI().getSchemeSpecificPart());
|
||||
return new File(url.toURI());
|
||||
} catch (URISyntaxException ex) {
|
||||
// Fallback for URLs that are not valid URIs (should hardly ever happen).
|
||||
return new File(url.getFile());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
/**
|
||||
* ClassPathSourceFactory 用于配置 Engine 使用 ClassPathSource 加载模板文件
|
||||
*
|
||||
* 配置示例:
|
||||
* engine.baseTemplatePath(null); // 清掉 base path
|
||||
* engine.setSourceFactory(new ClassPathSourceFactory());
|
||||
*/
|
||||
public class ClassPathSourceFactory implements ISourceFactory {
|
||||
|
||||
public ISource getSource(String baseTemplatePath, String fileName, String encoding) {
|
||||
return new ClassPathSource(baseTemplatePath, fileName, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
133
src/main/java/com/jfinal/template/source/FileSource.java
Normal file
133
src/main/java/com/jfinal/template/source/FileSource.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
|
||||
/**
|
||||
* FileSource 用于从普通文件中加载模板内容
|
||||
*/
|
||||
public class FileSource implements ISource {
|
||||
|
||||
private String finalFileName;
|
||||
private String fileName;
|
||||
private String encoding;
|
||||
|
||||
private long lastModified;
|
||||
|
||||
public FileSource(String baseTemplatePath, String fileName, String encoding) {
|
||||
this.finalFileName = buildFinalFileName(baseTemplatePath, fileName);
|
||||
this.fileName = fileName;
|
||||
this.encoding= encoding;
|
||||
}
|
||||
|
||||
public FileSource(String baseTemplatePath, String fileName) {
|
||||
this(baseTemplatePath, fileName, EngineConfig.DEFAULT_ENCODING);
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return lastModified != new File(finalFileName).lastModified();
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public String getFinalFileName() {
|
||||
return finalFileName;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
File file = new File(finalFileName);
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("File not found : " + finalFileName);
|
||||
}
|
||||
|
||||
// 极为重要,否则在开发模式下 isModified() 一直返回 true,缓存一直失效(原因是 lastModified 默认值为 0)
|
||||
this.lastModified = file.lastModified();
|
||||
|
||||
return loadFile(file, encoding);
|
||||
}
|
||||
|
||||
private String buildFinalFileName(String baseTemplatePath, String fileName) {
|
||||
char firstChar = fileName.charAt(0);
|
||||
String finalFileName;
|
||||
if (firstChar == '/' || firstChar == '\\') {
|
||||
finalFileName = baseTemplatePath + fileName;
|
||||
} else {
|
||||
finalFileName = baseTemplatePath + File.separator + fileName;
|
||||
}
|
||||
return finalFileName;
|
||||
}
|
||||
|
||||
public static StringBuilder loadFile(File file, String encoding) {
|
||||
StringBuilder ret = new StringBuilder((int)file.length() + 3);
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
|
||||
// br = new BufferedReader(new FileReader(fileName));
|
||||
String line = br.readLine();
|
||||
if (line != null) {
|
||||
ret.append(line);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
while ((line=br.readLine()) != null) {
|
||||
ret.append('\n').append(line);
|
||||
}
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
// com.jfinal.kit.LogKit.error(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("File name: ").append(fileName).append("\n");
|
||||
sb.append("Final file name: ").append(finalFileName).append("\n");
|
||||
sb.append("Last modified: ").append(lastModified).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
/**
|
||||
* FileSourceFactory 用于配置 Engine 使用 FileSource 加载模板文件
|
||||
*
|
||||
* 注意:
|
||||
* FileSourceFactory 为模板引擎默认配置
|
||||
*/
|
||||
public class FileSourceFactory implements ISourceFactory {
|
||||
|
||||
public ISource getSource(String baseTemplatePath, String fileName, String encoding) {
|
||||
return new FileSource(baseTemplatePath, fileName, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
48
src/main/java/com/jfinal/template/source/ISource.java
Normal file
48
src/main/java/com/jfinal/template/source/ISource.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
/**
|
||||
* ISource 用于表示模板内容的来源
|
||||
*/
|
||||
public interface ISource {
|
||||
|
||||
/**
|
||||
* reload template if modified on devMode
|
||||
*/
|
||||
boolean isModified();
|
||||
|
||||
/**
|
||||
* key used to cache, return null if do not cache the template
|
||||
*
|
||||
* 注意:如果不希望缓存从该 ISource 解析出来的 Template 对象
|
||||
* 让 getKey() 返回 null 值即可
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* content of ISource
|
||||
*/
|
||||
StringBuilder getContent();
|
||||
|
||||
/**
|
||||
* encoding of content
|
||||
*/
|
||||
String getEncoding();
|
||||
}
|
||||
|
||||
|
34
src/main/java/com/jfinal/template/source/ISourceFactory.java
Normal file
34
src/main/java/com/jfinal/template/source/ISourceFactory.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.source;
|
||||
|
||||
/**
|
||||
* ISourceFactory 用于为 engine 切换不同的 ISource 实现类
|
||||
*
|
||||
* FileSourceFactory 用于从指定的目录中加载模板文件
|
||||
* ClassPathSourceFactory 用于从 class path 以及 jar 文件中加载模板文件
|
||||
*
|
||||
* 配置示例:
|
||||
* engine.setSourceFactory(new ClassPathSourceFactory());
|
||||
*/
|
||||
public interface ISourceFactory {
|
||||
ISource getSource(String baseTemplatePath, String fileName, String encoding);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
81
src/main/java/com/jfinal/template/source/StringSource.java
Normal file
81
src/main/java/com/jfinal/template/source/StringSource.java
Normal 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.source;
|
||||
|
||||
import com.jfinal.kit.HashKit;
|
||||
import com.jfinal.kit.StrKit;
|
||||
import com.jfinal.template.EngineConfig;
|
||||
|
||||
/**
|
||||
* StringSource 用于从 String 变量中加载模板内容
|
||||
*/
|
||||
public class StringSource implements ISource {
|
||||
|
||||
private String key;
|
||||
private StringBuilder content;
|
||||
|
||||
/**
|
||||
* 构造 StringSource
|
||||
* @param content 模板内容
|
||||
* @param cache true 则缓存 Template,否则不缓存
|
||||
*/
|
||||
public StringSource(String content, boolean cache) {
|
||||
if (StrKit.isBlank(content)) {
|
||||
throw new IllegalArgumentException("content can not be blank");
|
||||
}
|
||||
this.content = new StringBuilder(content);
|
||||
this.key = cache ? HashKit.md5(content) : null; // 不缓存只要将 key 值赋为 null 即可
|
||||
}
|
||||
|
||||
public StringSource(StringBuilder content, boolean cache) {
|
||||
if (content == null || content.length() == 0) {
|
||||
throw new IllegalArgumentException("content can not be blank");
|
||||
}
|
||||
this.content = content;
|
||||
this.key = cache ? HashKit.md5(content.toString()) : null; // 不缓存只要将 key 值赋为 null 即可
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return EngineConfig.DEFAULT_ENCODING;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Key : ").append(key).append("\n");
|
||||
sb.append("Content : ").append(content).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
114
src/main/java/com/jfinal/template/stat/CharTable.java
Normal file
114
src/main/java/com/jfinal/template/stat/CharTable.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* CharTable 空间换时间优化字符判断性能
|
||||
* 负值参数强转 char 会自动变正值,无需判断负值数组下标
|
||||
* isLetter(EOF) 不会下标越界
|
||||
*/
|
||||
public class CharTable {
|
||||
|
||||
private static final char[] letterChars = buildLetterChars();
|
||||
private static final char[] letterOrDigitChars = buildLetterOrDigitChars();
|
||||
private static final char[] exprChars = buildExprChars();
|
||||
private static final char NULL = 0;
|
||||
private static final char SIZE = 128;
|
||||
private CharTable(){}
|
||||
|
||||
private static char[] createCharArray() {
|
||||
char[] ret = new char[SIZE];
|
||||
for (char i=0; i<SIZE; i++) {
|
||||
ret[i] = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static char[] buildLetterChars() {
|
||||
char[] ret = createCharArray();
|
||||
for (char i='a'; i<='z'; i++) {
|
||||
ret[i] = i;
|
||||
}
|
||||
for (char i='A'; i<='Z'; i++) {
|
||||
ret[i] = i;
|
||||
}
|
||||
ret['_'] = '_'; // 包含下划线字符 '_'
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static char[] buildLetterOrDigitChars() {
|
||||
char[] ret = buildLetterChars();
|
||||
for (char i='0'; i<='9'; i++) {
|
||||
ret[i] = i;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static char[] buildExprChars() {
|
||||
char[] ret = createCharArray();
|
||||
ret['\t'] = '\t';
|
||||
ret['\n'] = '\n';
|
||||
ret['\r'] = '\r';
|
||||
for (char i=' '; i<='}'; i++) {
|
||||
ret[i] = i;
|
||||
}
|
||||
|
||||
ret['#'] = NULL;
|
||||
ret['$'] = NULL;
|
||||
ret['@'] = NULL;
|
||||
ret['\\'] = NULL;
|
||||
ret['^'] = NULL;
|
||||
ret['`'] = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static boolean isLetter(char c) {
|
||||
return c < SIZE && letterChars[c] != NULL;
|
||||
}
|
||||
|
||||
public static boolean isLetterOrDigit(char c) {
|
||||
return c < SIZE && letterOrDigitChars[c] != NULL;
|
||||
}
|
||||
|
||||
public static boolean isExprChar(char c) {
|
||||
return c < SIZE && exprChars[c] != NULL;
|
||||
}
|
||||
|
||||
public static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
public static boolean isBlank(char c) {
|
||||
return c == ' ' || c == '\t'; // \t\r\u000C
|
||||
}
|
||||
|
||||
public static boolean isBlankOrLineFeed(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'; // \t\r\n\u000C
|
||||
}
|
||||
|
||||
public static boolean isHexadecimalDigit(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
|
||||
public static boolean isOctalDigit(char c) {
|
||||
return c >= '0' && c <= '7';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
118
src/main/java/com/jfinal/template/stat/Ctrl.java
Normal file
118
src/main/java/com/jfinal/template/stat/Ctrl.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Ctrl
|
||||
*
|
||||
* 封装 AST 执行过程中的控制状态,避免使用 Scope.data 保存控制状态
|
||||
* 从而污染用户空间数据,目前仅用于 nullSafe、break、continue、return 控制
|
||||
* 未来可根据需求引入更多控制状态
|
||||
*/
|
||||
public class Ctrl {
|
||||
|
||||
private static final int JUMP_NONE = 0;
|
||||
private static final int JUMP_BREAK = 1;
|
||||
private static final int JUMP_CONTINUE = 2;
|
||||
private static final int JUMP_RETURN = 3;
|
||||
|
||||
private static final int WISDOM_ASSIGNMENT = 0;
|
||||
private static final int LOCAL_ASSIGNMENT = 1;
|
||||
private static final int GLOBAL_ASSIGNMENT = 2;
|
||||
|
||||
private int jump = JUMP_NONE;
|
||||
private int assignmentType = WISDOM_ASSIGNMENT;
|
||||
private boolean nullSafe = false;
|
||||
|
||||
public boolean isJump() {
|
||||
return jump != JUMP_NONE;
|
||||
}
|
||||
|
||||
public boolean notJump() {
|
||||
return jump == JUMP_NONE;
|
||||
}
|
||||
|
||||
public boolean isBreak() {
|
||||
return jump == JUMP_BREAK;
|
||||
}
|
||||
|
||||
public void setBreak() {
|
||||
jump = JUMP_BREAK;
|
||||
}
|
||||
|
||||
public boolean isContinue() {
|
||||
return jump == JUMP_CONTINUE;
|
||||
}
|
||||
|
||||
public void setContinue() {
|
||||
jump = JUMP_CONTINUE;
|
||||
}
|
||||
|
||||
public boolean isReturn() {
|
||||
return jump == JUMP_RETURN;
|
||||
}
|
||||
|
||||
public void setReturn() {
|
||||
jump = JUMP_RETURN;
|
||||
}
|
||||
|
||||
public void setJumpNone() {
|
||||
jump = JUMP_NONE;
|
||||
}
|
||||
|
||||
public boolean isWisdomAssignment() {
|
||||
return assignmentType == WISDOM_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public void setWisdomAssignment() {
|
||||
assignmentType = WISDOM_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public boolean isLocalAssignment() {
|
||||
return assignmentType == LOCAL_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public void setLocalAssignment() {
|
||||
assignmentType = LOCAL_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public boolean isGlobalAssignment() {
|
||||
return assignmentType == GLOBAL_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public void setGlobalAssignment() {
|
||||
assignmentType = GLOBAL_ASSIGNMENT;
|
||||
}
|
||||
|
||||
public boolean isNullSafe() {
|
||||
return nullSafe;
|
||||
}
|
||||
|
||||
public boolean notNullSafe() {
|
||||
return !nullSafe;
|
||||
}
|
||||
|
||||
public void setNullSafe(boolean nullSafe) {
|
||||
this.nullSafe = nullSafe;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
526
src/main/java/com/jfinal/template/stat/Lexer.java
Normal file
526
src/main/java/com/jfinal/template/stat/Lexer.java
Normal file
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DKFF(Dynamic Key Feature Forward) Lexer
|
||||
*/
|
||||
class Lexer {
|
||||
|
||||
static final char EOF = (char)-1;
|
||||
static final int TEXT_STATE_DIAGRAM = 999;
|
||||
|
||||
char[] buf;
|
||||
int state = 0;
|
||||
int lexemeBegin = 0;
|
||||
int forward = 0;
|
||||
int beginRow = 1;
|
||||
int forwardRow = 1;
|
||||
TextToken previousTextToken = null;
|
||||
|
||||
List<Token> tokens = new ArrayList<Token>();
|
||||
String fileName;
|
||||
|
||||
public Lexer(StringBuilder content, String fileName) {
|
||||
int len = content.length();
|
||||
buf = new char[len + 1];
|
||||
content.getChars(0, content.length(), buf, 0);
|
||||
buf[len] = EOF;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入每个扫描方法之前 peek() 处于可用状态,不需要 next()
|
||||
* 每个扫描方法内部是否要 next() 移动,取决定具体情况
|
||||
* 每个扫描方法成功返回前,将 forward 置于下一次扫描需要处理的地方
|
||||
* 让下个扫描方法不必 next()
|
||||
* 紧靠 scanText() 之前的扫描方法在失败后必须保持住forward
|
||||
* 这是 scanText() 可以一直向前的保障
|
||||
*/
|
||||
public List<Token> scan() {
|
||||
while (peek() != EOF) {
|
||||
if (peek() == '#') {
|
||||
if (scanDire()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanSingleLineComment()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanMultiLineComment()) {
|
||||
continue ;
|
||||
}
|
||||
if (scanNoParse()) {
|
||||
continue ;
|
||||
}
|
||||
}
|
||||
|
||||
scanText();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令模式与解析规则
|
||||
* 1:指令 pattern
|
||||
* #(p)
|
||||
* #id(p)
|
||||
* #define id(p)
|
||||
* #@id(p) / #@id?(p)
|
||||
* #else / #end
|
||||
*
|
||||
* 2:关键字类型指令在获取到关键字以后,必须要正确解析出后续内容,否则抛异常
|
||||
*
|
||||
* 3:非关键字类型指令只有在本行内出现 # id ( 三个序列以后,才要求正确解析出后续内容
|
||||
* 否则当成普通文本
|
||||
*/
|
||||
boolean scanDire() {
|
||||
String id = null;
|
||||
StringBuilder para = null;
|
||||
Token idToken = null;
|
||||
Token paraToken = null;
|
||||
while(true) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (peek() == '#') { // #
|
||||
next();
|
||||
skipBlanks();
|
||||
state = 1;
|
||||
continue ;
|
||||
}
|
||||
return fail();
|
||||
case 1:
|
||||
if (peek() == '(') { // # (
|
||||
para = scanPara("");
|
||||
idToken = new Token(Symbol.OUTPUT, beginRow);
|
||||
paraToken = new ParaToken(para, beginRow);
|
||||
return addOutputToken(idToken, paraToken);
|
||||
}
|
||||
if (CharTable.isLetter(peek())) { // # id
|
||||
state = 10;
|
||||
continue ;
|
||||
}
|
||||
if (peek() == '@') { // # @
|
||||
next();
|
||||
skipBlanks();
|
||||
if (CharTable.isLetter(peek())) { // # @ id
|
||||
state = 20;
|
||||
continue ;
|
||||
}
|
||||
}
|
||||
return fail();
|
||||
// -----------------------------------------------------
|
||||
case 10: // # id
|
||||
id = scanId();
|
||||
Symbol symbol = Symbol.getKeywordSym(id);
|
||||
// 非关键字指令
|
||||
if (symbol == null) {
|
||||
state = 11;
|
||||
continue ;
|
||||
}
|
||||
|
||||
// define 指令
|
||||
if (symbol == Symbol.DEFINE) {
|
||||
state = 12;
|
||||
continue ;
|
||||
}
|
||||
|
||||
// 无参关键字指令
|
||||
if (symbol.noPara()) {
|
||||
return addNoParaToken(new Token(symbol, id, beginRow));
|
||||
}
|
||||
|
||||
// 有参关键字指令
|
||||
skipBlanks();
|
||||
if (peek() == '(') {
|
||||
para = scanPara(id);
|
||||
idToken = new Token(symbol, beginRow);
|
||||
paraToken = new ParaToken(para, beginRow);
|
||||
return addIdParaToken(idToken, paraToken);
|
||||
}
|
||||
throw new ParseException("#" + id + " directive requires parentheses \"()\"", new Location(fileName, beginRow));
|
||||
case 11: // 用户自定义指令必须有参数
|
||||
skipBlanks();
|
||||
if (peek() == '(') {
|
||||
para = scanPara(id);
|
||||
idToken = new Token(Symbol.ID, id, beginRow);
|
||||
paraToken = new ParaToken(para, beginRow);
|
||||
return addIdParaToken(idToken, paraToken);
|
||||
}
|
||||
return fail(); // 用户自定义指令在没有左括号的情况下当作普通文本
|
||||
case 12: // 处理 "# define id (para)" 指令
|
||||
skipBlanks();
|
||||
if (CharTable.isLetter(peek())) {
|
||||
id = scanId(); // 模板函数名称
|
||||
skipBlanks();
|
||||
if (peek() == '(') {
|
||||
para = scanPara("define " + id);
|
||||
idToken = new Token(Symbol.DEFINE, id, beginRow);
|
||||
paraToken = new ParaToken(para, beginRow);
|
||||
return addIdParaToken(idToken, paraToken);
|
||||
}
|
||||
throw new ParseException("#define " + id + " : template function definition requires parentheses \"()\"", new Location(fileName, beginRow));
|
||||
}
|
||||
throw new ParseException("#define directive requires identifier as a function name", new Location(fileName, beginRow));
|
||||
case 20: // # @ id
|
||||
id = scanId();
|
||||
skipBlanks();
|
||||
boolean hasQuestionMark = peek() == '?';
|
||||
if (hasQuestionMark) {
|
||||
next();
|
||||
skipBlanks();
|
||||
}
|
||||
if (peek() == '(') {
|
||||
para = scanPara(hasQuestionMark ? "@" + id + "?" : "@" + id);
|
||||
idToken = new Token(hasQuestionMark ? Symbol.CALL_IF_DEFINED : Symbol.CALL, id, beginRow);
|
||||
paraToken = new ParaToken(para, beginRow);
|
||||
return addIdParaToken(idToken, paraToken);
|
||||
}
|
||||
return fail();
|
||||
default :
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用者已确定以字母或下划线开头,故一定可以获取到 id值
|
||||
*/
|
||||
String scanId() {
|
||||
int idStart = forward;
|
||||
while (CharTable.isLetterOrDigit(next())) {
|
||||
;
|
||||
}
|
||||
return subBuf(idStart, forward - 1).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描指令参数,成功则返回,否则抛出词法分析异常
|
||||
*/
|
||||
StringBuilder scanPara(String id) {
|
||||
char quotes = '"';
|
||||
int localState = 0;
|
||||
int parenDepth = 1; // 指令后面参数的第一个 '(' 深度为 1
|
||||
next();
|
||||
int paraStart = forward;
|
||||
while (true) {
|
||||
switch (localState) {
|
||||
case 0:
|
||||
for (char c=peek(); true; c=next()) {
|
||||
if (c == ')') {
|
||||
parenDepth--;
|
||||
if (parenDepth == 0) { // parenDepth 不可能小于0,因为初始值为 1
|
||||
next();
|
||||
return subBuf(paraStart, forward - 2);
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
|
||||
if (c == '(') {
|
||||
parenDepth++;
|
||||
continue ;
|
||||
}
|
||||
|
||||
if (c == '"' || c == '\'') {
|
||||
quotes = c;
|
||||
localState = 1;
|
||||
break ;
|
||||
}
|
||||
|
||||
if (CharTable.isExprChar(c)) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
if (c == EOF) {
|
||||
throw new ParseException("#" + id + " parameter can not match the end char ')'", new Location(fileName, beginRow));
|
||||
}
|
||||
|
||||
throw new ParseException("#" + id + " parameter exists illegal char: '" + c + "'", new Location(fileName, beginRow));
|
||||
}
|
||||
break ;
|
||||
case 1:
|
||||
for (char c=next(); true; c=next()) {
|
||||
if (c == quotes) {
|
||||
if (buf[forward - 1] != '\\') { // 前一个字符不是转义字符
|
||||
next();
|
||||
localState = 0;
|
||||
break ;
|
||||
} else {
|
||||
continue ;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == EOF) {
|
||||
throw new ParseException("#" + id + " parameter error, the string parameter not ending", new Location(fileName, beginRow));
|
||||
}
|
||||
}
|
||||
break ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单行注释,开始状态 100,关注换行与 EOF
|
||||
*/
|
||||
boolean scanSingleLineComment() {
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case 100:
|
||||
if (peek() == '#' && next() == '#' && next() == '#') {
|
||||
state = 101;
|
||||
continue ;
|
||||
}
|
||||
return fail();
|
||||
case 101:
|
||||
for (char c=next(); true; c=next()) {
|
||||
if (c == '\n') {
|
||||
if (deletePreviousTextTokenBlankTails()) {
|
||||
return prepareNextScan(1);
|
||||
} else {
|
||||
return prepareNextScan(0);
|
||||
}
|
||||
}
|
||||
if (c == EOF) {
|
||||
deletePreviousTextTokenBlankTails();
|
||||
return prepareNextScan(0);
|
||||
}
|
||||
}
|
||||
default :
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多行注释,开始状态 200,关注结尾标记与 EOF
|
||||
*/
|
||||
boolean scanMultiLineComment() {
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case 200:
|
||||
if (peek() == '#' && next() == '-' && next() == '-') {
|
||||
state = 201;
|
||||
continue ;
|
||||
}
|
||||
return fail();
|
||||
case 201:
|
||||
for (char c=next(); true; c=next()) {
|
||||
if (c == '-' && buf[forward + 1] == '-' && buf[forward + 2] == '#') {
|
||||
forward = forward + 3;
|
||||
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
|
||||
return prepareNextScan(peek() != EOF ? 1 : 0);
|
||||
} else {
|
||||
return prepareNextScan(0);
|
||||
}
|
||||
}
|
||||
if (c == EOF) {
|
||||
throw new ParseException("The multiline comment start block \"#--\" can not match the end block: \"--#\"", new Location(fileName, beginRow));
|
||||
}
|
||||
}
|
||||
default :
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非解析块,开始状态 300,关注结尾标记与 EOF
|
||||
*/
|
||||
boolean scanNoParse() {
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case 300:
|
||||
if (peek() == '#' && next() == '[' && next() == '[') {
|
||||
state = 301;
|
||||
continue ;
|
||||
}
|
||||
return fail();
|
||||
case 301:
|
||||
for (char c=next(); true; c=next()) {
|
||||
if (c == ']' && buf[forward + 1] == ']' && buf[forward + 2] == '#') {
|
||||
addTextToken(subBuf(lexemeBegin + 3, forward - 1)); // NoParse 块使用 TextToken
|
||||
return prepareNextScan(3);
|
||||
}
|
||||
if (c == EOF) {
|
||||
throw new ParseException("The \"no parse\" start block \"#[[\" can not match the end block: \"]]#\"", new Location(fileName, beginRow));
|
||||
}
|
||||
}
|
||||
default :
|
||||
return fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean scanText() {
|
||||
for (char c=peek(); true; c=next()) {
|
||||
if (c == '#' || c == EOF) {
|
||||
addTextToken(subBuf(lexemeBegin, forward - 1));
|
||||
return prepareNextScan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean fail() {
|
||||
if (state < 300) {
|
||||
forward = lexemeBegin;
|
||||
forwardRow = beginRow;
|
||||
}
|
||||
if (state < 100) {
|
||||
state = 100;
|
||||
} else if (state < 200) {
|
||||
state = 200;
|
||||
} else if (state < 300) {
|
||||
state = 300;
|
||||
} else {
|
||||
state = TEXT_STATE_DIAGRAM;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char next() {
|
||||
if (buf[forward] == '\n') {
|
||||
forwardRow++;
|
||||
}
|
||||
return buf[++forward];
|
||||
}
|
||||
|
||||
char peek() {
|
||||
return buf[forward];
|
||||
}
|
||||
|
||||
void skipBlanks() {
|
||||
while(CharTable.isBlank(buf[forward])) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scanPara 与 scanNoParse 存在 start > end 的情况
|
||||
*/
|
||||
StringBuilder subBuf(int start, int end) {
|
||||
if (start > end) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder ret = new StringBuilder(end - start + 1);
|
||||
for (int i=start; i<=end; i++) {
|
||||
ret.append(buf[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
boolean prepareNextScan(int moveForward) {
|
||||
for (int i=0; i<moveForward; i++) {
|
||||
next();
|
||||
}
|
||||
|
||||
state = 0;
|
||||
lexemeBegin = forward;
|
||||
beginRow = forwardRow;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addTextToken(StringBuilder text) {
|
||||
if (text == null || text.length() == 0) {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (previousTextToken != null) {
|
||||
previousTextToken.append(text);
|
||||
} else {
|
||||
previousTextToken = new TextToken(text, beginRow);
|
||||
tokens.add(previousTextToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出指令不对前后空白与换行进行任何处理,直接调用 tokens.add(...)
|
||||
boolean addOutputToken(Token idToken, Token paraToken) {
|
||||
tokens.add(idToken);
|
||||
tokens.add(paraToken);
|
||||
previousTextToken = null;
|
||||
return prepareNextScan(0);
|
||||
}
|
||||
|
||||
// 向前看后续是否跟随的是空白 + 换行或者是空白 + EOF,是则表示当前指令后续没有其它有用内容
|
||||
boolean lookForwardLineFeedAndEof() {
|
||||
int forwardBak = forward;
|
||||
int forwardRowBak = forwardRow;
|
||||
for (char c=peek(); true; c=next()) {
|
||||
if (CharTable.isBlank(c)) {
|
||||
continue ;
|
||||
}
|
||||
if (c == '\n' || c == EOF) {
|
||||
return true;
|
||||
}
|
||||
forward = forwardBak;
|
||||
forwardRow = forwardRowBak;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带参指令处于独立行时删除前后空白字符,并且再删除一个后续的换行符
|
||||
* 处于独立行是指:向前看无有用内容,在前面情况成立的基础之上
|
||||
* 再向后看如果也无可用内容,前一个条件成立才开执行后续动作
|
||||
*
|
||||
* 向前看时 forward 在移动,意味着正在删除空白字符(通过 lookForwardLineFeed()方法)
|
||||
* 向后看时也会在碰到空白 + '\n' 时删空白字符 (通过 deletePreviousTextTokenBlankTails()方法)
|
||||
*/
|
||||
boolean addIdParaToken(Token idToken, Token paraToken) {
|
||||
tokens.add(idToken);
|
||||
tokens.add(paraToken);
|
||||
|
||||
// if (lookForwardLineFeed() && (deletePreviousTextTokenBlankTails() || lexemeBegin == 0)) {
|
||||
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
|
||||
prepareNextScan(peek() != EOF ? 1 : 0);
|
||||
} else {
|
||||
prepareNextScan(0);
|
||||
}
|
||||
previousTextToken = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理前后空白的逻辑与 addIdParaToken() 基本一样,仅仅多了一个对于紧随空白的 next() 操作
|
||||
boolean addNoParaToken(Token noParaToken) {
|
||||
tokens.add(noParaToken);
|
||||
if (CharTable.isBlank(peek())) {
|
||||
next(); // 无参指令之后紧随的一个空白字符仅为分隔符,不参与后续扫描
|
||||
}
|
||||
|
||||
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
|
||||
prepareNextScan(peek() != EOF ? 1 : 0);
|
||||
} else {
|
||||
prepareNextScan(0);
|
||||
}
|
||||
previousTextToken = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1:当前指令前方仍然是指令 (previousTextToken 为 null),直接返回 true
|
||||
* 2:当前指令前方为 TextToken 时的处理逻辑与返回值完全依赖于 TextToken.deleteBlankTails()
|
||||
*/
|
||||
boolean deletePreviousTextTokenBlankTails() {
|
||||
// return previousTextToken != null ? previousTextToken.deleteBlankTails() : false;
|
||||
return previousTextToken == null || previousTextToken.deleteBlankTails();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
61
src/main/java/com/jfinal/template/stat/Location.java
Normal file
61
src/main/java/com/jfinal/template/stat/Location.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Location
|
||||
* 生成异常发生的位置消息
|
||||
*/
|
||||
public class Location {
|
||||
|
||||
private String templateFile;
|
||||
private int row;
|
||||
private String msg;
|
||||
|
||||
public Location(String templateFile, int row) {
|
||||
this.templateFile = templateFile;
|
||||
this.row = row;
|
||||
this.msg = null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (msg == null) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (templateFile != null) {
|
||||
buf.append("\nTemplate: \"").append(templateFile).append("\". Line: ").append(row);
|
||||
} else {
|
||||
buf.append("\nString template line: ").append(row);
|
||||
}
|
||||
msg = buf.toString();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public String getTemplateFile() {
|
||||
return templateFile;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
52
src/main/java/com/jfinal/template/stat/ParaToken.java
Normal file
52
src/main/java/com/jfinal/template/stat/ParaToken.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* ParaToken
|
||||
*/
|
||||
public class ParaToken extends Token {
|
||||
|
||||
// 接管父类的 value,content 可能为 null
|
||||
private StringBuilder content;
|
||||
|
||||
public ParaToken(StringBuilder content, int row) {
|
||||
super(Symbol.PARA, row);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return content != null ? content.toString() : "null";
|
||||
}
|
||||
|
||||
public void print() {
|
||||
System.out.print("[");
|
||||
System.out.print(row);
|
||||
System.out.print(", PARA, ");
|
||||
System.out.print(toString());
|
||||
System.out.println("]");
|
||||
}
|
||||
}
|
||||
|
34
src/main/java/com/jfinal/template/stat/ParseException.java
Normal file
34
src/main/java/com/jfinal/template/stat/ParseException.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* ParseException
|
||||
* 词法、语法错误
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ParseException extends RuntimeException {
|
||||
|
||||
public ParseException(String msg, Location loc) {
|
||||
super(loc != null ? msg + loc : msg);
|
||||
}
|
||||
|
||||
public ParseException(String msg, Location loc, Throwable t) {
|
||||
super(loc != null ? msg + loc : msg, t);
|
||||
}
|
||||
}
|
||||
|
258
src/main/java/com/jfinal/template/stat/Parser.java
Normal file
258
src/main/java/com/jfinal/template/stat/Parser.java
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ExprParser;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.ForCtrl;
|
||||
import com.jfinal.template.stat.Symbol;
|
||||
import com.jfinal.template.stat.ast.Break;
|
||||
import com.jfinal.template.stat.ast.Call;
|
||||
import com.jfinal.template.stat.ast.Continue;
|
||||
import com.jfinal.template.stat.ast.Define;
|
||||
import com.jfinal.template.stat.ast.Else;
|
||||
import com.jfinal.template.stat.ast.ElseIf;
|
||||
import com.jfinal.template.stat.ast.For;
|
||||
import com.jfinal.template.stat.ast.If;
|
||||
import com.jfinal.template.stat.ast.Include;
|
||||
import com.jfinal.template.stat.ast.Return;
|
||||
import com.jfinal.template.stat.ast.Set;
|
||||
import com.jfinal.template.stat.ast.SetGlobal;
|
||||
import com.jfinal.template.stat.ast.SetLocal;
|
||||
import com.jfinal.template.stat.ast.Stat;
|
||||
import com.jfinal.template.stat.ast.StatList;
|
||||
import com.jfinal.template.stat.ast.Text;
|
||||
|
||||
/**
|
||||
* DLRD (Double Layer Recursive Descent) Parser
|
||||
*/
|
||||
public class Parser {
|
||||
|
||||
private static final Token EOF = new Token(Symbol.EOF, -1);
|
||||
|
||||
private int forward = 0;
|
||||
private List<Token> tokenList;
|
||||
private StringBuilder content;
|
||||
private String fileName;
|
||||
private Env env;
|
||||
|
||||
public Parser(Env env, StringBuilder content, String fileName) {
|
||||
this.env = env;
|
||||
this.content = content;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
private Token peek() {
|
||||
return tokenList.get(forward);
|
||||
}
|
||||
|
||||
private Token move() {
|
||||
return tokenList.get(++forward);
|
||||
}
|
||||
|
||||
private Token matchPara(Token name) {
|
||||
Token current = peek();
|
||||
if (current.symbol == Symbol.PARA) {
|
||||
move();
|
||||
return current;
|
||||
}
|
||||
throw new ParseException("Can not match the parameter of directive #" + name.value(), getLocation(name.row));
|
||||
}
|
||||
|
||||
private void matchEnd(Token name) {
|
||||
if (peek().symbol == Symbol.END) {
|
||||
move();
|
||||
return ;
|
||||
}
|
||||
throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row));
|
||||
}
|
||||
|
||||
public Stat parse() {
|
||||
tokenList = new Lexer(content, fileName).scan();
|
||||
tokenList.add(EOF);
|
||||
Stat statList = statList();
|
||||
if (peek() != EOF) {
|
||||
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
|
||||
}
|
||||
return statList;
|
||||
}
|
||||
|
||||
private StatList statList() {
|
||||
List<Stat> statList = new ArrayList<Stat>();
|
||||
while (true) {
|
||||
Stat stat = stat();
|
||||
if (stat == null) {
|
||||
break ;
|
||||
}
|
||||
|
||||
if (stat instanceof Define) {
|
||||
env.addFunction((Define)stat);
|
||||
continue ;
|
||||
}
|
||||
|
||||
// 过滤内容为空的 Text 节点,通常是处于两个指令之间的空白字符被移除以后的结果,详见 TextToken.deleteBlankTails()
|
||||
if (stat instanceof Text && ((Text)stat).isEmpty()) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
statList.add(stat);
|
||||
}
|
||||
return new StatList(statList);
|
||||
}
|
||||
|
||||
private Stat stat() {
|
||||
Token name = peek();
|
||||
switch (name.symbol) {
|
||||
case TEXT:
|
||||
move();
|
||||
return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row));
|
||||
case OUTPUT:
|
||||
move();
|
||||
Token para = matchPara(name);
|
||||
Location loc = getLocation(name.row);
|
||||
return env.getEngineConfig().getOutputDirective(parseExprList(para), loc).setLocation(loc);
|
||||
case INCLUDE:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new Include(env, parseExprList(para), fileName, getLocation(name.row));
|
||||
case FOR:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
StatList statList = statList();
|
||||
Stat _else = null;
|
||||
if (peek().symbol == Symbol.ELSE) {
|
||||
move();
|
||||
StatList elseStats = statList();
|
||||
_else = new Else(elseStats);
|
||||
}
|
||||
matchEnd(name);
|
||||
return new For(parseForCtrl(para), statList, _else).setLocation(getLocation(name.row));
|
||||
case IF:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
statList = statList();
|
||||
Stat ret = new If(parseExprList(para), statList, getLocation(name.row));
|
||||
|
||||
Stat current = ret;
|
||||
for (Token elseIfToken=peek(); elseIfToken.symbol == Symbol.ELSEIF; elseIfToken=peek()) {
|
||||
move();
|
||||
para = matchPara(elseIfToken);
|
||||
statList = statList();
|
||||
Stat elseIf = new ElseIf(parseExprList(para), statList, getLocation(elseIfToken.row));
|
||||
current.setStat(elseIf);
|
||||
current = elseIf;
|
||||
}
|
||||
if (peek().symbol == Symbol.ELSE) {
|
||||
move();
|
||||
statList = statList();
|
||||
_else = new Else(statList);
|
||||
current.setStat(_else);
|
||||
}
|
||||
matchEnd(name);
|
||||
return ret;
|
||||
case DEFINE:
|
||||
String functionName = name.value();
|
||||
move();
|
||||
para = matchPara(name);
|
||||
Stat stat = statList();
|
||||
matchEnd(name);
|
||||
return new Define(functionName, parseExprList(para), stat, getLocation(name.row));
|
||||
case CALL:
|
||||
functionName = name.value();
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new Call(functionName, parseExprList(para), false).setLocation(getLocation(name.row));
|
||||
case CALL_IF_DEFINED:
|
||||
functionName = name.value();
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new Call(functionName, parseExprList(para), true).setLocation(getLocation(name.row));
|
||||
case SET:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new Set(parseExprList(para), getLocation(name.row));
|
||||
case SET_LOCAL:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new SetLocal(parseExprList(para), getLocation(name.row));
|
||||
case SET_GLOBAL:
|
||||
move();
|
||||
para = matchPara(name);
|
||||
return new SetGlobal(parseExprList(para), getLocation(name.row));
|
||||
case CONTINUE:
|
||||
move();
|
||||
return Continue.me;
|
||||
case BREAK:
|
||||
move();
|
||||
return Break.me;
|
||||
case RETURN:
|
||||
move();
|
||||
return Return.me;
|
||||
case ID:
|
||||
Stat dire = env.getEngineConfig().getDirective(name.value());
|
||||
if (dire == null) {
|
||||
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
|
||||
}
|
||||
ret = createDirective(dire, name).setLocation(getLocation(name.row));
|
||||
move();
|
||||
para = matchPara(name);
|
||||
ret.setExprList(parseExprList(para));
|
||||
|
||||
if (dire.hasEnd()) {
|
||||
statList = statList();
|
||||
ret.setStat(statList);
|
||||
matchEnd(name);
|
||||
}
|
||||
return ret;
|
||||
case PARA:
|
||||
case ELSEIF:
|
||||
case ELSE:
|
||||
case END:
|
||||
case EOF:
|
||||
return null;
|
||||
default :
|
||||
throw new ParseException("Syntax error: can not match the token: " + name.value(), getLocation(name.row));
|
||||
}
|
||||
}
|
||||
|
||||
private Location getLocation(int row) {
|
||||
return new Location(fileName, row);
|
||||
}
|
||||
|
||||
private Stat createDirective(Stat dire, Token name) {
|
||||
try {
|
||||
return dire.getClass().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), getLocation(name.row), e);
|
||||
}
|
||||
}
|
||||
|
||||
private ExprList parseExprList(Token paraToken) {
|
||||
return new ExprParser((ParaToken)paraToken, env.getEngineConfig(), fileName).parseExprList();
|
||||
}
|
||||
|
||||
private ForCtrl parseForCtrl(Token paraToken) {
|
||||
return new ExprParser((ParaToken)paraToken, env.getEngineConfig(), fileName).parseForCtrl();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
235
src/main/java/com/jfinal/template/stat/Scope.java
Normal file
235
src/main/java/com/jfinal/template/stat/Scope.java
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Scope
|
||||
* 1:顶层 scope.parent 为 null
|
||||
* 2:scope.set(...) 自内向外查找赋值
|
||||
* 3:scope.get(...) 自内向外查找获取
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class Scope {
|
||||
|
||||
private final Scope parent;
|
||||
private final Ctrl ctrl;
|
||||
private Map data;
|
||||
private Map<String, Object> sharedObjectMap;
|
||||
|
||||
/**
|
||||
* 构建顶层 Scope, parent 为 null 是顶层 Scope 的标志
|
||||
* @param data 用于在模板中使用的数据,data 支持 null 值
|
||||
* @param sharedObjectMap 共享对象
|
||||
*/
|
||||
public Scope(Map data, Map<String, Object> sharedObjectMap) {
|
||||
this.parent = null;
|
||||
this.ctrl = new Ctrl();
|
||||
this.data = data;
|
||||
this.sharedObjectMap = sharedObjectMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 AST 执行过程中作用域栈
|
||||
*/
|
||||
public Scope(Scope parent) {
|
||||
if (parent == null) {
|
||||
throw new IllegalArgumentException("parent can not be null.");
|
||||
}
|
||||
this.parent = parent;
|
||||
this.ctrl = parent.ctrl;
|
||||
this.data = null;
|
||||
this.sharedObjectMap = parent.sharedObjectMap;
|
||||
}
|
||||
|
||||
public Ctrl getCtrl() {
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置变量
|
||||
* 自内向外在作用域栈中查找变量,如果找到则改写变量值,否则将变量存放到顶层 Scope
|
||||
*/
|
||||
public void set(Object key, Object value) {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
// HashMap 允许有 null 值 value,必须要做 containsKey 判断
|
||||
if (cur.data != null && cur.data.containsKey(key)) {
|
||||
cur.data.put(key, value);
|
||||
return ;
|
||||
}
|
||||
|
||||
if (cur.parent == null) {
|
||||
if (cur.data == null) { // 支持顶层 data 为 null 值
|
||||
cur.data = new HashMap();
|
||||
}
|
||||
cur.data.put(key, value);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变量
|
||||
* 自内向外在作用域栈中查找变量,返回最先找到的变量
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
// return null;
|
||||
return sharedObjectMap != null ? sharedObjectMap.get(key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除变量
|
||||
* 自内向外在作用域栈中查找变量,移除最先找到的变量
|
||||
*/
|
||||
public void remove(Object key) {
|
||||
for (Scope cur=this; cur!=null; cur=cur.parent) {
|
||||
if (cur.data != null && cur.data.containsKey(key)) {
|
||||
cur.data.remove(key);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置局部变量
|
||||
*/
|
||||
public void setLocal(Object key, Object value) {
|
||||
if (data == null) {
|
||||
data = new HashMap();
|
||||
}
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取局部变量
|
||||
*/
|
||||
public Object getLocal(Object key) {
|
||||
return data != null ? data.get(key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除局部变量
|
||||
*/
|
||||
public void removeLocal(Object key) {
|
||||
if (data != null) {
|
||||
data.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置全局变量
|
||||
* 全局作用域是指本次请求的整个 template
|
||||
*/
|
||||
public void setGlobal(Object key, Object value) {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
if (cur.parent == null) {
|
||||
cur.data.put(key, value);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局变量
|
||||
* 全局作用域是指本次请求的整个 template
|
||||
*/
|
||||
public Object getGlobal(Object key) {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
if (cur.parent == null) {
|
||||
return cur.data.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全局变量
|
||||
* 全局作用域是指本次请求的整个 template
|
||||
*/
|
||||
public void removeGlobal(Object key) {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
if (cur.parent == null) {
|
||||
cur.data.remove(key);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自内向外在作用域栈中查找变量,获取变量所在的 Map,主要用于 IncDec
|
||||
*/
|
||||
public Map getMapOfValue(Object key) {
|
||||
for (Scope cur=this; cur!=null; cur=cur.parent) {
|
||||
if (cur.data != null && cur.data.containsKey(key)) {
|
||||
return cur.data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本层作用域 data,可能为 null 值
|
||||
*/
|
||||
public Map getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置/替换本层作用域 data,通常用于在扩展指令中使用现成可用的 Map 来存放数据,
|
||||
* 从而避免 Scope 内部创建 data,节省时空
|
||||
*
|
||||
* 注意:本方法会替换掉已经存在的 data 对象
|
||||
*/
|
||||
public void setData(Map data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取顶层作用域 data,可能为 null 值
|
||||
*/
|
||||
public Map getRootData() {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
if (cur.parent == null) {
|
||||
return cur.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置/替换顶层作用域 data,可以在扩展指令之中通过此方法切换掉顶层作用域
|
||||
* 实现作用域完全隔离的功能
|
||||
*
|
||||
* 注意:本方法会替换掉顶层已经存在的 data 对象
|
||||
*/
|
||||
public void setRootData(Map data) {
|
||||
for (Scope cur=this; true; cur=cur.parent) {
|
||||
if (cur.parent == null) {
|
||||
cur.data = data;
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
106
src/main/java/com/jfinal/template/stat/Symbol.java
Normal file
106
src/main/java/com/jfinal/template/stat/Symbol.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Symbol
|
||||
*/
|
||||
enum Symbol {
|
||||
|
||||
TEXT("text", false),
|
||||
|
||||
OUTPUT("output", true),
|
||||
|
||||
DEFINE("define", true),
|
||||
CALL("call", true),
|
||||
CALL_IF_DEFINED("callIfDefined", true),
|
||||
SET("set", true),
|
||||
SET_LOCAL("setLocal", true),
|
||||
SET_GLOBAL("setGlobal", true),
|
||||
INCLUDE("include", true),
|
||||
|
||||
FOR("for", true),
|
||||
IF("if", true),
|
||||
ELSEIF("elseif", true),
|
||||
ELSE("else", false),
|
||||
END("end", false),
|
||||
CONTINUE("continue", false),
|
||||
BREAK("break", false),
|
||||
RETURN("return", false),
|
||||
|
||||
ID("ID", false), // 标识符:下划线或字母开头 ^[A-Za-z_][A-Za-z0-9_]*$
|
||||
PARA("PARA", false),
|
||||
|
||||
EOF("EOF", false);
|
||||
|
||||
private final String name;
|
||||
private final boolean hasPara; // 是否有参
|
||||
|
||||
/**
|
||||
* Lexer 中确定为系统指令以后,必须得到正确的后续 Token 序列,否则报异常
|
||||
* 扩展指令在得到 # id ( 序列以后才要求得到正确的后续 Token 序列,否则仅仅 return fail()
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<String, Symbol> keywords = new HashMap<String, Symbol>() {{
|
||||
put(Symbol.IF.getName(), IF);
|
||||
put(Symbol.ELSEIF.getName(), ELSEIF);
|
||||
put(Symbol.ELSE.getName(), ELSE);
|
||||
put(Symbol.END.getName(), END);
|
||||
put(Symbol.FOR.getName(), FOR);
|
||||
put(Symbol.BREAK.getName(), BREAK);
|
||||
put(Symbol.CONTINUE.getName(), CONTINUE);
|
||||
put(Symbol.RETURN.getName(), RETURN);
|
||||
|
||||
put(Symbol.DEFINE.getName(), DEFINE);
|
||||
put(Symbol.SET.getName(), SET);
|
||||
put(Symbol.SET_LOCAL.getName(), SET_LOCAL);
|
||||
put(Symbol.SET_GLOBAL.getName(), SET_GLOBAL);
|
||||
put(Symbol.INCLUDE.getName(), INCLUDE);
|
||||
}};
|
||||
|
||||
private Symbol(String name, boolean hasPara) {
|
||||
this.name = name;
|
||||
this.hasPara = hasPara;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
boolean hasPara() {
|
||||
return hasPara;
|
||||
}
|
||||
|
||||
boolean noPara() {
|
||||
return !hasPara;
|
||||
}
|
||||
|
||||
public static Symbol getKeywordSym(String name) {
|
||||
return keywords.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
91
src/main/java/com/jfinal/template/stat/TextToken.java
Normal file
91
src/main/java/com/jfinal/template/stat/TextToken.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* TextToken
|
||||
* 词法分析时,合并相邻 TextToken
|
||||
*/
|
||||
class TextToken extends Token {
|
||||
|
||||
// 接管父类的 value
|
||||
private StringBuilder text;
|
||||
|
||||
public TextToken(StringBuilder value, int row) {
|
||||
super(Symbol.TEXT, row);
|
||||
this.text = value;
|
||||
}
|
||||
|
||||
public void append(StringBuilder content) {
|
||||
if (content != null) {
|
||||
text.append(content); // 不要使用 toString(),性能不如直接这样快
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1:当前指令"后方"全是空白字符并且以 '\n' 或 EOF 结尾,当前指令"前方"为 TextToken 时调用此方法
|
||||
* 2:当前指令本行内前方为空白字符(必须遭遇 '\n'),则删掉前方的空白字符
|
||||
* 3:当前指令前方全为空白字符(不含 '\n'),表明是两个指令之间全为空白字符的情况,
|
||||
* 或者两指令不在同一行且第二个指令前方全是空白字符的情况,则删掉这两指令之间的全部空白字符
|
||||
* 4:返回 true,告知调用方需要吃掉本指令行尾的 '\n'
|
||||
*
|
||||
* 简单描述:
|
||||
* 1:当前指令独占一行,删除当前指令前方空白字符,并告知调用方吃掉行尾 '\n'
|
||||
* 2:当前指令前方仍然是指令,两指令之间有空白字符,吃掉前方(即所有)的空白字符,并告知调用方吃掉行尾 '\n'
|
||||
* 3:情况 2 时,相当于本 TextToken 内容变成了空字符串,后续的 Parser 将过滤掉这类节点
|
||||
*/
|
||||
public boolean deleteBlankTails() {
|
||||
for (int i = text.length() - 1; i >= 0; i--) {
|
||||
if (CharTable.isBlank(text.charAt(i))) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
if (text.charAt(i) == '\n') {
|
||||
text.delete(i+1, text.length());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 两个指令之间全是空白字符, 设置其长度为 0,为 Parser 过滤内容为空的 Text 节点做准备
|
||||
text.setLength(0);
|
||||
return true; // 当两指令之间全为空白字符时,告知调用方需要吃掉行尾的 '\n'
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public void print() {
|
||||
System.out.print("[");
|
||||
System.out.print(row);
|
||||
System.out.print(", TEXT, ");
|
||||
System.out.print(text.toString());
|
||||
System.out.println("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
72
src/main/java/com/jfinal/template/stat/Token.java
Normal file
72
src/main/java/com/jfinal/template/stat/Token.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Token
|
||||
*/
|
||||
class Token {
|
||||
|
||||
final Symbol symbol;
|
||||
final int row;
|
||||
private final String value;
|
||||
|
||||
Token(Symbol symbol, String value, int row) {
|
||||
if (symbol == null || value == null) {
|
||||
throw new IllegalArgumentException("symbol and value can not be null");
|
||||
}
|
||||
this.symbol = symbol;
|
||||
this.value = value;
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
Token(Symbol symbol, int row) {
|
||||
this(symbol, symbol.getName(), row);
|
||||
}
|
||||
|
||||
boolean hasPara() {
|
||||
return symbol.hasPara();
|
||||
}
|
||||
|
||||
boolean noPara() {
|
||||
return symbol.noPara();
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public void print() {
|
||||
System.out.print("[");
|
||||
System.out.print(row);
|
||||
System.out.print(", ");
|
||||
System.out.print(symbol.getName());
|
||||
System.out.print(", ");
|
||||
System.out.print(value());
|
||||
System.out.println("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Break.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Break.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Break
|
||||
* java 中 break、continue 可出现在 for 中的最后一行,不一定要套在 if 中
|
||||
*/
|
||||
public class Break extends Stat {
|
||||
|
||||
public static final Break me = new Break();
|
||||
|
||||
private Break() {
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope.getCtrl().setBreak();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
57
src/main/java/com/jfinal/template/stat/ast/Call.java
Normal file
57
src/main/java/com/jfinal/template/stat/ast/Call.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Call 调用模板函数,两种用法:
|
||||
* 1:常规调用
|
||||
* #@funcName(p1, p2, ..., pn)
|
||||
* 2:安全调用,函数被定义才调用,否则跳过
|
||||
* #@funcName?(p1, p2, ..., pn)
|
||||
*
|
||||
* 注意:在函数名前面引入 '@' 字符是为了区分模板函数和指令
|
||||
*/
|
||||
public class Call extends Stat {
|
||||
|
||||
private String funcName;
|
||||
private ExprList exprList;
|
||||
private boolean callIfDefined;
|
||||
|
||||
public Call(String funcName, ExprList exprList, boolean callIfDefined) {
|
||||
this.funcName = funcName;
|
||||
this.exprList = exprList;
|
||||
this.callIfDefined = callIfDefined;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Define function = env.getFunction(funcName);
|
||||
if (function != null) {
|
||||
function.call(env, scope, exprList, writer);
|
||||
} else if (callIfDefined) {
|
||||
return ;
|
||||
} else {
|
||||
throw new TemplateException("Template function not defined: " + funcName, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Continue.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Continue.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Continue
|
||||
*/
|
||||
public class Continue extends Stat {
|
||||
|
||||
public static final Continue me = new Continue();
|
||||
|
||||
private Continue() {
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope.getCtrl().setContinue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
141
src/main/java/com/jfinal/template/stat/ast/Define.java
Normal file
141
src/main/java/com/jfinal/template/stat/ast/Define.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Define 定义模板函数:
|
||||
* #define funcName(p1, p2, ..., pn)
|
||||
* body
|
||||
* #end
|
||||
*
|
||||
* 模板函数类型:
|
||||
* 1:全局共享的模板函数
|
||||
* 通过 engine.addSharedFunction(...) 添加,所有模板中可调用
|
||||
* 2:模板中定义的局部模板函数
|
||||
* 在模板中定义的模板函数,只在本模板中有效
|
||||
*
|
||||
* 高级用法:
|
||||
* 1:局部模板函数可以与全局共享模板函数同名,调用时优先调用模板内模板数
|
||||
* 2:模板内部不能定义同名的局部模板函数
|
||||
*/
|
||||
public class Define extends Stat {
|
||||
|
||||
private static final String[] NULL_PARAMETER_NAMES = new String[0];
|
||||
|
||||
private String functionName;
|
||||
private String[] parameterNames;
|
||||
private Stat stat;
|
||||
|
||||
public Define(String functionName, ExprList exprList, Stat stat, Location location) {
|
||||
setLocation(location);
|
||||
this.functionName = functionName;
|
||||
this.stat = stat;
|
||||
|
||||
Expr[] exprArray = exprList.getExprArray();
|
||||
if (exprArray.length == 0) {
|
||||
this.parameterNames = NULL_PARAMETER_NAMES;
|
||||
return ;
|
||||
}
|
||||
|
||||
parameterNames = new String[exprArray.length];
|
||||
for (int i=0; i<exprArray.length; i++) {
|
||||
if (exprArray[i] instanceof Id) {
|
||||
parameterNames[i] = ((Id)exprArray[i]).getId();
|
||||
} else {
|
||||
throw new ParseException("The parameter of template function definition must be identifier", location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFunctionName() {
|
||||
return functionName;
|
||||
}
|
||||
|
||||
public String[] getParameterNames() {
|
||||
return parameterNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define 的继承类可以覆盖此方法实现一些 register 类的动作
|
||||
*/
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 真正调用模板函数
|
||||
*/
|
||||
public void call(Env env, Scope scope, ExprList exprList, Writer writer) {
|
||||
if (exprList.length() != parameterNames.length) {
|
||||
throw new TemplateException("Wrong number of argument to call the template function, right number is: " + parameterNames.length, location);
|
||||
}
|
||||
|
||||
scope = new Scope(scope);
|
||||
if (exprList.length() > 0) {
|
||||
Object[] parameterValues = exprList.evalExprList(scope);
|
||||
for (int i=0; i<parameterValues.length; i++) {
|
||||
scope.setLocal(parameterNames[i], parameterValues[i]); // 参数赋值
|
||||
}
|
||||
}
|
||||
|
||||
stat.exec(env, scope, writer);
|
||||
scope.getCtrl().setJumpNone(); // #define 中的 return、continue、break 全部在此消化
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
ret.append("#define ").append(functionName).append("(");
|
||||
for (int i=0; i<parameterNames.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(parameterNames[i]);
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
/**
|
||||
* envForDevMode 属性性以及相关方法仅用于 devMode 判断当前 #define 指令所在资源是否被修改
|
||||
* 仅用于 EngineConfig 中处理 shared function 的逻辑
|
||||
*/
|
||||
private Env envForDevMode;
|
||||
|
||||
public void setEnvForDevMode(Env envForDevMode) {
|
||||
this.envForDevMode = envForDevMode;
|
||||
}
|
||||
|
||||
public boolean isSourceModifiedForDevMode() {
|
||||
if (envForDevMode == null) {
|
||||
throw new IllegalStateException("Check engine config: setDevMode(...) must be invoked before addSharedFunction(...)");
|
||||
}
|
||||
return envForDevMode.isSourceListModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Else.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Else.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Else
|
||||
*/
|
||||
public class Else extends Stat {
|
||||
|
||||
private Stat stat;
|
||||
|
||||
public Else(Stat stat) {
|
||||
this.stat = stat;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
stat.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
63
src/main/java/com/jfinal/template/stat/ast/ElseIf.java
Normal file
63
src/main/java/com/jfinal/template/stat/ast/ElseIf.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* ElseIf
|
||||
*/
|
||||
public class ElseIf extends Stat {
|
||||
|
||||
private ExprList cond;
|
||||
private Stat stat;
|
||||
private Stat elseIfOrElse;
|
||||
|
||||
public ElseIf(ExprList cond, Stat stat, Location location) {
|
||||
if (cond.length() == 0) {
|
||||
throw new ParseException("The condition expression of #elseif statement can not be blank", location);
|
||||
}
|
||||
this.cond = cond;
|
||||
this.stat = stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* take over setStat(...) method of super class
|
||||
*/
|
||||
public void setStat(Stat elseIfOrElse) {
|
||||
this.elseIfOrElse = elseIfOrElse;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
if (Logic.isTrue(cond.eval(scope))) {
|
||||
stat.exec(env, scope, writer);
|
||||
} else if (elseIfOrElse != null) {
|
||||
elseIfOrElse.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
140
src/main/java/com/jfinal/template/stat/ast/For.java
Normal file
140
src/main/java/com/jfinal/template/stat/ast/For.java
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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 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.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* For 循环控制,支持 List、Map、数组、Collection、Iterator、Iterable
|
||||
* Enumeration、null 以及任意单个对象的迭代,简单说是支持所有对象迭代
|
||||
*
|
||||
* 主要用法:
|
||||
* 1:#for(item : list) #(item) #end
|
||||
* 2:#for(item : list) #(item) #else content #end
|
||||
* 3:#for(i=0; i<9; i++) #(item) #end
|
||||
* 4:#for(i=0; i<9; i++) #(item) #else content #end
|
||||
*/
|
||||
public class For extends Stat {
|
||||
|
||||
private ForCtrl forCtrl;
|
||||
private StatList statList;
|
||||
private Stat _else;
|
||||
|
||||
public For(ForCtrl forCtrl, StatList statList, Stat _else) {
|
||||
this.forCtrl = forCtrl;
|
||||
this.statList = statList;
|
||||
this._else = _else;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope = new Scope(scope);
|
||||
if (forCtrl.isIterator()) {
|
||||
forIterator(env, scope, writer);
|
||||
} else {
|
||||
forLoop(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #for( id : expr)
|
||||
*/
|
||||
private void forIterator(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
Object outer = scope.get("for");
|
||||
ctrl.setLocalAssignment();
|
||||
ForIteratorStatus forIteratorStatus = new ForIteratorStatus(outer, forCtrl.getExpr().eval(scope), location);
|
||||
ctrl.setWisdomAssignment();
|
||||
scope.setLocal("for", forIteratorStatus);
|
||||
|
||||
Iterator<?> it = forIteratorStatus.getIterator();
|
||||
String itemName = forCtrl.getId();
|
||||
while(it.hasNext()) {
|
||||
scope.setLocal(itemName, it.next());
|
||||
statList.exec(env, scope, writer);
|
||||
forIteratorStatus.nextState();
|
||||
|
||||
if (ctrl.isJump()) {
|
||||
if (ctrl.isBreak()) {
|
||||
ctrl.setJumpNone();
|
||||
break ;
|
||||
} else if (ctrl.isContinue()) {
|
||||
ctrl.setJumpNone();
|
||||
continue ;
|
||||
} else {
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_else != null && forIteratorStatus.getIndex() == 0) {
|
||||
_else.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #for(exprList; cond; update)
|
||||
*/
|
||||
private void forLoop(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
Object outer = scope.get("for");
|
||||
ForLoopStatus forLoopStatus = new ForLoopStatus(outer);
|
||||
scope.setLocal("for", forLoopStatus);
|
||||
|
||||
Expr init = forCtrl.getInit();
|
||||
Expr cond = forCtrl.getCond();
|
||||
Expr update = forCtrl.getUpdate();
|
||||
|
||||
ctrl.setLocalAssignment();
|
||||
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
|
||||
ctrl.setWisdomAssignment();
|
||||
statList.exec(env, scope, writer);
|
||||
ctrl.setLocalAssignment();
|
||||
forLoopStatus.nextState();
|
||||
|
||||
if (ctrl.isJump()) {
|
||||
if (ctrl.isBreak()) {
|
||||
ctrl.setJumpNone();
|
||||
break ;
|
||||
} else if (ctrl.isContinue()) {
|
||||
ctrl.setJumpNone();
|
||||
continue ;
|
||||
} else {
|
||||
ctrl.setWisdomAssignment();
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.setWisdomAssignment();
|
||||
if (_else != null && forLoopStatus.getIndex() == 0) {
|
||||
_else.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
46
src/main/java/com/jfinal/template/stat/ast/ForEntry.java
Normal file
46
src/main/java/com/jfinal/template/stat/ast/ForEntry.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* ForEntry 包装 HashMap、LinkedHashMap 等 Map 类型的 Entry 对象
|
||||
*/
|
||||
public class ForEntry implements Entry<Object, Object> {
|
||||
|
||||
private Entry<Object, Object> entry;
|
||||
|
||||
public ForEntry(Entry<Object, Object> entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public Object setValue(Object value) {
|
||||
return entry.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
|
||||
/**
|
||||
* ForIteratorStatus
|
||||
* 封装 #for( id : expr) 迭代语句状态,便于模板中获取
|
||||
*
|
||||
* 使用以下表达式可以模板中获取迭代状态:
|
||||
* for.size 被迭代集合元素数量,不支持 Iterator 与 Iterable
|
||||
* for.index 从 0 下始的下标
|
||||
* for.count 从 1 开始的计数器
|
||||
* for.first 是否第一个元素
|
||||
* for.last 是否最后一个元素
|
||||
* for.odd 是否第奇数个元素
|
||||
* for.even 是否第偶数个元素
|
||||
* for.outer 获取外层 for 对象,便于获取外层 for 循环状态
|
||||
* 例如: for.outer.index
|
||||
*/
|
||||
public class ForIteratorStatus {
|
||||
|
||||
private Object outer;
|
||||
private int index;
|
||||
private int size;
|
||||
private Iterator<?> iterator;
|
||||
private Location location;
|
||||
|
||||
public ForIteratorStatus(Object outer, Object target, Location location) {
|
||||
this.outer = outer;
|
||||
this.index = 0;
|
||||
this.location = location;
|
||||
init(target);
|
||||
}
|
||||
|
||||
@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();
|
||||
return ;
|
||||
}
|
||||
if (target instanceof Map<?, ?>) {
|
||||
size = ((Map<?, ?>)target).size();
|
||||
iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator());
|
||||
return ;
|
||||
}
|
||||
if (target.getClass().isArray()) {
|
||||
size = Array.getLength(target);
|
||||
iterator = new ArrayIterator(target, size);
|
||||
return ;
|
||||
}
|
||||
if (target instanceof Iterator) {
|
||||
size = -1;
|
||||
iterator = (Iterator<?>)target;
|
||||
return ;
|
||||
}
|
||||
if (target instanceof Iterable) {
|
||||
size = -1;
|
||||
iterator = ((Iterable<?>)target).iterator();
|
||||
return ;
|
||||
}
|
||||
if (target instanceof Enumeration) {
|
||||
ArrayList<?> list = Collections.list((Enumeration<?>)target);
|
||||
size = list.size();
|
||||
iterator = list.iterator();
|
||||
return ;
|
||||
}
|
||||
|
||||
size = 1;
|
||||
iterator = new SingleObjectIterator(target);
|
||||
}
|
||||
|
||||
Iterator<?> getIterator() {
|
||||
return iterator;
|
||||
}
|
||||
|
||||
void nextState() {
|
||||
index++;
|
||||
}
|
||||
|
||||
public Object getOuter() {
|
||||
return outer;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
if (size >= 0) {
|
||||
return size;
|
||||
}
|
||||
throw new TemplateException("No such method getSize() of the iterator", location);
|
||||
}
|
||||
|
||||
public boolean getFirst() {
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
public boolean getLast() {
|
||||
return !iterator.hasNext();
|
||||
}
|
||||
|
||||
public boolean getOdd() {
|
||||
return index % 2 == 0;
|
||||
}
|
||||
|
||||
public boolean getEven() {
|
||||
return index % 2 != 0;
|
||||
}
|
||||
}
|
||||
|
||||
class MapIterator implements Iterator<Entry<Object, Object>> {
|
||||
|
||||
private Iterator<Entry<Object, Object>> iterator;
|
||||
|
||||
public MapIterator(Iterator<Entry<Object, Object>> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
public Entry<Object, Object> next() {
|
||||
return new ForEntry((Entry<Object, Object>)iterator.next());
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIterator implements Iterator<Object> {
|
||||
|
||||
private Object array;
|
||||
private int size;
|
||||
private int index;
|
||||
|
||||
ArrayIterator(Object array, int size) {
|
||||
this.array = array;
|
||||
this.size = size;
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return index < size;
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
return Array.get(array, index++);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
class SingleObjectIterator implements Iterator<Object> {
|
||||
|
||||
private Object target;
|
||||
private boolean hasNext = true;
|
||||
|
||||
public SingleObjectIterator(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
if (hasNext) {
|
||||
hasNext = false;
|
||||
return target;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
class NullIterator implements Iterator<Object> {
|
||||
|
||||
static final Iterator<?> me = new NullIterator();
|
||||
|
||||
private NullIterator() {
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* ForLoopStatus
|
||||
* 封装 #for( init; cond; update) 循环的状态,便于模板中获取
|
||||
*
|
||||
* 如下表达式可从模板中获取循环状态:
|
||||
* for.index 从 0 下始的下标
|
||||
* for.count 从 1 开始的计数器
|
||||
* for.first 是否第一个元素
|
||||
* for.odd 是否第奇数个元素
|
||||
* for.even 是否第偶数个元素
|
||||
* for.outer 获取外层 for 对象,便于获取外层 for 循环状态
|
||||
* 例如: for.outer.index
|
||||
*
|
||||
* 注意:比迭代型循环语句少支持两个状态取值表达式:for.size、for.last
|
||||
*/
|
||||
public class ForLoopStatus {
|
||||
|
||||
private Object outer;
|
||||
private int index;
|
||||
|
||||
public ForLoopStatus(Object outer) {
|
||||
this.outer = outer;
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
void nextState() {
|
||||
index++;
|
||||
}
|
||||
|
||||
public Object getOuter() {
|
||||
return outer;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
public boolean getFirst() {
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
public boolean getOdd() {
|
||||
return index % 2 == 0;
|
||||
}
|
||||
|
||||
public boolean getEven() {
|
||||
return index % 2 != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
61
src/main/java/com/jfinal/template/stat/ast/If.java
Normal file
61
src/main/java/com/jfinal/template/stat/ast/If.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.expr.ast.Logic;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* If
|
||||
*/
|
||||
public class If extends Stat {
|
||||
|
||||
private ExprList cond;
|
||||
private Stat stat;
|
||||
private Stat elseIfOrElse;
|
||||
|
||||
public If(ExprList cond, Stat stat, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* take over setStat(...) method of super class
|
||||
*/
|
||||
public void setStat(Stat elseIfOrElse) {
|
||||
this.elseIfOrElse = elseIfOrElse;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
if (Logic.isTrue(cond.eval(scope))) {
|
||||
stat.exec(env, scope, writer);
|
||||
} else if (elseIfOrElse != null) {
|
||||
elseIfOrElse.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
161
src/main/java/com/jfinal/template/stat/ast/Include.java
Normal file
161
src/main/java/com/jfinal/template/stat/ast/Include.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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 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.source.ISource;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Parser;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Include
|
||||
*
|
||||
* 1:父模板被缓存时,被 include 的模板会被间接缓存,无需关心缓存问题
|
||||
* 2:同一个模板文件被多个父模板 include,所处的背景环境不同,例如各父模板中定义的模板函数不同
|
||||
* 各父模板所处的相对路径不同,所以多个父模板不能共用一次 parse 出来的结果,而是在每个被include
|
||||
* 的地方重新 parse
|
||||
*
|
||||
* <pre>
|
||||
* 两种用法:
|
||||
* 1:只传入一个参数,参数必须是 String 常量,如果希望第一个参数是变量可以使用 #render 指令去实现
|
||||
* #include("_hot.html")
|
||||
*
|
||||
* 2:传入任意多个参数,除第一个参数以外的所有参数必须是赋值表达式,用于实现参数传递功能
|
||||
* #include("_hot.html", title = "热门新闻", list = newsList)
|
||||
*
|
||||
* 上例中传递了 title、list 两个参数,可以代替父模板中的 #set 指令传参方式
|
||||
* 并且此方式传入的参数只在子模板作用域有效,不会污染父模板作用域
|
||||
*
|
||||
* 这种传参方式有利于将子模板模块化,例如上例的调用改成如下的参数:
|
||||
* #include("_hot.html", title = "热门项目", list = projectList)
|
||||
* 通过这种传参方式在子模板 _hot.html 之中,完全不需要修改对于 title 与 list
|
||||
* 这两个变量的处理代码,就实现了对 “热门项目” 数据的渲染
|
||||
* </pre>
|
||||
*/
|
||||
public class Include extends Stat {
|
||||
|
||||
private Assign[] assignArray;
|
||||
private Stat stat;
|
||||
|
||||
public Include(Env env, ExprList exprList, String parentFileName, Location location) {
|
||||
int len = exprList.length();
|
||||
if (len == 0) {
|
||||
throw new ParseException("The parameter of #include directive can not be blank", location);
|
||||
}
|
||||
// 第一个参数必须为 String 类型
|
||||
Expr expr = exprList.getExpr(0);
|
||||
if (expr instanceof Const && ((Const)expr).isStr()) {
|
||||
} else {
|
||||
throw new ParseException("The first parameter of #include directive must be String", location);
|
||||
}
|
||||
// 其它参数必须为赋值表达式
|
||||
if (len > 1) {
|
||||
for (int i = 1; i < len; i++) {
|
||||
if (!(exprList.getExpr(i) instanceof Assign)) {
|
||||
throw new ParseException("The " + i + "th parameter of #include directive must be an assignment expression", location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseSubTemplate(env, ((Const)expr).getStr(), parentFileName, location);
|
||||
getAssignExpression(exprList);
|
||||
}
|
||||
|
||||
private void parseSubTemplate(Env env, String fileName, String parentFileName, Location location) {
|
||||
String subFileName = getSubFileName(fileName, parentFileName);
|
||||
EngineConfig config = env.getEngineConfig();
|
||||
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||||
ISource fileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||||
try {
|
||||
Parser parser = new Parser(env, fileSource.getContent(), subFileName);
|
||||
if (config.isDevMode()) {
|
||||
env.addSource(fileSource);
|
||||
}
|
||||
this.stat = parser.parse();
|
||||
} catch (Exception e) {
|
||||
// 文件路径不正确抛出异常时添加 location 信息
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在父模板之下子模板的最终文件名,子模板目录相对于父模板文件目录来确定
|
||||
* 以 "/" 打头则以 baseTemplatePath 为根,否则以父文件所在路径为根
|
||||
*/
|
||||
public static String getSubFileName(String fileName, String parentFileName) {
|
||||
if (parentFileName == null) {
|
||||
return fileName;
|
||||
}
|
||||
if (fileName.startsWith("/")) {
|
||||
return fileName;
|
||||
}
|
||||
int index = parentFileName.lastIndexOf('/');
|
||||
if (index == -1) {
|
||||
return fileName;
|
||||
}
|
||||
return parentFileName.substring(0, index + 1) + fileName;
|
||||
}
|
||||
|
||||
private void getAssignExpression(ExprList exprList) {
|
||||
int len = exprList.length();
|
||||
if (len > 1) {
|
||||
assignArray = new Assign[len - 1];
|
||||
for (int i = 0; i < assignArray.length; i++) {
|
||||
assignArray[i] = (Assign)exprList.getExpr(i + 1);
|
||||
}
|
||||
} else {
|
||||
assignArray = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope = new Scope(scope);
|
||||
if (assignArray != null) {
|
||||
evalAssignExpression(scope);
|
||||
}
|
||||
stat.exec(env, scope, writer);
|
||||
scope.getCtrl().setJumpNone();
|
||||
}
|
||||
|
||||
private void evalAssignExpression(Scope scope) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setLocalAssignment();
|
||||
for (Assign assign : assignArray) {
|
||||
assign.eval(scope);
|
||||
}
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
63
src/main/java/com/jfinal/template/stat/ast/Output.java
Normal file
63
src/main/java/com/jfinal/template/stat/ast/Output.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.ExprList;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Output 输出指令
|
||||
*
|
||||
* 用法:
|
||||
* 1:#(value)
|
||||
* 2:#(x = 1, y = 2, x + y)
|
||||
* 3:#(seoTitle ?? 'JFinal 极速开发社区')
|
||||
*/
|
||||
public class Output extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
try {
|
||||
Object value = exprList.eval(scope);
|
||||
if (value != null) {
|
||||
String str = value.toString();
|
||||
writer.write(str, 0, str.length());
|
||||
}
|
||||
} catch(TemplateException e) {
|
||||
throw e;
|
||||
} catch(Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
44
src/main/java/com/jfinal/template/stat/ast/Return.java
Normal file
44
src/main/java/com/jfinal/template/stat/ast/Return.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.stat.ast;
|
||||
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Return
|
||||
* 通常用于 #define 指令内部,不支持返回值
|
||||
*/
|
||||
public class Return extends Stat {
|
||||
|
||||
public static final Return me = new Return();
|
||||
|
||||
private Return() {
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope.getCtrl().setReturn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
59
src/main/java/com/jfinal/template/stat/ast/Set.java
Normal file
59
src/main/java/com/jfinal/template/stat/ast/Set.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 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.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Set 赋值,从内向外作用域查找变量,找到则替换变量值,否则在顶层作用域赋值
|
||||
*
|
||||
* 用法:
|
||||
* 1:#set(k = v)
|
||||
* 2:#set(k1 = v1, k2 = v2, ..., kn = vn)
|
||||
* 3:#set(x = 1+2)
|
||||
* 4:#set(x = 1+2, y = 3>4, ..., z = c ? a : b)
|
||||
*/
|
||||
public class Set extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
|
||||
public Set(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
throw new ParseException("The parameter of #set directive can not be blank", location);
|
||||
}
|
||||
|
||||
for (Expr expr : exprList.getExprArray()) {
|
||||
if ( !(expr instanceof Assign) ) {
|
||||
throw new ParseException("#set directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
scope.getCtrl().setWisdomAssignment();
|
||||
exprList.eval(scope);
|
||||
}
|
||||
}
|
||||
|
65
src/main/java/com/jfinal/template/stat/ast/SetGlobal.java
Normal file
65
src/main/java/com/jfinal/template/stat/ast/SetGlobal.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jfinal.template.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.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* SetLocal 设置全局变量,全局作用域是指本次请求的整个 template
|
||||
*
|
||||
* 适用于极少数的在内层作用域中希望直接操作顶层作用域的场景
|
||||
*/
|
||||
public class SetGlobal extends Stat {
|
||||
|
||||
private ExprList exprList;
|
||||
|
||||
public SetGlobal(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
throw new ParseException("The parameter of #setGlobal directive can not be blank", location);
|
||||
}
|
||||
|
||||
for (Expr expr : exprList.getExprArray()) {
|
||||
if ( !(expr instanceof Assign) ) {
|
||||
throw new ParseException("#setGlobal directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setGlobalAssignment();
|
||||
exprList.eval(scope);
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
66
src/main/java/com/jfinal/template/stat/ast/SetLocal.java
Normal file
66
src/main/java/com/jfinal/template/stat/ast/SetLocal.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 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.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* SetLocal 设置局部变量
|
||||
*
|
||||
* 通常用于 #define #include 指令内部需要与外层作用域区分,以便于定义重用型模块的场景
|
||||
* 也常用于 #for 循环内部的临时变量
|
||||
*/
|
||||
public class SetLocal extends Stat {
|
||||
|
||||
final ExprList exprList;
|
||||
|
||||
public SetLocal(ExprList exprList, Location location) {
|
||||
if (exprList.length() == 0) {
|
||||
throw new ParseException("The parameter of #setLocal directive can not be blank", location);
|
||||
}
|
||||
|
||||
for (Expr expr : exprList.getExprArray()) {
|
||||
if ( !(expr instanceof Assign) ) {
|
||||
throw new ParseException("#setLocal directive only supports assignment expressions", location);
|
||||
}
|
||||
}
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
try {
|
||||
ctrl.setLocalAssignment();
|
||||
exprList.eval(scope);
|
||||
} finally {
|
||||
ctrl.setWisdomAssignment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
77
src/main/java/com/jfinal/template/stat/ast/Stat.java
Normal file
77
src/main/java/com/jfinal/template/stat/ast/Stat.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 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.stat.Location;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Stat
|
||||
*/
|
||||
public abstract class Stat {
|
||||
|
||||
protected Location location;
|
||||
|
||||
public Stat setLocation(Location location) {
|
||||
this.location = location;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setExprList(ExprList exprList) {
|
||||
}
|
||||
|
||||
public void setStat(Stat stat) {
|
||||
}
|
||||
|
||||
public abstract void exec(Env env, Scope scope, Writer writer);
|
||||
|
||||
public boolean hasEnd() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void write(Writer writer, String str) {
|
||||
try {
|
||||
writer.write(str, 0, str.length());
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(Writer writer, char[] chars) {
|
||||
try {
|
||||
writer.write(chars, 0, chars.length);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
64
src/main/java/com/jfinal/template/stat/ast/StatList.java
Normal file
64
src/main/java/com/jfinal/template/stat/ast/StatList.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 java.io.Writer;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* StatList
|
||||
*/
|
||||
public class StatList extends Stat {
|
||||
|
||||
public static final Stat[] NULL_STATS = 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;
|
||||
}
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
for (Stat stat : statArray) {
|
||||
if (ctrl.isJump()) {
|
||||
break ;
|
||||
}
|
||||
stat.exec(env, scope, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return statArray.length;
|
||||
}
|
||||
|
||||
public Stat getStat(int index) {
|
||||
if (index < 0 || index >= statArray.length) {
|
||||
throw new TemplateException("Index out of bounds: index = " + index + ", length = " + statArray.length, location);
|
||||
}
|
||||
return statArray[index];
|
||||
}
|
||||
}
|
||||
|
||||
|
59
src/main/java/com/jfinal/template/stat/ast/Text.java
Normal file
59
src/main/java/com/jfinal/template/stat/ast/Text.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import com.jfinal.template.Env;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Text 输出纯文本块以及使用 "#[[" 与 "]]#" 指定的非解析块
|
||||
*/
|
||||
public class Text extends Stat {
|
||||
|
||||
private char[] text;
|
||||
|
||||
public Text(StringBuilder content) {
|
||||
this.text = new char[content.length()];
|
||||
content.getChars(0, content.length(), this.text, 0);
|
||||
}
|
||||
|
||||
public void exec(Env env, Scope scope, Writer writer) {
|
||||
try {
|
||||
writer.write(text, 0, text.length);
|
||||
} catch (IOException e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return text.length == 0;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return text != null ? new String(text) : null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return text != null ? new String(text) : "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user