All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jfinal.template.Engine Maven / Gradle / Ivy

Go to download

Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.

The newest version!
/**
 * 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> 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 directiveClass, boolean keepLineBlank) { config.addDirective(directiveName, directiveClass, keepLineBlank); return this; } /** * 添加自定义指令,keepLineBlank 使用默认值 */ public Engine addDirective(String directiveName, Class 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 shared Method */ public Engine removeSharedMethod(String methodName, Class... paraTypes) { config.removeSharedMethod(methodName, paraTypes); 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 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); } /** * 设置为 true 支持静态方法调用表达式,自 jfinal 5.0.2 版本开始默认值为 false */ public Engine setStaticMethodExpression(boolean enable) { config.setStaticMethodExpression(enable); return this; } /** * 设置为 true 支持静态属性访问表达式,自 jfinal 5.0.2 版本开始默认值为 false */ public Engine setStaticFieldExpression(boolean enable) { config.setStaticFieldExpression(enable); return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy