com.jfinal.template.Engine Maven / Gradle / Ivy
/**
* Copyright (c) 2011-2023, James Zhan 詹波 ([email protected]).
*
* 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.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit;
import com.jfinal.kit.SyncWriteMap;
import com.jfinal.template.expr.ast.FieldGetter;
import com.jfinal.template.expr.ast.FieldKeyBuilder;
import com.jfinal.template.expr.ast.FieldKit;
import com.jfinal.template.expr.ast.MethodKit;
import com.jfinal.template.io.EncoderFactory;
import com.jfinal.template.io.JdkEncoderFactory;
import com.jfinal.template.source.ClassPathSourceFactory;
import com.jfinal.template.source.ISource;
import com.jfinal.template.source.ISourceFactory;
import com.jfinal.template.source.StringSource;
import com.jfinal.template.stat.CharTable;
import com.jfinal.template.stat.Compressor;
import com.jfinal.template.stat.OutputDirectiveFactory;
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 engineMap = new HashMap(64, 0.5F);
// 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 boolean cacheStringTemplate = false;
private EngineConfig config = new EngineConfig();
private ISourceFactory sourceFactory = config.getSourceFactory();
private Map templateCache = new SyncWriteMap(2048, 0.5F);
/**
* 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;
}
/**
* Create engine if absent with engine name managed by JFinal
*
* Example:
* Engine engine = Engine.createIfAbsent("myEngine", e -> {
* e.setDevMode(true);
* e.setToClassPathSourceFactory();
* });
*
* engine.getTemplate("template.html").render(System.out);
* >
*/
public static Engine createIfAbsent(String engineName, Consumer e) {
Engine ret = engineMap.get(engineName);
if (ret == null) {
synchronized (Engine.class) {
ret = engineMap.get(engineName);
if (ret == null) {
ret = create(engineName);
e.accept(ret);
}
}
}
return ret;
}
/**
* Create engine with engine name managed by JFinal
*
* Example:
* Engine engine = Engine.create("myEngine", e -> {
* e.setDevMode(true);
* e.setToClassPathSourceFactory();
* });
*
* engine.getTemplate("template.html").render(System.out);
* >
*/
public static Engine create(String engineName, Consumer e) {
Engine ret = create(engineName);
e.accept(ret);
return ret;
}
/**
* 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 by 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
*/
public Template getTemplateByString(String content) {
return getTemplateByString(content, cacheStringTemplate);
}
/**
* Get template by string content
*
* 重要:StringSource 中的 cacheKey = HashKit.md5(content),也即 cacheKey
* 与 content 有紧密的对应关系,当 content 发生变化时 cacheKey 值也相应变化
* 因此,原先 cacheKey 所对应的 Template 缓存对象已无法被获取,当 getTemplateByString(String)
* 的 String 参数的数量不确定时会引发内存泄漏
*
* 当 getTemplateByString(String, boolean) 中的 String 参数的
* 数量可控并且确定时,才可对其使用缓存
*
* @param content 模板内容
* @param cache true 则缓存 Template,否则不缓存
*/
public Template getTemplateByString(String content, boolean cache) {
if (!cache) {
return buildTemplateBySource(new StringSource(content, cache));
}
String cacheKey = HashKit.md5(content);
Template template = templateCache.get(cacheKey);
if (template == null) {
template = buildTemplateBySource(new StringSource(content, cacheKey));
templateCache.put(cacheKey, template);
} else if (devMode) {
if (template.isModified()) {
template = buildTemplateBySource(new StringSource(content, cacheKey));
templateCache.put(cacheKey, template);
}
}
return template;
}
public Template getTemplateByString(String content, String cacheKey) {
if (cacheKey == null) {
return buildTemplateBySource(new StringSource(content, cacheKey));
}
Template template = templateCache.get(cacheKey);
if (template == null) {
template = buildTemplateBySource(new StringSource(content, cacheKey));
templateCache.put(cacheKey, template);
} else if (devMode) {
if (template.isModified()) {
template = buildTemplateBySource(new StringSource(content, cacheKey));
templateCache.put(cacheKey, template);
}
}
return template;
}
/**
* Get template by implementation of ISource
*/
public Template getTemplate(ISource source) {
String cacheKey = source.getCacheKey();
if (cacheKey == null) { // cacheKey 为 null 则不缓存,详见 ISource.getCacheKey() 注释
return buildTemplateBySource(source);
}
Template template = templateCache.get(cacheKey);
if (template == null) {
template = buildTemplateBySource(source);
templateCache.put(cacheKey, template);
} else if (devMode) {
if (template.isModified()) {
template = buildTemplateBySource(source);
templateCache.put(cacheKey, 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 by 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 by 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;
}
public Engine removeSharedObject(String name) {
config.removeSharedObject(name);
return this;
}
/**
* 添加枚举类型,便于在模板中使用
*
*
* 例子:
* 1:定义枚举类型
* public enum UserType {
*
* ADMIN,
* USER;
*
* public String hello() {
* return "hello";
* }
* }
*
* 2:配置
* engine.addEnum(UserType.class);
*
* 3:模板中使用
* ### 以下的对象 u 通过 Controller 中的 setAttr("u", UserType.ADMIN) 传递
* #if( u == UserType.ADMIN )
* #(UserType.ADMIN)
*
* #(UserType.ADMIN.name())
*
* #(UserType.ADMIN.hello())
* #end
*
*
*/
public Engine addEnum(Class extends Enum>> enumClass) {
Map> map = new java.util.LinkedHashMap<>();
Enum>[] es = enumClass.getEnumConstants();
for (Enum> e : es) {
map.put(e.name(), e);
}
return addSharedObject(enumClass.getSimpleName(), map);
}
/**
* Set output directive factory
*/
public Engine setOutputDirectiveFactory(OutputDirectiveFactory outputDirectiveFactory) {
config.setOutputDirectiveFactory(outputDirectiveFactory);
return this;
}
/**
* 添加自定义指令
*
* 建议添加自定义指令时明确指定 keepLineBlank 变量值,其规则如下:
* 1:keepLineBlank 为 true 时, 该指令所在行的前后空白字符以及末尾字符 '\n' 将会被保留
* 一般用于具有输出值的指令,例如 #date、#para 等指令
*
* 2:keepLineBlank 为 false 时,该指令所在行的前后空白字符以及末尾字符 '\n' 将会被删除
* 一般用于没有输出值的指令,例如 #for、#if、#else、#end 这种性质的指令
*
*
* 示例:
* addDirective("now", NowDirective.class, true)
*
*/
public Engine addDirective(String directiveName, Class extends Directive> directiveClass, boolean keepLineBlank) {
config.addDirective(directiveName, directiveClass, keepLineBlank);
return this;
}
/**
* 添加自定义指令,keepLineBlank 使用默认值
*/
public Engine addDirective(String directiveName, Class extends Directive> directiveClass) {
config.addDirective(directiveName, directiveClass);
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 by 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 by cache key
*/
public void removeTemplateCache(String cacheKey) {
templateCache.remove(cacheKey);
}
/**
* 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;
}
/**
* 配置是否缓存字符串模板,也即是否缓存通过 getTemplateByString(String content)
* 方法获取的模板,默认配置为 false
*/
public Engine setCacheStringTemplate(boolean cacheStringTemplate) {
this.cacheStringTemplate = cacheStringTemplate;
return this;
}
/**
* 设置 ISourceFactory 用于为 engine 切换不同的 ISource 实现类
* ISource 用于从不同的来源加载模板内容
*
*
* 配置为 ClassPathSourceFactory 时特别注意:
* 由于 JFinal 会在 configEngine(Engine me) 方法调用 “之前”,会默认调用一次如下方法:
* me.setBaseTemplatePath(PathKit.getWebRootPath())
*
* 而 ClassPathSourceFactory 在以上默认值下不能工作,所以需要通过如下方式清掉该值:
* me.setBaseTemplatePath(null)
*
* 或者配置具体要用的 baseTemplatePath 值,例如:
* me.setBaseTemplatePath("view");
*
*/
public Engine setSourceFactory(ISourceFactory sourceFactory) {
this.config.setSourceFactory(sourceFactory); // 放第一行先进行参数验证
this.sourceFactory = sourceFactory;
return this;
}
/**
* 设置为 ClassPathSourceFactory 的快捷方法
*/
public Engine setToClassPathSourceFactory() {
return setSourceFactory(new ClassPathSourceFactory());
}
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();
}
/**
* 设置 #number 指令与 Arith 中浮点数的舍入规则,默认为 RoundingMode.HALF_UP "四舍五入"
*/
public Engine setRoundingMode(RoundingMode roundingMode) {
config.setRoundingMode(roundingMode);
return this;
}
/**
* Enjoy 模板引擎对 UTF-8 的 encoding 做过性能优化,某些罕见字符
* 无法被编码,可以配置为 JdkEncoderFactory 解决问题:
* engine.setEncoderFactory(new JdkEncoderFactory());
*/
public Engine setEncoderFactory(EncoderFactory encoderFactory) {
config.setEncoderFactory(encoderFactory);
return this;
}
/**
* 配置为 JdkEncoderFactory,支持 utf8mb4,支持 emoji 表情字符,
* 支持各种罕见字符编码
*/
public Engine setToJdkEncoderFactory() {
config.setEncoderFactory(new JdkEncoderFactory());
return this;
}
public Engine setBufferSize(int bufferSize) {
config.setBufferSize(bufferSize);
return this;
}
public Engine setReentrantBufferSize(int reentrantBufferSize) {
config.setReentrantBufferSize(reentrantBufferSize);
return this;
}
/**
* 设置开启压缩功能
*
* @param separator 压缩使用的分隔符,常用配置为 '\n' 与 ' '。
* 如果模板中存在 javascript 脚本,需要配置为 '\n'
* 两种配置的压缩率是完全一样的
*/
public Engine setCompressorOn(char separator) {
return setCompressor(new Compressor(separator));
}
/**
* 设置开启压缩功能。压缩分隔符使用默认值 '\n'
*/
public Engine setCompressorOn() {
return setCompressor(new Compressor());
}
/**
* 配置 Compressor 可对模板中的静态内容进行压缩
*
* 可通过该方法配置自定义的 Compressor 来代替系统默认实现,例如:
* engine.setCompressor(new MyCompressor());
*/
public Engine setCompressor(Compressor compressor) {
config.setCompressor(compressor);
return this;
}
/**
* 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);
}
/**
* 添加 FieldGetter 实现类到指定的位置
*
* 系统当前默认 FieldGetter 实现类及其位置如下:
* GetterMethodFieldGetter ---> 调用 getter 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field 值
* ModelFieldGetter ---> 调用 Model.get(String) 方法取值
* RecordFieldGetter ---> 调用 Record.get(String) 方法取值
* MapFieldGetter ---> 调用 Map.get(String) 方法取值
* ArrayLengthGetter ---> 获取数组长度
*
* 根据以上次序,如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter
* 之后的代码如下:
* Engine.addFieldGetter(1, new IsMethodFieldGetter());
*
* 注:IsMethodFieldGetter 系统已经提供,只是默认没有启用。该实现类通过调用
* target.isXxx() 方法获取 target.xxx 表达式的值,其中 isXxx() 返回值
* 必须是 Boolean/boolean 类型才会被调用
*/
public static void addFieldGetter(int index, FieldGetter fieldGetter) {
FieldKit.addFieldGetter(index, fieldGetter);
}
public static void addFieldGetterToLast(FieldGetter fieldGetter) {
FieldKit.addFieldGetterToLast(fieldGetter);
}
public static void addFieldGetterToFirst(FieldGetter fieldGetter) {
FieldKit.addFieldGetterToFirst(fieldGetter);
}
public static void removeFieldGetter(Class extends FieldGetter> fieldGetterClass) {
FieldKit.removeFieldGetter(fieldGetterClass);
}
public static void setFastFieldKeyBuilder(boolean enable) {
FieldKeyBuilder.setFastFieldKeyBuilder(enable);
}
/**
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用,
* 性能提升 12.9%
*/
public static void setFastMode(boolean fastMode) {
FieldKit.setFastMode(fastMode);
FieldKeyBuilder.setFastFieldKeyBuilder(fastMode);
}
/**
* 设置为 true 支持表达式、变量名、方法名、模板函数名使用中文
*/
public static void setChineseExpression(boolean enable) {
CharTable.setChineseExpression(enable);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy